device_mmio: Introduce DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME

Currently the device MMIO APIs is only able to map single DT-defined
regions and also the _NAMED variant is assuming that each DT-defined
device has only one single region to map.

This is a limitation and a problem when in the DT are defined devices
with multiple regions that need to be mapped.

This patch is trying to overcome this limitation by introducing the
DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME macro that leveraged the 'reg-names'
DT property to map multiple regions defined by a single device.

So for example in the DT we can have a device like:

  driver@c4000000 {
    reg = <0xc4000000 0x1000>, <0xc4001000 0x1000>;
    reg-names = "region0", "region1";
  };

and then we can use DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME doing:

  struct driver_config config = {
    DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(region0, DT_DRV_INST(0)),
    DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(region1, DT_DRV_INST(0)),
  };

Signed-off-by: Carlo Caione <ccaione@baylibre.com>
diff --git a/doc/kernel/drivers/index.rst b/doc/kernel/drivers/index.rst
index 1424372..977328e 100644
--- a/doc/kernel/drivers/index.rst
+++ b/doc/kernel/drivers/index.rst
@@ -529,6 +529,37 @@
       ...
    }
 
+Device Model Drivers with multiple MMIO regions in the same DT node
+===================================================================
+
+Some drivers may have multiple MMIO regions defined into the same DT device
+node using the ``reg-names`` property to differentiate them, for example:
+
+.. code-block:: devicetree
+
+   /dts-v1/;
+
+   / {
+           a-driver@40000000 {
+                   reg = <0x40000000 0x1000>,
+                         <0x40001000 0x1000>;
+                   reg-names = "corge", "grault";
+           };
+   };
+
+This can be managed as seen in the previous section but this time using the
+``DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME`` macro instead. So the only difference
+would be in the driver config struct:
+
+.. code-block:: C
+
+   const static struct my_driver_config my_driver_config_0 = {
+      ...
+      DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(corge, DT_DRV_INST(...)),
+      DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(grault, DT_DRV_INST(...)),
+      ...
+   }
+
 Drivers that do not use Zephyr Device Model
 ===========================================
 
diff --git a/include/zephyr/sys/device_mmio.h b/include/zephyr/sys/device_mmio.h
index 5052611..1849f4a 100644
--- a/include/zephyr/sys/device_mmio.h
+++ b/include/zephyr/sys/device_mmio.h
@@ -62,6 +62,12 @@
 		.size = DT_REG_SIZE(node_id) \
 	}
 
+#define Z_DEVICE_MMIO_NAMED_ROM_INITIALIZER(name, node_id) \
+	{ \
+		.phys_addr = DT_REG_ADDR_BY_NAME(node_id, name), \
+		.size = DT_REG_SIZE_BY_NAME(node_id, name) \
+	}
+
 /**
  * Set linear address for device MMIO access
  *
@@ -112,6 +118,12 @@
 	{ \
 		.addr = DT_REG_ADDR(node_id) \
 	}
+
+#define Z_DEVICE_MMIO_NAMED_ROM_INITIALIZER(name, node_id) \
+	{ \
+		.addr = DT_REG_ADDR_BY_NAME(node_id, name) \
+	}
+
 #endif /* DEVICE_MMIO_IS_IN_RAM */
 #endif /* !_ASMLANGUAGE */
 /** @} */
