xref: /linux/drivers/firmware/imx/sm-misc.c (revision bba2c3615bd6cfee7456d1130f2e6b01b3f4e9ba)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright 2024 NXP
4  */
5 
6 #include <linux/debugfs.h>
7 #include <linux/device/devres.h>
8 #include <linux/firmware/imx/sm.h>
9 #include <linux/module.h>
10 #include <linux/of.h>
11 #include <linux/platform_device.h>
12 #include <linux/scmi_protocol.h>
13 #include <linux/scmi_imx_protocol.h>
14 #include <linux/seq_file.h>
15 #include <linux/sizes.h>
16 
17 static const struct scmi_imx_misc_proto_ops *imx_misc_ctrl_ops;
18 static struct scmi_protocol_handle *ph;
19 static struct notifier_block scmi_imx_misc_ctrl_nb;
20 
21 static const char * const rst_imx95[] = {
22 	"cm33_lockup", "cm33_swreq", "cm7_lockup", "cm7_swreq", "fccu",
23 	"jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4",
24 	"wdog5", "jtag", "cm33_exc", "bbm", "sw", "sm_err", "fusa_sreco",
25 	"pmic", "unused", "unused", "unused", "unused", "unused", "unused",
26 	"unused", "unused", "unused", "unused", "unused", "por",
27 };
28 
29 static const char * const rst_imx94[] = {
30 	"cm33_lockup", "cm33_swreq", "cm70_lockup", "cm70_swreq", "fccu",
31 	"jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4",
32 	"wdog5", "jtag", "wdog6", "wdog7", "wdog8", "wo_netc", "cm33s_lockup",
33 	"cm33s_swreq", "cm71_lockup", "cm71_swreq", "cm33_exc", "bbm", "sw",
34 	"sm_err", "fusa_sreco", "pmic", "unused", "unused", "unused", "por",
35 };
36 
37 static const struct of_device_id allowlist[] = {
38 	{ .compatible = "fsl,imx952", .data = rst_imx95 },
39 	{ .compatible = "fsl,imx95", .data = rst_imx95 },
40 	{ .compatible = "fsl,imx94", .data = rst_imx94 },
41 	{ /* Sentinel */ }
42 };
43 
44 int scmi_imx_misc_ctrl_set(u32 id, u32 val)
45 {
46 	if (!ph)
47 		return -EPROBE_DEFER;
48 
49 	return imx_misc_ctrl_ops->misc_ctrl_set(ph, id, 1, &val);
50 };
51 EXPORT_SYMBOL(scmi_imx_misc_ctrl_set);
52 
53 int scmi_imx_misc_ctrl_get(u32 id, u32 *num, u32 *val)
54 {
55 	if (!ph)
56 		return -EPROBE_DEFER;
57 
58 	return imx_misc_ctrl_ops->misc_ctrl_get(ph, id, num, val);
59 }
60 EXPORT_SYMBOL(scmi_imx_misc_ctrl_get);
61 
62 static int scmi_imx_misc_ctrl_notifier(struct notifier_block *nb,
63 				       unsigned long event, void *data)
64 {
65 	/*
66 	 * notifier_chain_register requires a valid notifier_block and
67 	 * valid notifier_call. SCMI_EVENT_IMX_MISC_CONTROL is needed
68 	 * to let SCMI firmware enable control events, but the hook here
69 	 * is just a dummy function to avoid kernel panic as of now.
70 	 */
71 	return 0;
72 }
73 
74 static int syslog_show(struct seq_file *file, void *priv)
75 {
76 	/* 4KB is large enough for syslog */
77 	void *syslog __free(kfree) = kmalloc(SZ_4K, GFP_KERNEL);
78 	/* syslog API use num words, not num bytes */
79 	u16 size = SZ_4K / 4;
80 	int ret;
81 
82 	if (!ph)
83 		return -ENODEV;
84 
85 	ret = imx_misc_ctrl_ops->misc_syslog(ph, &size, syslog);
86 	if (ret)
87 		return ret;
88 
89 	seq_hex_dump(file, " ", DUMP_PREFIX_NONE, 16, sizeof(u32), syslog, size * 4, false);
90 	seq_putc(file, '\n');
91 
92 	return 0;
93 }
94 DEFINE_SHOW_ATTRIBUTE(syslog);
95 
96 static void scmi_imx_misc_put(void *p)
97 {
98 	debugfs_remove((struct dentry *)p);
99 }
100 
101 static int scmi_imx_misc_get_reason(struct scmi_device *sdev)
102 {
103 	struct scmi_imx_misc_reset_reason boot, shutdown;
104 	const char **rst;
105 	bool system = true;
106 	int ret;
107 
108 	if (!of_machine_device_match(allowlist))
109 		return 0;
110 
111 	rst = (const char **)of_machine_get_match_data(allowlist);
112 
113 	ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL);
114 	if (!ret) {
115 		if (boot.valid)
116 			dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n",
117 				 system ? "SYS" : "LM", rst[boot.reason],
118 				 boot.orig_valid ? boot.origin : -1,
119 				 boot.err_valid ? boot.errid : -1);
120 		if (shutdown.valid)
121 			dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n",
122 				 system ? "SYS" : "LM", rst[shutdown.reason],
123 				 shutdown.orig_valid ? shutdown.origin : -1,
124 				 shutdown.err_valid ? shutdown.errid : -1);
125 	} else {
126 		dev_err(&sdev->dev, "Failed to get system reset reason: %d\n", ret);
127 	}
128 
129 	system = false;
130 	ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL);
131 	if (!ret) {
132 		if (boot.valid)
133 			dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n",
134 				 system ? "SYS" : "LM", rst[boot.reason],
135 				 boot.orig_valid ? boot.origin : -1,
136 				 boot.err_valid ? boot.errid : -1);
137 		if (shutdown.valid)
138 			dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n",
139 				 system ? "SYS" : "LM", rst[shutdown.reason],
140 				 shutdown.orig_valid ? shutdown.origin : -1,
141 				 shutdown.err_valid ? shutdown.errid : -1);
142 	} else {
143 		dev_err(&sdev->dev, "Failed to get lm reset reason: %d\n", ret);
144 	}
145 
146 	return 0;
147 }
148 
149 static int scmi_imx_misc_ctrl_probe(struct scmi_device *sdev)
150 {
151 	const struct scmi_handle *handle = sdev->handle;
152 	struct device_node *np = sdev->dev.of_node;
153 	struct dentry *scmi_imx_dentry;
154 	u32 src_id, flags;
155 	int ret, i, num;
156 
157 	if (!handle)
158 		return -ENODEV;
159 
160 	if (imx_misc_ctrl_ops) {
161 		dev_err(&sdev->dev, "misc ctrl already initialized\n");
162 		return -EEXIST;
163 	}
164 
165 	imx_misc_ctrl_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_IMX_MISC, &ph);
166 	if (IS_ERR(imx_misc_ctrl_ops))
167 		return PTR_ERR(imx_misc_ctrl_ops);
168 
169 	num = of_property_count_u32_elems(np, "nxp,ctrl-ids");
170 	if (num % 2) {
171 		dev_err(&sdev->dev, "Invalid wakeup-sources\n");
172 		return -EINVAL;
173 	}
174 
175 	scmi_imx_misc_ctrl_nb.notifier_call = &scmi_imx_misc_ctrl_notifier;
176 	for (i = 0; i < num; i += 2) {
177 		ret = of_property_read_u32_index(np, "nxp,ctrl-ids", i, &src_id);
178 		if (ret) {
179 			dev_err(&sdev->dev, "Failed to read ctrl-id: %i\n", i);
180 			continue;
181 		}
182 
183 		ret = of_property_read_u32_index(np, "nxp,ctrl-ids", i + 1, &flags);
184 		if (ret) {
185 			dev_err(&sdev->dev, "Failed to read ctrl-id value: %d\n", i + 1);
186 			continue;
187 		}
188 
189 		ret = handle->notify_ops->devm_event_notifier_register(sdev, SCMI_PROTOCOL_IMX_MISC,
190 								       SCMI_EVENT_IMX_MISC_CONTROL,
191 								       &src_id,
192 								       &scmi_imx_misc_ctrl_nb);
193 		if (ret) {
194 			dev_err(&sdev->dev, "Failed to register scmi misc event: %d\n", src_id);
195 		} else {
196 			ret = imx_misc_ctrl_ops->misc_ctrl_req_notify(ph, src_id,
197 								      SCMI_EVENT_IMX_MISC_CONTROL,
198 								      flags);
199 			if (ret)
200 				dev_err(&sdev->dev, "Failed to req notify: %d\n", src_id);
201 		}
202 	}
203 
204 	scmi_imx_dentry = debugfs_create_dir("scmi_imx", NULL);
205 	debugfs_create_file("syslog", 0444, scmi_imx_dentry, &sdev->dev, &syslog_fops);
206 
207 	scmi_imx_misc_get_reason(sdev);
208 
209 	return devm_add_action_or_reset(&sdev->dev, scmi_imx_misc_put, scmi_imx_dentry);
210 }
211 
212 static const struct scmi_device_id scmi_id_table[] = {
213 	{ SCMI_PROTOCOL_IMX_MISC, "imx-misc-ctrl" },
214 	{ },
215 };
216 MODULE_DEVICE_TABLE(scmi, scmi_id_table);
217 
218 static struct scmi_driver scmi_imx_misc_ctrl_driver = {
219 	.name = "scmi-imx-misc-ctrl",
220 	.probe = scmi_imx_misc_ctrl_probe,
221 	.id_table = scmi_id_table,
222 };
223 module_scmi_driver(scmi_imx_misc_ctrl_driver);
224 
225 MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
226 MODULE_DESCRIPTION("IMX SM MISC driver");
227 MODULE_LICENSE("GPL");
228