pmdomain: renesas: Add R-Car X5H MDLC driver

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 <geert+renesas@glider.be>
Signed-off-by: Ulf Hansson <ulfh@kernel.org>
This commit is contained in:
Geert Uytterhoeven
2026-08-11 21:04:44 +02:00
committed by Ulf Hansson
parent 558df30e1c
commit 9f79f93963
4 changed files with 331 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,325 @@
// 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;
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");

View File

@@ -358,6 +358,7 @@ config ARCH_R8A78000
default y if ARCH_RENESAS
default ARCH_RENESAS
select ARCH_RCAR_GEN5
select MDLC_R8A78000
help
This enables support for the Renesas R-Car X5H SoC.