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
+}