[PATCH v3 4/6] pmdomain: renesas: Add R-Car X5H MDLC driver
Geert Uytterhoeven <[email protected]> Wed, 5 Aug 2026 17:20:52 +0200
| Newsgroups | org.kernel.vger.linux-renesas-soc,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-clk,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm |
|---|---|
| Message-ID | <89e6a71b35703f10e12f520af1f389b5f40f4b35.1785941595.git.geert+renesas@glider.be> |
Add a minimal Module Controller driver for the R-Car X5H (R8A78000) SoC. For now this just supports the always-on power domains, and dummy module clocks and resets for the serial console (which is enabled by the boot loader). Signed-off-by: Geert Uytterhoeven <[email protected]> --- v3: - Replace <linux/mod_devicetable.h> by more specific <linux/device-id/of.h> in v7.2-rc2, - Add a comment to document only always-on domains are supported, - Consistently use /* sentinel */ for zero sentinels, - Drop trailing commas from sentinels, v2: - Spin off from "pmdomain: renesas: Add R-Car X5H MDLC SCMI remapping driver", - Add default support not using SCMI, - Drop all SCMI remapping support, - Document use of -1 as a sentinel, - Rename struct r8a78000_mdlc_info to mdlc_info, - Print HW IDs in hexadecimal, - Reduce log level for unsupported MDLC instances from warn to dbg, --- drivers/pmdomain/renesas/Kconfig | 4 + drivers/pmdomain/renesas/Makefile | 1 + drivers/pmdomain/renesas/r8a78000-mdlc.c | 330 +++++++++++++++++++++++ drivers/soc/renesas/Kconfig | 1 + 4 files changed, 336 insertions(+) create mode 100644 drivers/pmdomain/renesas/r8a78000-mdlc.c diff --git a/drivers/pmdomain/renesas/Kconfig b/drivers/pmdomain/renesas/Kconfig index b507c3e0d723efc6..f2f52d3c29a083f1 100644 --- a/drivers/pmdomain/renesas/Kconfig +++ b/drivers/pmdomain/renesas/Kconfig @@ -13,6 +13,10 @@ config SYSC_RMOBILE bool "System Controller support for R-Mobile" if COMPILE_TEST # SoC +config MDLC_R8A78000 + bool "Module Controller support for R8A78000 (R-Car X5H)" if COMPILE_TEST + select RESET_CONTROLLER + config SYSC_R8A7742 bool "System Controller support for R8A7742 (RZ/G1H)" if COMPILE_TEST select SYSC_RCAR diff --git a/drivers/pmdomain/renesas/Makefile b/drivers/pmdomain/renesas/Makefile index 0391e6e67440a786..17849aad37a5ac4f 100644 --- a/drivers/pmdomain/renesas/Makefile +++ b/drivers/pmdomain/renesas/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 # SoC +obj-$(CONFIG_MDLC_R8A78000) += r8a78000-mdlc.o obj-$(CONFIG_SYSC_R8A7742) += r8a7742-sysc.o obj-$(CONFIG_SYSC_R8A7743) += r8a7743-sysc.o obj-$(CONFIG_SYSC_R8A7745) += r8a7745-sysc.o diff --git a/drivers/pmdomain/renesas/r8a78000-mdlc.c b/drivers/pmdomain/renesas/r8a78000-mdlc.c new file mode 100644 index 0000000000000000..5c8acead7cff9bdd --- /dev/null +++ b/drivers/pmdomain/renesas/r8a78000-mdlc.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R-Car X5H Module Controller + * + * Copyright (C) 2026 Glider bv + */ + +#include <linux/dev_printk.h> +#include <linux/device-id/of.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_domain.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> + +#include <dt-bindings/power/renesas,r8a78000-mdlc.h> + +struct mod_map { + int hw_id; /* Hardware module ID or -1 sentinel */ +}; + +struct mdlc_info { + u32 base; + const struct mod_map *mod_map; +}; + +/** + * struct r8a78000_mdlc_priv - Module Controller Private Data + * + * @link: Link into list of MDLC instances + * @genpd_data: PM domain provider data + * @rcdev: Reset controller entity + * @dev: MDLC device + * @np: Device node in DT representing the MDLC + * @mod_map: Mapping from hardware module IDs + */ +struct r8a78000_mdlc_priv { + struct hlist_node link; + struct genpd_onecell_data genpd_data; + struct reset_controller_dev rcdev; + struct device *dev; + struct device_node *np; + const struct mod_map *mod_map; +}; + +static struct generic_pm_domain *r8a78000_genpd_always_on; +static HLIST_HEAD(r8a78000_mdlc_list); +static DEFINE_MUTEX(r8a78000_mdlc_lock); /* protects the two above */ + +static struct generic_pm_domain *r8a78000_genpd_xlate( + const struct of_phandle_args *spec, void *data) +{ + struct r8a78000_mdlc_priv *priv = container_of(data, + struct r8a78000_mdlc_priv, genpd_data); + struct device *dev = priv->dev; + u32 id; + + if (spec->args_count != 2) + return ERR_PTR(-EINVAL); + + id = spec->args[0]; + + if (id >= R8A78000_MDLC_PD_AON) { + dev_dbg(dev, + "Mapping HW power domain 0x%x to always-on domain\n", + id); + return r8a78000_genpd_always_on; + } + + /* For now only always-on domains are supported */ + dev_err(dev, "Unknown power domain 0x%x\n", id); + return ERR_PTR(-ENOENT); +} + +#define rcdev_to_priv(_rcdev) \ + container_of(_rcdev, struct r8a78000_mdlc_priv, rcdev) + +static const struct mod_map *mod_map_find(const struct mod_map *map, u32 id) +{ + if (!map) + return NULL; + + for (; map->hw_id >= 0; map++) { + if (map->hw_id == id) + return map; + } + + return NULL; +} + +static int r8a78000_mdlc_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *spec) +{ + struct r8a78000_mdlc_priv *priv = rcdev_to_priv(rcdev); + struct device *dev = priv->dev; + const struct mod_map *map; + u32 id; + + if (spec->args_count != 1) + return -EINVAL; + + id = spec->args[0]; + + map = mod_map_find(priv->mod_map, id); + if (!map) { + dev_err(dev, "Unknown reset 0x%x\n", id); + return -ENOENT; + } + + dev_dbg(dev, "Ignoring HW reset 0x%x\n", id); + return id; +} + +#define DEFINE_MDLC_RESET_WRAPPER(op) \ + static int r8a78000_mdlc_ ## op(struct reset_controller_dev *rcdev, \ + unsigned long id) \ + { \ + struct r8a78000_mdlc_priv *priv = rcdev_to_priv(rcdev); \ + \ + dev_dbg(priv->dev, "%s: Ignoring\n", __func__); \ + return 0; \ + } + +DEFINE_MDLC_RESET_WRAPPER(reset) +DEFINE_MDLC_RESET_WRAPPER(assert) +DEFINE_MDLC_RESET_WRAPPER(deassert) +DEFINE_MDLC_RESET_WRAPPER(status) + +static const struct reset_control_ops r8a78000_mdlc_reset_ops = { + .reset = r8a78000_mdlc_reset, + .assert = r8a78000_mdlc_assert, + .deassert = r8a78000_mdlc_deassert, + .status = r8a78000_mdlc_status, +}; + +static int r8a78000_mdlc_attach_dev(struct generic_pm_domain *domain, + struct device *dev) +{ + struct device_node *np = dev->of_node; + struct r8a78000_mdlc_priv *priv; + struct of_phandle_args pd_spec; + const struct mod_map *map; + unsigned int id; + int ret; + + ret = of_parse_phandle_with_args(np, "power-domains", + "#power-domain-cells", 0, &pd_spec); + if (ret < 0) + return ret; + + if (pd_spec.args_count != 2) { + of_node_put(pd_spec.np); + return -EINVAL; + } + + scoped_guard(mutex, &r8a78000_mdlc_lock) { + hlist_for_each_entry(priv, &r8a78000_mdlc_list, link) { + if (priv->np == pd_spec.np) + break; + } + } + + if (!priv) { + dev_err(dev, "%s: MDLC %pOF not found\n", __func__, pd_spec.np); + of_node_put(pd_spec.np); + return -ENODEV; + } + + id = pd_spec.args[1]; + of_node_put(pd_spec.np); + + map = mod_map_find(priv->mod_map, id); + if (!map) { + dev_err(dev, "Unknown module 0x%x\n", id); + return -ENOENT; + } + + dev_dbg(dev, "Ignoring HW module 0x%x\n", id); + return 0; +} + +static void r8a78000_mdlc_unlink(void *data) +{ + struct r8a78000_mdlc_priv *priv = data; + + scoped_guard(mutex, &r8a78000_mdlc_lock) { + hlist_del(&priv->link); + } +} + +static void r8a78000_genpd_del_provider(void *data) +{ + of_genpd_del_provider(data); +} + +static int r8a78000_genpd_always_on_singleton(struct device *dev) +{ + struct generic_pm_domain *genpd; + int ret; + + guard(mutex)(&r8a78000_mdlc_lock); + + if (r8a78000_genpd_always_on) + return 0; + + genpd = kzalloc_obj(*genpd); + if (!genpd) + return -ENOMEM; + + genpd->name = "always-on"; + genpd->attach_dev = r8a78000_mdlc_attach_dev; + + ret = pm_genpd_init(genpd, &pm_domain_always_on_gov, false); + if (ret) { + kfree(genpd); + return dev_err_probe(dev, ret, + "Failed to create always-on domain\n"); + } + + r8a78000_genpd_always_on = genpd; + return 0; +} + +static int r8a78000_mdlc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct r8a78000_mdlc_priv *priv; + const struct mdlc_info *info; + struct resource *res; + int ret; + + ret = r8a78000_genpd_always_on_singleton(dev); + if (ret) + return ret; + + info = of_device_get_match_data(dev); + if (!info) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->np = np; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + for (; info->base; info++) { + if (info->base == res->start) + break; + } + + if (!info->base) { + dev_dbg(dev, "Unsupported MDLC instance 0x%pa\n", &res->start); + return -ENODEV; + } + + priv->mod_map = info->mod_map; + + scoped_guard(mutex, &r8a78000_mdlc_lock) { + hlist_add_head(&priv->link, &r8a78000_mdlc_list); + } + + ret = devm_add_action_or_reset(dev, r8a78000_mdlc_unlink, priv); + if (ret) + return dev_err_probe(dev, ret, "failed to add action\n"); + + /* Note that no actual domains are registered, just need translation */ + priv->genpd_data.xlate = r8a78000_genpd_xlate; + ret = of_genpd_add_provider_onecell(np, &priv->genpd_data); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register genpd provider\n"); + + ret = devm_add_action_or_reset(dev, r8a78000_genpd_del_provider, np); + if (ret) + return dev_err_probe(dev, ret, + "failed to add unregister action\n"); + + priv->rcdev.ops = &r8a78000_mdlc_reset_ops; + priv->rcdev.of_node = np; + priv->rcdev.of_reset_n_cells = 1; + priv->rcdev.of_xlate = r8a78000_mdlc_reset_xlate; + + ret = devm_reset_controller_register(dev, &priv->rcdev); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register reset controller\n"); + + return 0; +} + +static const struct mod_map r8a78000_mdlc_perw_mod_default[] = { + { 0x54 }, /* HSCIF0 */ + { -1 } +}; + +static const struct mdlc_info r8a78000_mdlc_default[] = { + { + .base = 0xc05d0000 /* mdlc_perw */, + .mod_map = r8a78000_mdlc_perw_mod_default, + }, + { /* sentinel */ } +}; + +static const struct of_device_id r8a78000_mdlc_match[] = { + { + .compatible = "renesas,r8a78000-mdlc", + .data = &r8a78000_mdlc_default, + }, + { /* sentinel */ } +}; + +static struct platform_driver r8a78000_mdlc_driver = { + .probe = r8a78000_mdlc_probe, + .driver = { + .name = "r8a78000-mdlc", + .of_match_table = r8a78000_mdlc_match, + .suppress_bind_attrs = true, + }, +}; + +builtin_platform_driver(r8a78000_mdlc_driver) + +MODULE_DESCRIPTION("R-Car X5H MDLC Driver"); diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig index fdf18ed2dfc2186d..eb8ac0d47d9d42b1 100644 --- a/drivers/soc/renesas/Kconfig +++ b/drivers/soc/renesas/Kconfig @@ -357,6 +357,7 @@ config ARCH_R8A78000 bool "ARM64 Platform support for R8A78000 (R-Car X5H)" default y if ARCH_RENESAS select ARCH_RCAR_GEN5 + select MDLC_R8A78000 help This enables support for the Renesas R-Car X5H SoC. -- 2.43.0