xref: /linux/drivers/hwtracing/coresight/coresight-tnoc.c (revision cb4eb6771c0f8fd1c52a8f6fdec7762fb087380a)
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