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,struct coresight_path * path)159 static int ctcu_enable(struct coresight_device *csdev, enum cs_mode mode,
160 struct coresight_path *path)
161 {
162 return ctcu_set_etr_traceid(csdev, path, true);
163 }
164
ctcu_disable(struct coresight_device * csdev,struct coresight_path * path)165 static int ctcu_disable(struct coresight_device *csdev, struct coresight_path *path)
166 {
167 return ctcu_set_etr_traceid(csdev, path, false);
168 }
169
170 static const struct coresight_ops_helper ctcu_helper_ops = {
171 .enable = ctcu_enable,
172 .disable = ctcu_disable,
173 };
174
175 static const struct coresight_ops ctcu_ops = {
176 .helper_ops = &ctcu_helper_ops,
177 };
178
ctcu_probe(struct platform_device * pdev)179 static int ctcu_probe(struct platform_device *pdev)
180 {
181 const struct ctcu_etr_config *etr_cfg;
182 struct coresight_platform_data *pdata;
183 struct coresight_desc desc = { 0 };
184 struct device *dev = &pdev->dev;
185 const struct ctcu_config *cfgs;
186 struct ctcu_drvdata *drvdata;
187 void __iomem *base;
188 int i, ret;
189
190 desc.name = coresight_alloc_device_name(&ctcu_devs, dev);
191 if (!desc.name)
192 return -ENOMEM;
193
194 drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
195 if (!drvdata)
196 return -ENOMEM;
197
198 pdata = coresight_get_platform_data(dev);
199 if (IS_ERR(pdata))
200 return PTR_ERR(pdata);
201 dev->platform_data = pdata;
202
203 base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
204 if (IS_ERR(base))
205 return PTR_ERR(base);
206
207 ret = coresight_get_enable_clocks(dev, &drvdata->apb_clk, NULL);
208 if (ret)
209 return ret;
210
211 cfgs = of_device_get_match_data(dev);
212 if (cfgs) {
213 if (cfgs->num_etr_config <= ETR_MAX_NUM) {
214 for (i = 0; i < cfgs->num_etr_config; i++) {
215 etr_cfg = &cfgs->etr_cfgs[i];
216 drvdata->atid_offset[i] = etr_cfg->atid_offset;
217 }
218 }
219 }
220
221 drvdata->base = base;
222 drvdata->dev = dev;
223 platform_set_drvdata(pdev, drvdata);
224
225 desc.type = CORESIGHT_DEV_TYPE_HELPER;
226 desc.subtype.helper_subtype = CORESIGHT_DEV_SUBTYPE_HELPER_CTCU;
227 desc.pdata = pdata;
228 desc.dev = dev;
229 desc.ops = &ctcu_ops;
230 desc.access = CSDEV_ACCESS_IOMEM(base);
231
232 drvdata->csdev = coresight_register(&desc);
233 if (IS_ERR(drvdata->csdev))
234 return PTR_ERR(drvdata->csdev);
235
236 return 0;
237 }
238
ctcu_remove(struct platform_device * pdev)239 static void ctcu_remove(struct platform_device *pdev)
240 {
241 struct ctcu_drvdata *drvdata = platform_get_drvdata(pdev);
242
243 coresight_unregister(drvdata->csdev);
244 }
245
ctcu_platform_probe(struct platform_device * pdev)246 static int ctcu_platform_probe(struct platform_device *pdev)
247 {
248 int ret;
249
250 pm_runtime_get_noresume(&pdev->dev);
251 pm_runtime_set_active(&pdev->dev);
252 pm_runtime_enable(&pdev->dev);
253
254 ret = ctcu_probe(pdev);
255 pm_runtime_put(&pdev->dev);
256 if (ret)
257 pm_runtime_disable(&pdev->dev);
258
259 return ret;
260 }
261
ctcu_platform_remove(struct platform_device * pdev)262 static void ctcu_platform_remove(struct platform_device *pdev)
263 {
264 struct ctcu_drvdata *drvdata = platform_get_drvdata(pdev);
265
266 if (WARN_ON(!drvdata))
267 return;
268
269 ctcu_remove(pdev);
270 pm_runtime_disable(&pdev->dev);
271 }
272
273 #ifdef CONFIG_PM
ctcu_runtime_suspend(struct device * dev)274 static int ctcu_runtime_suspend(struct device *dev)
275 {
276 struct ctcu_drvdata *drvdata = dev_get_drvdata(dev);
277
278 clk_disable_unprepare(drvdata->apb_clk);
279
280 return 0;
281 }
282
ctcu_runtime_resume(struct device * dev)283 static int ctcu_runtime_resume(struct device *dev)
284 {
285 struct ctcu_drvdata *drvdata = dev_get_drvdata(dev);
286
287 return clk_prepare_enable(drvdata->apb_clk);
288 }
289 #endif
290
291 static const struct dev_pm_ops ctcu_dev_pm_ops = {
292 SET_RUNTIME_PM_OPS(ctcu_runtime_suspend, ctcu_runtime_resume, NULL)
293 };
294
295 static const struct of_device_id ctcu_match[] = {
296 {.compatible = "qcom,sa8775p-ctcu", .data = &sa8775p_cfgs},
297 {}
298 };
299
300 static struct platform_driver ctcu_driver = {
301 .probe = ctcu_platform_probe,
302 .remove = ctcu_platform_remove,
303 .driver = {
304 .name = "coresight-ctcu",
305 .of_match_table = ctcu_match,
306 .pm = &ctcu_dev_pm_ops,
307 .suppress_bind_attrs = true,
308 },
309 };
310 module_platform_driver(ctcu_driver);
311
312 MODULE_LICENSE("GPL");
313 MODULE_DESCRIPTION("CoreSight TMC Control Unit driver");
314