diff --git a/drivers/gpio/gpio-mockup.c b/drivers/gpio/gpio-mockup.c
index 9d2de78a45c2..6577d18671df 100644
--- a/drivers/gpio/gpio-mockup.c
+++ b/drivers/gpio/gpio-mockup.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2014  Kamlakant Patel <kamlakant.patel@broadcom.com>
  * Copyright (C) 2015-2016  Bamvor Jian Zhang <bamv2005@gmail.com>
  * Copyright (C) 2017 Bartosz Golaszewski <brgl@bgdev.pl>
+ * Copyright (C) 2020 Bartosz Golaszewski <bgolaszewski@baylibre.com>
  */
 
 #include <linux/debugfs.h>
@@ -14,6 +15,7 @@
 #include <linux/irq.h>
 #include <linux/irq_sim.h>
 #include <linux/irqdomain.h>
+#include <linux/kernel.h>
 #include <linux/list.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
@@ -546,8 +548,8 @@ static void gpio_mockup_unregister_devices(void)
 	mutex_unlock(&gpio_mockup_devices_lock);
 }
 
-static __init char **gpio_mockup_make_line_names(const char *label,
-						 unsigned int num_lines)
+static char **gpio_mockup_make_line_names(const char *label,
+					  unsigned int num_lines)
 {
 	unsigned int i;
 	char **names;
@@ -567,7 +569,7 @@ static __init char **gpio_mockup_make_line_names(const char *label,
 	return names;
 }
 
-static int __init gpio_mockup_register_device(struct property_entry *properties)
+static int gpio_mockup_register_device(struct property_entry *properties)
 {
 	struct gpio_mockup_device *mockup_dev;
 	struct platform_device_info pdevinfo;
@@ -641,8 +643,7 @@ static int __init gpio_mockup_register_chips_from_params(void)
 {
 	int num_chips, i, ret;
 
-	if ((gpio_mockup_num_ranges < 2) ||
-	    (gpio_mockup_num_ranges % 2) ||
+	if ((gpio_mockup_num_ranges % 2) ||
 	    (gpio_mockup_num_ranges > GPIO_MOCKUP_MAX_RANGES))
 		return -EINVAL;
 
@@ -669,6 +670,205 @@ static int __init gpio_mockup_register_chips_from_params(void)
 	return 0;
 }
 
+/*
+ * We store all data associated with device properties in this structure. It's
+ * only needed until we register the platform device at which point the driver
+ * core makes a deep copy of all property data (even the string arrays).
+ *
+ * The reason to keep them bunched up is simple: we can have a single function
+ * to free all resources which simplifies error handling.
+ */
+struct gpio_mockup_prop_data {
+	char chip_label[GPIO_MOCKUP_LABEL_SIZE];
+	u16 ngpio;
+	bool named_lines;
+	char **line_names;
+};
+
+/* We don't free the structure itself - it's expected to live on the stack. */
+static void
+gpio_mockup_free_property_data(struct gpio_mockup_prop_data *prop_data)
+{
+	kfree_strarray(prop_data->line_names, prop_data->ngpio);
+}
+
+/*
+ * Each supported option is parsed by a separate callback - this way the
+ * 'new_device' attribute is easily exstensible.
+ */
+struct gpio_mockup_new_device_opt {
+	char *name;
+	int (*func)(const char *val, struct gpio_mockup_prop_data *prop_data,
+		    struct property_entry *properties, int *prop_idx);
+	bool has_val;
+};
+
+static int
+gpio_mockup_parse_label(const char *val,
+		struct gpio_mockup_prop_data *prop_data,
+		struct property_entry *properties, int *prop_idx)
+{
+	snprintf(prop_data->chip_label, sizeof(prop_data->chip_label), val);
+	properties[(*prop_idx)++] =
+		PROPERTY_ENTRY_STRING("chip-label", prop_data->chip_label);
+
+	return 0;
+}
+
+static int gpio_mockup_parse_num_lines(const char *val,
+			struct gpio_mockup_prop_data *prop_data,
+			struct property_entry *properties, int *prop_idx)
+{
+	int ret;
+
+	ret = kstrtou16(val, 10, &prop_data->ngpio);
+	if (ret) {
+		pr_err("invalid new_lines format: %s\n", val);
+		return ret;
+	}
+
+	properties[(*prop_idx)++] = PROPERTY_ENTRY_U16("nr-gpios",
+						     prop_data->ngpio);
+
+	return 0;
+}
+
+static int gpio_mockup_parse_named_lines(const char *val,
+			struct gpio_mockup_prop_data *prop_data,
+			struct property_entry *properties, int *prop_idx)
+{
+	prop_data->named_lines = true;
+
+	return 0;
+}
+
+static struct gpio_mockup_new_device_opt gpio_mockup_new_device_opts[] = {
+	{
+		.name = "label",
+		.func = gpio_mockup_parse_label,
+		.has_val = true,
+	},
+	{
+		.name = "num_lines",
+		.func = gpio_mockup_parse_num_lines,
+		.has_val = true,
+	},
+	{
+		.name = "named_lines",
+		.func = gpio_mockup_parse_named_lines,
+		.has_val = false,
+	},
+};
+
+static int
+gpio_mockup_parse_one_opt(const char *key, const char *val,
+			  struct gpio_mockup_prop_data *prop_data,
+			  struct property_entry *properties, int *prop_idx)
+{
+	struct gpio_mockup_new_device_opt *opt;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(gpio_mockup_new_device_opts); i++) {
+		opt = &gpio_mockup_new_device_opts[i];
+
+		if (strcmp(key, opt->name) == 0) {
+			if (opt->has_val && !val) {
+				pr_err("%s option requires an argument\n",
+				       opt->name);
+				return -EINVAL;
+			}
+
+			if (!opt->has_val && val) {
+				pr_err("%s option doesn't take any arguments\n",
+				       opt->name);
+				return -EINVAL;
+			}
+
+			return opt->func(val, prop_data, properties, prop_idx);
+		}
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int
+gpio_mockup_new_device_from_opts(char *opts,
+				 struct gpio_mockup_prop_data *prop_data)
+{
+	struct property_entry properties[GPIO_MOCKUP_MAX_PROP];
+	int prop_idx = 0, ret;
+	char *key, *val;
+
+	memset(properties, 0, sizeof(properties));
+
+	while (*opts) {
+		if (prop_idx >= GPIO_MOCKUP_MAX_PROP)
+			return -EINVAL;
+
+		opts = next_arg(opts, &key, &val);
+
+		ret = gpio_mockup_parse_one_opt(key, val, prop_data,
+						properties, &prop_idx);
+		if (ret)
+			return ret;
+	}
+
+	/* This is the only mandatory property. */
+	if (!prop_data->ngpio) {
+		pr_err("number of lines must be specified\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Line names must be created at the end - once we know how
+	 * many GPIOs there are.
+	 */
+	if (prop_data->named_lines) {
+		prop_data->line_names = gpio_mockup_make_line_names(
+							prop_data->chip_label,
+							prop_data->ngpio);
+		if (!prop_data->line_names)
+			return -ENOMEM;
+
+		properties[prop_idx++] =
+			PROPERTY_ENTRY_STRING_ARRAY_LEN("gpio-line-names",
+							prop_data->line_names,
+							prop_data->ngpio);
+	}
+
+	return gpio_mockup_register_device(properties);
+}
+
+static ssize_t gpio_mockup_debugfs_new_device_write(struct file *file,
+						    const char __user *usr_buf,
+						    size_t size, loff_t *ppos)
+{
+	struct gpio_mockup_prop_data prop_data;
+	char opts[128];
+	int ret;
+
+	if (*ppos != 0 || size > sizeof(opts))
+		return -EINVAL;
+
+	ret = getline_from_user(opts, sizeof(opts), usr_buf, size);
+	if (ret < 0)
+		return ret;
+
+	memset(&prop_data, 0, sizeof(prop_data));
+
+	ret = gpio_mockup_new_device_from_opts(opts, &prop_data);
+	gpio_mockup_free_property_data(&prop_data);
+	return ret < 0 ? ret : size;
+}
+
+static const struct file_operations gpio_mockup_debugfs_new_device_ops = {
+	.owner = THIS_MODULE,
+	.open = gpio_mockup_debugfs_open,
+	.write = gpio_mockup_debugfs_new_device_write,
+	.llseek = no_llseek,
+	.release = single_release,
+};
+
 static ssize_t gpio_mockup_debugfs_delete_device_write(struct file *file,
 						const char __user *usr_buf,
 						size_t size, loff_t *ppos)
@@ -726,6 +926,13 @@ static int __init gpio_mockup_debugfs_init(void)
 	if (IS_ERR(gpio_mockup_dbg_dir))
 		return PTR_ERR(gpio_mockup_dbg_dir);
 
+	entry = debugfs_create_file("new_device", 0200, gpio_mockup_dbg_dir,
+				NULL, &gpio_mockup_debugfs_new_device_ops);
+	if (IS_ERR(entry)) {
+		debugfs_remove_recursive(gpio_mockup_dbg_dir);
+		return PTR_ERR(entry);
+	}
+
 	entry = debugfs_create_file("delete_device", 0200, gpio_mockup_dbg_dir,
 				NULL, &gpio_mockup_debugfs_delete_device_ops);
 	if (IS_ERR(entry)) {
@@ -751,12 +958,14 @@ static int __init gpio_mockup_init(void)
 		return ret;
 	}
 
-	ret = gpio_mockup_register_chips_from_params();
-	if (ret) {
-		pr_err("error registering device");
-		debugfs_remove_recursive(gpio_mockup_dbg_dir);
-		platform_driver_unregister(&gpio_mockup_driver);
-		return ret;
+	if (gpio_mockup_ranges > 0) {
+		ret = gpio_mockup_register_chips_from_params();
+		if (ret) {
+			pr_err("error registering device");
+			debugfs_remove_recursive(gpio_mockup_dbg_dir);
+			platform_driver_unregister(&gpio_mockup_driver);
+			return ret;
+		}
 	}
 
 	return 0;
