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