diff mbox series

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

Message ID 20240411052257.2113-4-tony@atomide.com
State New
Headers show
Series Provide interconnect resets for ti-sysc users | expand

Commit Message

Tony Lindgren April 11, 2024, 5:22 a.m. UTC
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 mbox series

Patch

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,