1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2024-2025 Qualcomm Innovation Center, Inc. All rights reserved.
4 */
5
6 #include <linux/clk.h>
7 #include <linux/coresight.h>
8 #include <linux/device.h>
9 #include <linux/err.h>
10 #include <linux/kernel.h>
11 #include <linux/init.h>
12 #include <linux/io.h>
13 #include <linux/module.h>
14 #include <linux/of.h>
15 #include <linux/platform_device.h>
16 #include <linux/pm_runtime.h>
17 #include <linux/slab.h>
18
19 #include "coresight-ctcu.h"
20 #include "coresight-priv.h"
21
22 DEFINE_CORESIGHT_DEVLIST(ctcu_devs, "ctcu");
23
24 #define ctcu_writel(drvdata, val, offset) __raw_writel((val), drvdata->base + offset)
25 #define ctcu_readl(drvdata, offset) __raw_readl(drvdata->base + offset)
26
27 /*
28 * The TMC Coresight Control Unit utilizes four ATID registers to control the data
29 * filter function based on the trace ID for each TMC ETR sink. The length of each
30 * ATID register is 32 bits. Therefore, an ETR device has a 128-bit long field
31 * in CTCU. Each trace ID is represented by one bit in that filed.
32 * e.g. ETR0ATID0 layout, set bit 5 for traceid 5
33 * bit5
34 * ------------------------------------------------------
35 * | |28| |24| |20| |16| |12| |8| 1|4| |0|
36 * ------------------------------------------------------
37 *
38 * e.g. ETR0:
39 * 127 0 from ATID_offset for ETR0ATID0
40 * -------------------------
41 * |ATID3|ATID2|ATID1|ATID0|
42 */
43 #define CTCU_ATID_REG_OFFSET(traceid, atid_offset) \
44 ((traceid / 32) * 4 + atid_offset)
45
46 #define CTCU_ATID_REG_BIT(traceid) (traceid % 32)
47 #define CTCU_ATID_REG_SIZE 0x10
48 #define CTCU_ETR0_ATID0 0xf8
49 #define CTCU_ETR1_ATID0 0x108
50
51 static const struct ctcu_etr_config sa8775p_etr_cfgs[] = {
52 {
53 .atid_offset = CTCU_ETR0_ATID0,
54 .port_num = 0,
55 },
56 {
57 .atid_offset = CTCU_ETR1_ATID0,
58 .port_num = 1,
59 },
60 };
61
62 static const struct ctcu_config sa8775p_cfgs = {
63 .etr_cfgs = sa8775p_etr_cfgs,
64 .num_etr_config = ARRAY_SIZE(sa8775p_etr_cfgs),
65 };
66
ctcu_program_atid_register(struct ctcu_drvdata * drvdata,u32 reg_offset,u8 bit,bool enable)67 static void ctcu_program_atid_register(struct ctcu_drvdata *drvdata, u32 reg_offset,
68 u8 bit, bool enable)
69 {
70 u32 val;
71
72 CS_UNLOCK(drvdata->base);
73 val = ctcu_readl(drvdata, reg_offset);
74 if (enable)
75 val |= BIT(bit);
76 else
77 val &= ~BIT(bit);
78
79 ctcu_writel(drvdata, val, reg_offset);
80 CS_LOCK(drvdata->base);
81 }
82
83 /*
84 * __ctcu_set_etr_traceid: Set bit in the ATID register based on trace ID when enable is true.
85 * Reset the bit of the ATID register based on trace ID when enable is false.
86 *
87 * @csdev: coresight_device of CTCU.
88 * @traceid: trace ID of the source tracer.
89 * @port_num: port number connected to TMC ETR sink.
90 * @enable: True for set bit and false for reset bit.
91 *
92 * Returns 0 indicates success. Non-zero result means failure.
93 */
__ctcu_set_etr_traceid(struct coresight_device * csdev,u8 traceid,int port_num,bool enable)94 static int __ctcu_set_etr_traceid(struct coresight_device *csdev, u8 traceid, int port_num,
95 bool enable)
96 {
97 struct ctcu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
98 u32 atid_offset, reg_offset;
99 u8 refcnt, bit;
100
101 atid_offset = drvdata->atid_offset[port_num];
102 if (atid_offset == 0)
103 return -EINVAL;
104
105 bit = CTCU_ATID_REG_BIT(traceid);
106 reg_offset = CTCU_ATID_REG_OFFSET(traceid, atid_offset);
107 if (reg_offset - atid_offset > CTCU_ATID_REG_SIZE)
108 return -EINVAL;
109
110 guard(raw_spinlock_irqsave)(&drvdata->spin_lock);
111 refcnt = drvdata->traceid_refcnt[port_num][traceid];
112 /* Only program the atid register when the refcnt value is 1 or 0 */
113 if ((enable && !refcnt++) || (!enable && !--refcnt))
114 ctcu_program_atid_register(drvdata, reg_offset, bit, enable);
115
116 drvdata->traceid_refcnt[port_num][traceid] = refcnt;
117
118 return 0;
119 }
120
121 /*
122 * Searching the sink device from helper's view in case there are multiple helper devices
123 * connected to the sink device.
124 */
ctcu_get_active_port(struct coresight_device * sink,struct coresight_device * helper)125 static int ctcu_get_active_port(struct coresight_device *sink, struct coresight_device *helper)
126 {
127 struct coresight_platform_data *pdata = helper->pdata;
128 int i;
129
130 for (i = 0; i < pdata->nr_inconns; ++i) {
131 if (pdata->in_conns[i]->src_dev == sink)
132 return pdata->in_conns[i]->dest_port;
133 }
134
135 return -EINVAL;
136 }
137
ctcu_set_etr_traceid(struct coresight_device * csdev,struct coresight_path * path,bool enable)138 static int ctcu_set_etr_traceid(struct coresight_device *csdev, struct coresight_path *path,
139 bool enable)
140 {
141 struct coresight_device *sink = coresight_get_sink(path);
142 u8 traceid = path->trace_id;
143 int port_num;
144
145 if ((sink == NULL) || !IS_VALID_CS_TRACE_ID(traceid)) {
146 dev_err(&csdev->dev, "Invalid sink device or trace ID\n");
147 return -EINVAL;
148 }
149
150 port_num = ctcu_get_active_port(sink, csdev);
151 if (port_num < 0)
152 return -EINVAL;
153
154 dev_dbg(&csdev->dev, "traceid is %d\n", traceid);
155
156 return __ctcu_set_etr_traceid(csdev, traceid, port_num, enable);
157 }
158
ctcu_enable(struct coresight_device * csdev,enum cs_mode mode,void * data)159 static int ctcu_enable(struct coresight_device *csdev, enum cs_mode mode, void *data)
160 {
161 struct coresight_path *path = (struct coresight_path *)data;
162
163 return ctcu_set_etr_traceid(csdev, path, true);
164 }
165
ctcu_disable(struct coresight_device * csdev,void * data)166 static int ctcu_disable(struct coresight_device *csdev, void *data)
167 {
168 struct coresight_path *path = (struct coresight_path *)data;
169
170 return ctcu_set_etr_traceid(csdev, path, false);
171 }
172
173 static const struct coresight_ops_helper ctcu_helper_ops = {
174 .enable = ctcu_enable,
175 .disable = ctcu_disable,
176 };
177
178 static const struct coresight_ops ctcu_ops = {
179 .helper_ops = &ctcu_helper_ops,
180 };
181
ctcu_probe(struct platform_device * pdev)182 static int ctcu_probe(struct platform_device *pdev)
183 {
184 const struct ctcu_etr_config *etr_cfg;
185 struct coresight_platform_data *pdata;
186 struct coresight_desc desc = { 0 };
187 struct device *dev = &pdev->dev;
188 const struct ctcu_config *cfgs;
189 struct ctcu_drvdata *drvdata;
190 void __iomem *base;
191 int i;
192
193 desc.name = coresight_alloc_device_name(&ctcu_devs, dev);
194 if (!desc.name)
195 return -ENOMEM;
196
197 drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
198 if (!drvdata)
199 return -ENOMEM;
200
201 pdata = coresight_get_platform_data(dev);
202 if (IS_ERR(pdata))
203 return PTR_ERR(pdata);
204 dev->platform_data = pdata;
205
206 base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
207 if (IS_ERR(base))
208 return PTR_ERR(base);
209
210 drvdata->apb_clk = coresight_get_enable_apb_pclk(dev);
211 if (IS_ERR(drvdata->apb_clk))
212 return -ENODEV;
213
214 cfgs = of_device_get_match_data(dev);
215 if (cfgs) {
216 if (cfgs->num_etr_config <= ETR_MAX_NUM) {
217 for (i = 0; i < cfgs->num_etr_config; i++) {
218 etr_cfg = &cfgs->etr_cfgs[i];
219 drvdata->atid_offset[i] = etr_cfg->atid_offset;
220 }
221 }
222 }
223
224 drvdata->base = base;
225 drvdata->dev = dev;
226 platform_set_drvdata(pdev, drvdata);
227
228 desc.type = CORESIGHT_DEV_TYPE_HELPER;
229 desc.subtype.helper_subtype = CORESIGHT_DEV_SUBTYPE_HELPER_CTCU;
230 desc.pdata = pdata;
231 desc.dev = dev;
232 desc.ops = &ctcu_ops;
233 desc.access = CSDEV_ACCESS_IOMEM(base);
234
235 drvdata->csdev = coresight_register(&desc);
236 if (IS_ERR(drvdata->csdev)) {
237 if (!IS_ERR_OR_NULL(drvdata->apb_clk))
238 clk_put(drvdata->apb_clk);
239
240 return PTR_ERR(drvdata->csdev);
241 }
242
243 return 0;
244 }
245
ctcu_remove(struct platform_device * pdev)246 static void ctcu_remove(struct platform_device *pdev)
247 {
248 struct ctcu_drvdata *drvdata = platform_get_drvdata(pdev);
249
250 coresight_unregister(drvdata->csdev);
251 }
252
ctcu_platform_probe(struct platform_device * pdev)253 static int ctcu_platform_probe(struct platform_device *pdev)
254 {
255 int ret;
256
257 pm_runtime_get_noresume(&pdev->dev);
258 pm_runtime_set_active(&pdev->dev);
259 pm_runtime_enable(&pdev->dev);
260
261 ret = ctcu_probe(pdev);
262 pm_runtime_put(&pdev->dev);
263 if (ret)
264 pm_runtime_disable(&pdev->dev);
265
266 return ret;
267 }
268
ctcu_platform_remove(struct platform_device * pdev)269 static void ctcu_platform_remove(struct platform_device *pdev)
270 {
271 struct ctcu_drvdata *drvdata = platform_get_drvdata(pdev);
272
273 if (WARN_ON(!drvdata))
274 return;
275
276 ctcu_remove(pdev);
277 pm_runtime_disable(&pdev->dev);
278 if (!IS_ERR_OR_NULL(drvdata->apb_clk))
279 clk_put(drvdata->apb_clk);
280 }
281
282 #ifdef CONFIG_PM
ctcu_runtime_suspend(struct device * dev)283 static int ctcu_runtime_suspend(struct device *dev)
284 {
285 struct ctcu_drvdata *drvdata = dev_get_drvdata(dev);
286
287 if (drvdata && !IS_ERR_OR_NULL(drvdata->apb_clk))
288 clk_disable_unprepare(drvdata->apb_clk);
289
290 return 0;
291 }
292
ctcu_runtime_resume(struct device * dev)293 static int ctcu_runtime_resume(struct device *dev)
294 {
295 struct ctcu_drvdata *drvdata = dev_get_drvdata(dev);
296
297 if (drvdata && !IS_ERR_OR_NULL(drvdata->apb_clk))
298 clk_prepare_enable(drvdata->apb_clk);
299
300 return 0;
301 }
302 #endif
303
304 static const struct dev_pm_ops ctcu_dev_pm_ops = {
305 SET_RUNTIME_PM_OPS(ctcu_runtime_suspend, ctcu_runtime_resume, NULL)
306 };
307
308 static const struct of_device_id ctcu_match[] = {
309 {.compatible = "qcom,sa8775p-ctcu", .data = &sa8775p_cfgs},
310 {}
311 };
312
313 static struct platform_driver ctcu_driver = {
314 .probe = ctcu_platform_probe,
315 .remove = ctcu_platform_remove,
316 .driver = {
317 .name = "coresight-ctcu",
318 .of_match_table = ctcu_match,
319 .pm = &ctcu_dev_pm_ops,
320 .suppress_bind_attrs = true,
321 },
322 };
323 module_platform_driver(ctcu_driver);
324
325 MODULE_LICENSE("GPL");
326 MODULE_DESCRIPTION("CoreSight TMC Control Unit driver");
327