[RFC PATCH 3/4] bus: ti-sysc: Implement reset control framework for soft reset

Tony Lindgren
2024-04-11T05:24:17Z

We can implement reset control framework for ti-sysc for the connected
devices to use for the interconnect target reset.

Signed-off-by: Tony Lindgren <tony@atomide.com>
---
drivers/bus/ti-sysc.c | 109 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)

diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c
--- a/drivers/bus/ti-sysc.c
+++ b/drivers/bus/ti-sysc.c
@@ -25,6 +25,7 @@
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
+#include <linux/reset-controller.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
@@ -42,6 +43,7 @@

#define SOC_FLAG(match, flag) { .machine = match, .data = (void *)(flag), }

+#define TI_SYSC_SOFTRESET_ID 0
#define MAX_MODULE_SOFTRESET_WAIT 10000

enum sysc_soc {
@@ -79,6 +81,11 @@ struct sysc_soc_info {
struct notifier_block nb;
};

+struct sysc_reset_lookup {
+ struct list_head node;
+ struct reset_control_lookup lookup;
+};
+
enum sysc_clocks {
SYSC_FCK,
SYSC_ICK,
@@ -147,6 +154,9 @@ struct sysc {
const char **clock_roles;
int nr_clocks;
struct reset_control *rsts;
+ struct reset_controller_dev rcdev;
+ struct list_head child_resets;
+ struct mutex child_lock; /* child device data list lock */
const char *legacy_mode;
const struct sysc_capabilities *cap;
struct sysc_config cfg;
@@ -2194,6 +2204,46 @@ static int sysc_reset(struct sysc *ddata)
return error;
}

+/*
+ * Only handles the softreset for the interconnect target, does not consider
+ * the device specific external resets. We must ensure the interconnect target
+ * is runtime PM active for the reset. And we must restore the sysconfig
+ * register after reset. Locking is currently not needed as we only touch the
+ * sysconfig register on PM runtime state changes, and no other sysconfig
+ * register access happens when the interconnect target is runtime PM active.
+ * Interconnect targets with multiple children must coordinate the reset
+ * usage with reset_control_get_shared().
+ */
+static int ti_sysc_reset_control_reset(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct sysc *ddata;
+ int error;
+
+ if (id != TI_SYSC_SOFTRESET_ID)
+ return -EINVAL;
+
+ ddata = container_of(rcdev, struct sysc, rcdev);
+
+ error = pm_runtime_resume_and_get(ddata->dev);
+ if (error < 0)
+ return error;
+
+ error = sysc_reset(ddata);
+ if (error)
+ dev_warn(ddata->dev, "reset failed: %i\n", error);
+
+ sysc_write_sysconfig(ddata, ddata->sysconfig);
+
+ pm_runtime_put_sync(ddata->dev);
+
+ return error;
+}
+
+static const struct reset_control_ops ti_sysc_reset_ops = {
+ .reset = ti_sysc_reset_control_reset,
+};
+
/*
* At this point the module is configured enough to read the revision but
* module may not be completely configured yet to use PM runtime. Enable
@@ -2408,6 +2458,45 @@ static int sysc_child_add_clocks(struct sysc *ddata,
return 0;
}

+static int sysc_child_add_reset(struct sysc *ddata,
+ struct device *child)
+{
+ struct sysc_reset_lookup *srl;
+
+ srl = kzalloc(sizeof(*srl), GFP_KERNEL);
+ if (!srl)
+ return -ENOMEM;
+
+ srl->lookup.provider = dev_name(ddata->dev);
+ srl->lookup.index = TI_SYSC_SOFTRESET_ID;
+ srl->lookup.dev_id = dev_name(child);
+ srl->lookup.con_id = "softreset";
+ reset_controller_add_lookup(&srl->lookup, 1);
+ mutex_lock(&ddata->child_lock);
+ list_add(&srl->node, &ddata->child_resets);
+ mutex_unlock(&ddata->child_lock);
+
+ return 0;
+}
+
+static void sysc_child_remove_reset(struct sysc *ddata,
+ struct device *child)
+{
+ struct sysc_reset_lookup *srl;
+
+ mutex_lock(&ddata->child_lock);
+ list_for_each_entry(srl, &ddata->child_resets, node) {
+ if (srl->lookup.index == TI_SYSC_SOFTRESET_ID &&
+ !strcmp(dev_name(child), srl->lookup.dev_id)) {
+ reset_controller_remove_lookup(&srl->lookup, 1);
+ list_del(&srl->node);
+ kfree(srl);
+ break;
+ }
+ }
+ mutex_unlock(&ddata->child_lock);
+}
+
static const struct device_type sysc_device_type = {
};

@@ -2541,6 +2630,14 @@ static int sysc_notifier_call(struct notifier_block *nb,
error = sysc_child_add_clocks(ddata, dev);
if (error)
return error;
+
+ error = sysc_child_add_reset(ddata, dev);
+ if (error)
+ return error;
+
+ break;
+ case BUS_NOTIFY_REMOVED_DEVICE:
+ sysc_child_remove_reset(ddata, dev);
break;
default:
break;
@@ -3186,6 +3283,8 @@ static int sysc_probe(struct platform_device *pdev)
ddata->offsets[SYSC_SYSCONFIG] = -ENODEV;
ddata->offsets[SYSC_SYSSTATUS] = -ENODEV;
ddata->dev = &pdev->dev;
+ mutex_init(&ddata->child_lock);
+ INIT_LIST_HEAD(&ddata->child_resets);
platform_set_drvdata(pdev, ddata);

error = sysc_init_static_data(ddata);
@@ -3266,6 +3365,16 @@ static int sysc_probe(struct platform_device *pdev)

ddata->dev->type = &sysc_device_type;

+ ddata->rcdev.owner = THIS_MODULE;
+ ddata->rcdev.nr_resets = 1;
+ ddata->rcdev.ops = &ti_sysc_reset_ops;
+ ddata->rcdev.dev = &pdev->dev;
+ ddata->rcdev.of_node = ddata->dev->of_node;
+
+ error = devm_reset_controller_register(ddata->dev, &ddata->rcdev);
+ if (error)
+ goto err;
+
if (!ddata->reserved) {
error = of_platform_populate(ddata->dev->of_node,
sysc_match_table,
--
2.44.0