@@ -436,6 +448,49 @@
 	.name = Z_DEVICE_MMIO_ROM_INITIALIZER(node_id)
 
 /**
+ * @def DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(name, node_id)
+ *
+ * @brief Initialize a named DEVICE_MMIO_NAMED_ROM member using a named DT
+ *        reg property.
+ *
+ * Same as @ref DEVICE_MMIO_NAMED_ROM_INIT but the size and address are taken
+ * from a named DT reg property.
+ *
+ * Example for an instance of a driver belonging to the "foo" subsystem
+ * that will have two DT-defined regions named 'chip' and 'dale':
+ *
+ * @code{.dts}
+ *
+ *    foo@E5000000 {
+ *         reg = <0xE5000000 0x1000>, <0xE6000000 0x1000>;
+ *         reg-names = "chip", "dale";
+ *         ...
+ *    };
+ *
+ * @endcode
+ *
+ * @code{.c}
+ *
+ * struct foo_config my_config = {
+ *	bar = 7;
+ *	DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(chip, DT_DRV_INST(...));
+ *	DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(dale, DT_DRV_INST(...));
+ *	baz = 2;
+ *	...
+ * }
+ *
+ * @endcode
+ *
+ * @see DEVICE_MMIO_NAMED_ROM_INIT()
+ *
+ * @param name Member name within config for the MMIO region and name of the
+ *             reg property in the DT
+ * @param node_id DTS node identifier
+ */
+#define DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(name, node_id) \
+	.name = Z_DEVICE_MMIO_NAMED_ROM_INITIALIZER(name, node_id)
+
+/**
  * @def DEVICE_MMIO_NAMED_MAP(dev, name, flags)
  *
  * @brief Set up memory for a named MMIO region
diff --git a/tests/kernel/device/app.overlay b/tests/kernel/device/app.overlay
index 5fb05ce..aae8873 100644
--- a/tests/kernel/device/app.overlay
+++ b/tests/kernel/device/app.overlay
@@ -42,4 +42,13 @@
 		reg = <0xE4000000 0x2000>;
 		status = "okay";
 	};
+
+	fakedriver_multireg@E5000000 {
+		compatible = "fakedriver_multireg";
+		reg = <0xE5000000 0x1000>,
+		      <0xE6000000 0x1000>;
+		reg-names = "chip",
+			    "dale";
+		status = "okay";
+	};
 };
diff --git a/tests/kernel/device/src/main.c b/tests/kernel/device/src/main.c
index d487ea5..0b17ad9 100644
--- a/tests/kernel/device/src/main.c
+++ b/tests/kernel/device/src/main.c
@@ -24,6 +24,7 @@
 extern void test_mmio_toplevel(void);
 extern void test_mmio_single(void);
 extern void test_mmio_device_map(void);
+extern void test_mmio_multireg(void);
 
 /**
  * @brief Test cases to verify device objects
@@ -364,6 +365,7 @@
 			 ztest_unit_test(test_mmio_single),
 			 ztest_unit_test(test_mmio_multiple),
 			 ztest_unit_test(test_mmio_toplevel),
-			 ztest_unit_test(test_mmio_device_map));
+			 ztest_unit_test(test_mmio_device_map),
+			 ztest_unit_test(test_mmio_multireg));
 	ztest_run_test_suite(device);
 }
diff --git a/tests/kernel/device/src/mmio_multireg.c b/tests/kernel/device/src/mmio_multireg.c
new file mode 100644
index 0000000..c54a1a4
--- /dev/null
+++ b/tests/kernel/device/src/mmio_multireg.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2020 Intel Corporation
+ * Copyright (c) 2022 Carlo Caione <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <ztest.h>
+#include <zephyr/device.h>
+
+#define DT_DRV_COMPAT	fakedriver_multireg
+
+/*
+ * Driver with multiple MMIO regions to manage defined into DT
+ */
+
+struct foo_multireg_dev_data {
+	int baz;
+
+	DEVICE_MMIO_NAMED_RAM(chip);
+	DEVICE_MMIO_NAMED_RAM(dale);
+};
+
+struct foo_multireg_dev_data foo_multireg_data;
+
+struct foo_multireg_config_info {
+	DEVICE_MMIO_NAMED_ROM(chip);
+	DEVICE_MMIO_NAMED_ROM(dale);
+};
+
+const struct foo_multireg_config_info foo_multireg_config = {
+	DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(chip, DT_DRV_INST(0)),
+	DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(dale, DT_DRV_INST(0))
+};
+
+#define DEV_DATA(dev)	((struct foo_multireg_dev_data *)((dev)->data))
+#define DEV_CFG(dev)	((struct foo_multireg_config_info *)((dev)->config))
+
+int foo_multireg_init(const struct device *dev)
+{
+	DEVICE_MMIO_NAMED_MAP(dev, chip, K_MEM_CACHE_NONE);
+	DEVICE_MMIO_NAMED_MAP(dev, dale, K_MEM_CACHE_NONE);
+
+	return 0;
+}
+
+DEVICE_DEFINE(foo_multireg, "foo_multireg", foo_multireg_init, NULL,
+		&foo_multireg_data, &foo_multireg_config,
+		POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
+		(void *)0xDEADBEEF);
+/**
+ * @brief Test DEVICE_MMIO_NAMED_* macros
+ *
+ * This is the same as the @ref test_mmio_multiple test but in this test the
+ * memory regions are created by the named DT property 'reg'.
+ *
+ * @see test_mmio_multiple
+ *
+ * @ingroup kernel_device_tests
+ */
+void test_mmio_multireg(void)
+{
+	const struct device *dev = device_get_binding("foo_multireg");
+	mm_reg_t regs_chip, regs_dale;
+	const struct z_device_mmio_rom *rom_chip, *rom_dale;
+
+	zassert_not_null(dev, "null foo_multireg");
+
+	regs_chip = DEVICE_MMIO_NAMED_GET(dev, chip);
+	regs_dale = DEVICE_MMIO_NAMED_GET(dev, dale);
+	rom_chip = DEVICE_MMIO_NAMED_ROM_PTR(dev, chip);
+	rom_dale = DEVICE_MMIO_NAMED_ROM_PTR(dev, dale);
+
+	zassert_not_equal(regs_chip, 0, "bad regs_chip");
+	zassert_not_equal(regs_dale, 0, "bad regs_dale");
+
+#ifdef DEVICE_MMIO_IS_IN_RAM
+	zassert_equal(rom_chip->phys_addr, DT_INST_REG_ADDR_BY_NAME(0, chip),
+		      "bad phys_addr (chip)");
+	zassert_equal(rom_chip->size, DT_INST_REG_SIZE_BY_NAME(0, chip),
+		      "bad size (chip)");
+	zassert_equal(rom_dale->phys_addr, DT_INST_REG_ADDR_BY_NAME(0, dale),
+		      "bad phys_addr (dale)");
+	zassert_equal(rom_dale->size, DT_INST_REG_SIZE_BY_NAME(0, dale),
+		      "bad size (dale)");
+#else
+	zassert_equal(rom_chip->addr, DT_INST_REG_ADDR_BY_NAME(0, chip),
+		      "bad addr (chip)");
+	zassert_equal(regs_chip, rom_chip->addr, "bad regs (chip)");
+	zassert_equal(rom_dale->addr, DT_INST_REG_ADDR_BY_NAME(0, dale),
+		      "bad addr (dale)");
+	zassert_equal(regs_dale, rom_dale->addr, "bad regs (dale)");
+	zassert_equal(sizeof(struct foo_multireg_dev_data), sizeof(int),
+		      "too big foo_multireg_dev_data");
+#endif
+}