1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
4 */
5
6 #include <linux/amba/bus.h>
7 #include <linux/coresight.h>
8 #include <linux/device.h>
9 #include <linux/io.h>
10 #include <linux/kernel.h>
11 #include <linux/module.h>
12 #include <linux/of.h>
13 #include <linux/platform_device.h>
14
15 #include "coresight-priv.h"
16 #include "coresight-trace-id.h"
17
18 #define TRACE_NOC_CTRL 0x008
19 #define TRACE_NOC_XLD 0x010
20 #define TRACE_NOC_FREQVAL 0x018
21 #define TRACE_NOC_SYNCR 0x020
22
23 /* Enable generation of output ATB traffic.*/
24 #define TRACE_NOC_CTRL_PORTEN BIT(0)
25 /* Sets the type of issued ATB FLAG packets.*/
26 #define TRACE_NOC_CTRL_FLAGTYPE BIT(7)
27 /* Sets the type of issued ATB FREQ packet*/
28 #define TRACE_NOC_CTRL_FREQTYPE BIT(8)
29
30 #define TRACE_NOC_SYNC_INTERVAL 0xFFFF
31
32 /*
33 * struct trace_noc_drvdata - specifics associated to a trace noc component
34 * @base: memory mapped base address for this component.
35 * @dev: device node for trace_noc_drvdata.
36 * @csdev: component vitals needed by the framework.
37 * @pclk: APB clock if present, otherwise NULL
38 * @spinlock: serialize enable/disable operation.
39 * @atid: id for the trace packet.
40 */
41 struct trace_noc_drvdata {
42 void __iomem *base;
43 struct device *dev;
44 struct coresight_device *csdev;
45 struct clk *pclk;
46 spinlock_t spinlock;
47 int atid;
48 };
49
trace_noc_enable_hw(struct trace_noc_drvdata * drvdata)50 static void trace_noc_enable_hw(struct trace_noc_drvdata *drvdata)
51 {
52 u32 val;
53
54 /* No valid ATID, simply enable the unit */
55 if (drvdata->atid == -EOPNOTSUPP) {
56 writel(TRACE_NOC_CTRL_PORTEN, drvdata->base + TRACE_NOC_CTRL);
57 return;
58 }
59
60 /* Set ATID */
61 writel_relaxed(drvdata->atid, drvdata->base + TRACE_NOC_XLD);
62
63 /* Set the data word count between 'SYNC' packets */
64 writel_relaxed(TRACE_NOC_SYNC_INTERVAL, drvdata->base + TRACE_NOC_SYNCR);
65
66 /* Set the Control register:
67 * - Set the FLAG packets to 'FLAG' packets
68 * - Set the FREQ packets to 'FREQ_TS' packets
69 * - Enable generation of output ATB traffic
70 */
71
72 val = readl_relaxed(drvdata->base + TRACE_NOC_CTRL);
73
74 val &= ~TRACE_NOC_CTRL_FLAGTYPE;
75 val |= TRACE_NOC_CTRL_FREQTYPE;
76 val |= TRACE_NOC_CTRL_PORTEN;
77
78 writel(val, drvdata->base + TRACE_NOC_CTRL);
79 }
80
trace_noc_enable(struct coresight_device * csdev,struct coresight_connection * inport,struct coresight_connection * outport)81 static int trace_noc_enable(struct coresight_device *csdev, struct coresight_connection *inport,
82 struct coresight_connection *outport)
83 {
84 struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
85
86 scoped_guard(spinlock, &drvdata->spinlock) {
87 if (csdev->refcnt == 0)
88 trace_noc_enable_hw(drvdata);
89
90 csdev->refcnt++;
91 }
92
93 dev_dbg(drvdata->dev, "Trace NOC is enabled\n");
94 return 0;
95 }
96
trace_noc_disable(struct coresight_device * csdev,struct coresight_connection * inport,struct coresight_connection * outport)97 static void trace_noc_disable(struct coresight_device *csdev, struct coresight_connection *inport,
98 struct coresight_connection *outport)
99 {
100 struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
101
102 scoped_guard(spinlock, &drvdata->spinlock) {
103 if (--csdev->refcnt == 0)
104 writel(0x0, drvdata->base + TRACE_NOC_CTRL);
105 }
106 dev_dbg(drvdata->dev, "Trace NOC is disabled\n");
107 }
108
trace_noc_id(struct coresight_device * csdev,__maybe_unused enum cs_mode mode,__maybe_unused struct coresight_device * sink)109 static int trace_noc_id(struct coresight_device *csdev, __maybe_unused enum cs_mode mode,
110 __maybe_unused struct coresight_device *sink)
111 {
112 struct trace_noc_drvdata *drvdata;
113
114 drvdata = dev_get_drvdata(csdev->dev.parent);
115
116 return drvdata->atid;
117 }
118
119 static const struct coresight_ops_link trace_noc_link_ops = {
120 .enable = trace_noc_enable,
121 .disable = trace_noc_disable,
122 };
123
124 static const struct coresight_ops trace_noc_cs_ops = {
125 .trace_id = trace_noc_id,
126 .link_ops = &trace_noc_link_ops,
127 };
128
trace_noc_init_default_data(struct trace_noc_drvdata * drvdata)129 static int trace_noc_init_default_data(struct trace_noc_drvdata *drvdata)
130 {
131 int atid;
132
133 if (!dev_is_amba(drvdata->dev)) {
134 drvdata->atid = -EOPNOTSUPP;
135 return 0;
136 }
137
138 atid = coresight_trace_id_get_system_id();
139 if (atid < 0)
140 return atid;
141
142 drvdata->atid = atid;
143
144 return 0;
145 }
146
traceid_show(struct device * dev,struct device_attribute * attr,char * buf)147 static ssize_t traceid_show(struct device *dev,
148 struct device_attribute *attr, char *buf)
149 {
150 unsigned long val;
151 struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev->parent);
152
153 val = drvdata->atid;
154 return sprintf(buf, "%#lx\n", val);
155 }
156 static DEVICE_ATTR_RO(traceid);
157
158 static struct attribute *coresight_tnoc_attrs[] = {
159 &dev_attr_traceid.attr,
160 NULL,
161 };
162
trace_id_is_visible(struct kobject * kobj,struct attribute * attr,int idx)163 static umode_t trace_id_is_visible(struct kobject *kobj,
164 struct attribute *attr, int idx)
165 {
166 struct device *dev = kobj_to_dev(kobj);
167 struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev->parent);
168
169 if (attr == &dev_attr_traceid.attr && drvdata->atid < 0)
170 return 0;
171
172 return attr->mode;
173 }
174
175 static const struct attribute_group coresight_tnoc_group = {
176 .attrs = coresight_tnoc_attrs,
177 .is_visible = trace_id_is_visible,
178 };
179
180 static const struct attribute_group *coresight_tnoc_groups[] = {
181 &coresight_tnoc_group,
182 NULL,
183 };
184
_tnoc_probe(struct device * dev,struct resource * res)185 static int _tnoc_probe(struct device *dev, struct resource *res)
186 {
187 struct coresight_platform_data *pdata;
188 struct trace_noc_drvdata *drvdata;
189 struct coresight_desc desc = { 0 };
190 int ret;
191
192 desc.name = coresight_alloc_device_name("traceNoc", dev);
193 if (!desc.name)
194 return -ENOMEM;
195
196 pdata = coresight_get_platform_data(dev);
197 if (IS_ERR(pdata))
198 return PTR_ERR(pdata);
199 dev->platform_data = pdata;
200
201 drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
202 if (!drvdata)
203 return -ENOMEM;
204
205 drvdata->dev = dev;
206 dev_set_drvdata(dev, drvdata);
207
208 ret = coresight_get_enable_clocks(dev, &drvdata->pclk, NULL);
209 if (ret)
210 return ret;
211
212 drvdata->base = devm_ioremap_resource(dev, res);
213 if (IS_ERR(drvdata->base))
214 return PTR_ERR(drvdata->base);
215
216 spin_lock_init(&drvdata->spinlock);
217
218 ret = trace_noc_init_default_data(drvdata);
219 if (ret)
220 return ret;
221
222 desc.ops = &trace_noc_cs_ops;
223 desc.type = CORESIGHT_DEV_TYPE_LINK;
224 desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG;
225 desc.pdata = pdata;
226 desc.dev = dev;
227 desc.access = CSDEV_ACCESS_IOMEM(drvdata->base);
228 desc.groups = coresight_tnoc_groups;
229 drvdata->csdev = coresight_register(&desc);
230 if (IS_ERR(drvdata->csdev)) {
231 if (drvdata->atid > 0)
232 coresight_trace_id_put_system_id(drvdata->atid);
233 return PTR_ERR(drvdata->csdev);
234 }
235
236 return 0;
237 }
238
trace_noc_probe(struct amba_device * adev,const struct amba_id * id)239 static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id)
240 {
241 int ret;
242
243 ret = _tnoc_probe(&adev->dev, &adev->res);
244 if (!ret)
245 pm_runtime_put(&adev->dev);
246
247 return ret;
248 }
249
trace_noc_remove(struct amba_device * adev)250 static void trace_noc_remove(struct amba_device *adev)
251 {
252 struct trace_noc_drvdata *drvdata = dev_get_drvdata(&adev->dev);
253
254 coresight_unregister(drvdata->csdev);
255 coresight_trace_id_put_system_id(drvdata->atid);
256 }
257
258 static struct amba_id trace_noc_ids[] = {
259 {
260 .id = 0x000f0c00,
261 .mask = 0x00ffff00,
262 },
263 {
264 .id = 0x001f0c00,
265 .mask = 0x00ffff00,
266 },
267 {},
268 };
269 MODULE_DEVICE_TABLE(amba, trace_noc_ids);
270
271 static struct amba_driver trace_noc_driver = {
272 .drv = {
273 .name = "coresight-trace-noc",
274 .suppress_bind_attrs = true,
275 },
276 .probe = trace_noc_probe,
277 .remove = trace_noc_remove,
278 .id_table = trace_noc_ids,
279 };
280
itnoc_probe(struct platform_device * pdev)281 static int itnoc_probe(struct platform_device *pdev)
282 {
283 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
284 int ret;
285
286 pm_runtime_get_noresume(&pdev->dev);
287 pm_runtime_set_active(&pdev->dev);
288 pm_runtime_enable(&pdev->dev);
289
290 ret = _tnoc_probe(&pdev->dev, res);
291 pm_runtime_put(&pdev->dev);
292 if (ret)
293 pm_runtime_disable(&pdev->dev);
294
295 return ret;
296 }
297
itnoc_remove(struct platform_device * pdev)298 static void itnoc_remove(struct platform_device *pdev)
299 {
300 struct trace_noc_drvdata *drvdata = platform_get_drvdata(pdev);
301
302 coresight_unregister(drvdata->csdev);
303 pm_runtime_disable(&pdev->dev);
304 }
305
306 #ifdef CONFIG_PM
itnoc_runtime_suspend(struct device * dev)307 static int itnoc_runtime_suspend(struct device *dev)
308 {
309 struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev);
310
311 clk_disable_unprepare(drvdata->pclk);
312
313 return 0;
314 }
315
itnoc_runtime_resume(struct device * dev)316 static int itnoc_runtime_resume(struct device *dev)
317 {
318 struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev);
319
320 return clk_prepare_enable(drvdata->pclk);
321 }
322 #endif
323
324 static const struct dev_pm_ops itnoc_dev_pm_ops = {
325 SET_RUNTIME_PM_OPS(itnoc_runtime_suspend, itnoc_runtime_resume, NULL)
326 };
327
328 static const struct of_device_id itnoc_of_match[] = {
329 { .compatible = "qcom,coresight-itnoc" },
330 {}
331 };
332 MODULE_DEVICE_TABLE(of, itnoc_of_match);
333
334 static struct platform_driver itnoc_driver = {
335 .probe = itnoc_probe,
336 .remove = itnoc_remove,
337 .driver = {
338 .name = "coresight-itnoc",
339 .of_match_table = itnoc_of_match,
340 .suppress_bind_attrs = true,
341 .pm = &itnoc_dev_pm_ops,
342 },
343 };
344
tnoc_init(void)345 static int __init tnoc_init(void)
346 {
347 return coresight_init_driver("tnoc", &trace_noc_driver, &itnoc_driver, THIS_MODULE);
348 }
349
tnoc_exit(void)350 static void __exit tnoc_exit(void)
351 {
352 coresight_remove_driver(&trace_noc_driver, &itnoc_driver);
353 }
354 module_init(tnoc_init);
355 module_exit(tnoc_exit);
356
357 MODULE_LICENSE("GPL");
358 MODULE_DESCRIPTION("Trace NOC driver");
359