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