1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/amba/bus.h> |
7 | #include <linux/bitfield.h> |
8 | #include <linux/coresight.h> |
9 | #include <linux/device.h> |
10 | #include <linux/err.h> |
11 | #include <linux/fs.h> |
12 | #include <linux/io.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_device.h> |
17 | |
18 | #include "coresight-priv.h" |
19 | #include "coresight-tpda.h" |
20 | #include "coresight-trace-id.h" |
21 | #include "coresight-tpdm.h" |
22 | |
23 | DEFINE_CORESIGHT_DEVLIST(tpda_devs, "tpda" ); |
24 | |
25 | static bool coresight_device_is_tpdm(struct coresight_device *csdev) |
26 | { |
27 | return (csdev->type == CORESIGHT_DEV_TYPE_SOURCE) && |
28 | (csdev->subtype.source_subtype == |
29 | CORESIGHT_DEV_SUBTYPE_SOURCE_TPDM); |
30 | } |
31 | |
32 | static void tpda_clear_element_size(struct coresight_device *csdev) |
33 | { |
34 | struct tpda_drvdata *drvdata = dev_get_drvdata(dev: csdev->dev.parent); |
35 | |
36 | drvdata->dsb_esize = 0; |
37 | drvdata->cmb_esize = 0; |
38 | } |
39 | |
40 | static void tpda_set_element_size(struct tpda_drvdata *drvdata, u32 *val) |
41 | { |
42 | /* Clear all relevant fields */ |
43 | *val &= ~(TPDA_Pn_CR_DSBSIZE | TPDA_Pn_CR_CMBSIZE); |
44 | |
45 | if (drvdata->dsb_esize == 64) |
46 | *val |= TPDA_Pn_CR_DSBSIZE; |
47 | else if (drvdata->dsb_esize == 32) |
48 | *val &= ~TPDA_Pn_CR_DSBSIZE; |
49 | |
50 | if (drvdata->cmb_esize == 64) |
51 | *val |= FIELD_PREP(TPDA_Pn_CR_CMBSIZE, 0x2); |
52 | else if (drvdata->cmb_esize == 32) |
53 | *val |= FIELD_PREP(TPDA_Pn_CR_CMBSIZE, 0x1); |
54 | else if (drvdata->cmb_esize == 8) |
55 | *val &= ~TPDA_Pn_CR_CMBSIZE; |
56 | } |
57 | |
58 | /* |
59 | * Read the element size from the TPDM device. One TPDM must have at least one of the |
60 | * element size property. |
61 | * Returns |
62 | * 0 - The element size property is read |
63 | * Others - Cannot read the property of the element size |
64 | */ |
65 | static int tpdm_read_element_size(struct tpda_drvdata *drvdata, |
66 | struct coresight_device *csdev) |
67 | { |
68 | int rc = -EINVAL; |
69 | struct tpdm_drvdata *tpdm_data = dev_get_drvdata(dev: csdev->dev.parent); |
70 | |
71 | if (tpdm_has_dsb_dataset(drvdata: tpdm_data)) { |
72 | rc = fwnode_property_read_u32(dev_fwnode(csdev->dev.parent), |
73 | propname: "qcom,dsb-element-bits" , val: &drvdata->dsb_esize); |
74 | } |
75 | if (tpdm_has_cmb_dataset(drvdata: tpdm_data)) { |
76 | rc = fwnode_property_read_u32(dev_fwnode(csdev->dev.parent), |
77 | propname: "qcom,cmb-element-bits" , val: &drvdata->cmb_esize); |
78 | } |
79 | |
80 | if (rc) |
81 | dev_warn_once(&csdev->dev, |
82 | "Failed to read TPDM Element size: %d\n" , rc); |
83 | |
84 | return rc; |
85 | } |
86 | |
87 | /* |
88 | * Search and read element data size from the TPDM node in |
89 | * the devicetree. Each input port of TPDA is connected to |
90 | * a TPDM. Different TPDM supports different types of dataset, |
91 | * and some may support more than one type of dataset. |
92 | * Parameter "inport" is used to pass in the input port number |
93 | * of TPDA, and it is set to -1 in the recursize call. |
94 | */ |
95 | static int tpda_get_element_size(struct tpda_drvdata *drvdata, |
96 | struct coresight_device *csdev, |
97 | int inport) |
98 | { |
99 | int rc = 0; |
100 | int i; |
101 | struct coresight_device *in; |
102 | |
103 | for (i = 0; i < csdev->pdata->nr_inconns; i++) { |
104 | in = csdev->pdata->in_conns[i]->src_dev; |
105 | if (!in) |
106 | continue; |
107 | |
108 | /* Ignore the paths that do not match port */ |
109 | if (inport >= 0 && |
110 | csdev->pdata->in_conns[i]->dest_port != inport) |
111 | continue; |
112 | |
113 | if (coresight_device_is_tpdm(csdev: in)) { |
114 | if (drvdata->dsb_esize || drvdata->cmb_esize) |
115 | return -EEXIST; |
116 | rc = tpdm_read_element_size(drvdata, csdev: in); |
117 | if (rc) |
118 | return rc; |
119 | } else { |
120 | /* Recurse down the path */ |
121 | rc = tpda_get_element_size(drvdata, csdev: in, inport: -1); |
122 | if (rc) |
123 | return rc; |
124 | } |
125 | } |
126 | |
127 | |
128 | return rc; |
129 | } |
130 | |
131 | /* Settings pre enabling port control register */ |
132 | static void tpda_enable_pre_port(struct tpda_drvdata *drvdata) |
133 | { |
134 | u32 val; |
135 | |
136 | val = readl_relaxed(drvdata->base + TPDA_CR); |
137 | val &= ~TPDA_CR_ATID; |
138 | val |= FIELD_PREP(TPDA_CR_ATID, drvdata->atid); |
139 | writel_relaxed(val, drvdata->base + TPDA_CR); |
140 | } |
141 | |
142 | static int tpda_enable_port(struct tpda_drvdata *drvdata, int port) |
143 | { |
144 | u32 val; |
145 | int rc; |
146 | |
147 | val = readl_relaxed(drvdata->base + TPDA_Pn_CR(port)); |
148 | tpda_clear_element_size(csdev: drvdata->csdev); |
149 | rc = tpda_get_element_size(drvdata, csdev: drvdata->csdev, inport: port); |
150 | if (!rc && (drvdata->dsb_esize || drvdata->cmb_esize)) { |
151 | tpda_set_element_size(drvdata, val: &val); |
152 | /* Enable the port */ |
153 | val |= TPDA_Pn_CR_ENA; |
154 | writel_relaxed(val, drvdata->base + TPDA_Pn_CR(port)); |
155 | } else if (rc == -EEXIST) |
156 | dev_warn_once(&drvdata->csdev->dev, |
157 | "Detected multiple TPDMs on port %d" , port); |
158 | else |
159 | dev_warn_once(&drvdata->csdev->dev, |
160 | "Didn't find TPDM element size" ); |
161 | |
162 | return rc; |
163 | } |
164 | |
165 | static int __tpda_enable(struct tpda_drvdata *drvdata, int port) |
166 | { |
167 | int ret; |
168 | |
169 | CS_UNLOCK(addr: drvdata->base); |
170 | |
171 | /* |
172 | * Only do pre-port enable for first port that calls enable when the |
173 | * device's main refcount is still 0 |
174 | */ |
175 | lockdep_assert_held(&drvdata->spinlock); |
176 | if (!drvdata->csdev->refcnt) |
177 | tpda_enable_pre_port(drvdata); |
178 | |
179 | ret = tpda_enable_port(drvdata, port); |
180 | CS_LOCK(addr: drvdata->base); |
181 | |
182 | return ret; |
183 | } |
184 | |
185 | static int tpda_enable(struct coresight_device *csdev, |
186 | struct coresight_connection *in, |
187 | struct coresight_connection *out) |
188 | { |
189 | struct tpda_drvdata *drvdata = dev_get_drvdata(dev: csdev->dev.parent); |
190 | int ret = 0; |
191 | |
192 | spin_lock(lock: &drvdata->spinlock); |
193 | if (atomic_read(v: &in->dest_refcnt) == 0) { |
194 | ret = __tpda_enable(drvdata, port: in->dest_port); |
195 | if (!ret) { |
196 | atomic_inc(v: &in->dest_refcnt); |
197 | csdev->refcnt++; |
198 | dev_dbg(drvdata->dev, "TPDA inport %d enabled.\n" , in->dest_port); |
199 | } |
200 | } |
201 | |
202 | spin_unlock(lock: &drvdata->spinlock); |
203 | return ret; |
204 | } |
205 | |
206 | static void __tpda_disable(struct tpda_drvdata *drvdata, int port) |
207 | { |
208 | u32 val; |
209 | |
210 | CS_UNLOCK(addr: drvdata->base); |
211 | |
212 | val = readl_relaxed(drvdata->base + TPDA_Pn_CR(port)); |
213 | val &= ~TPDA_Pn_CR_ENA; |
214 | writel_relaxed(val, drvdata->base + TPDA_Pn_CR(port)); |
215 | |
216 | CS_LOCK(addr: drvdata->base); |
217 | } |
218 | |
219 | static void tpda_disable(struct coresight_device *csdev, |
220 | struct coresight_connection *in, |
221 | struct coresight_connection *out) |
222 | { |
223 | struct tpda_drvdata *drvdata = dev_get_drvdata(dev: csdev->dev.parent); |
224 | |
225 | spin_lock(lock: &drvdata->spinlock); |
226 | if (atomic_dec_return(v: &in->dest_refcnt) == 0) { |
227 | __tpda_disable(drvdata, port: in->dest_port); |
228 | csdev->refcnt--; |
229 | } |
230 | spin_unlock(lock: &drvdata->spinlock); |
231 | |
232 | dev_dbg(drvdata->dev, "TPDA inport %d disabled\n" , in->dest_port); |
233 | } |
234 | |
235 | static const struct coresight_ops_link tpda_link_ops = { |
236 | .enable = tpda_enable, |
237 | .disable = tpda_disable, |
238 | }; |
239 | |
240 | static const struct coresight_ops tpda_cs_ops = { |
241 | .link_ops = &tpda_link_ops, |
242 | }; |
243 | |
244 | static int tpda_init_default_data(struct tpda_drvdata *drvdata) |
245 | { |
246 | int atid; |
247 | /* |
248 | * TPDA must has a unique atid. This atid can uniquely |
249 | * identify the TPDM trace source connected to the TPDA. |
250 | * The TPDMs which are connected to same TPDA share the |
251 | * same trace-id. When TPDA does packetization, different |
252 | * port will have unique channel number for decoding. |
253 | */ |
254 | atid = coresight_trace_id_get_system_id(); |
255 | if (atid < 0) |
256 | return atid; |
257 | |
258 | drvdata->atid = atid; |
259 | return 0; |
260 | } |
261 | |
262 | static int tpda_probe(struct amba_device *adev, const struct amba_id *id) |
263 | { |
264 | int ret; |
265 | struct device *dev = &adev->dev; |
266 | struct coresight_platform_data *pdata; |
267 | struct tpda_drvdata *drvdata; |
268 | struct coresight_desc desc = { 0 }; |
269 | void __iomem *base; |
270 | |
271 | pdata = coresight_get_platform_data(dev); |
272 | if (IS_ERR(ptr: pdata)) |
273 | return PTR_ERR(ptr: pdata); |
274 | adev->dev.platform_data = pdata; |
275 | |
276 | drvdata = devm_kzalloc(dev, size: sizeof(*drvdata), GFP_KERNEL); |
277 | if (!drvdata) |
278 | return -ENOMEM; |
279 | |
280 | drvdata->dev = &adev->dev; |
281 | dev_set_drvdata(dev, data: drvdata); |
282 | |
283 | base = devm_ioremap_resource(dev, res: &adev->res); |
284 | if (IS_ERR(ptr: base)) |
285 | return PTR_ERR(ptr: base); |
286 | drvdata->base = base; |
287 | |
288 | spin_lock_init(&drvdata->spinlock); |
289 | |
290 | ret = tpda_init_default_data(drvdata); |
291 | if (ret) |
292 | return ret; |
293 | |
294 | desc.name = coresight_alloc_device_name(devs: &tpda_devs, dev); |
295 | if (!desc.name) |
296 | return -ENOMEM; |
297 | desc.type = CORESIGHT_DEV_TYPE_LINK; |
298 | desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG; |
299 | desc.ops = &tpda_cs_ops; |
300 | desc.pdata = adev->dev.platform_data; |
301 | desc.dev = &adev->dev; |
302 | desc.access = CSDEV_ACCESS_IOMEM(base); |
303 | drvdata->csdev = coresight_register(desc: &desc); |
304 | if (IS_ERR(ptr: drvdata->csdev)) |
305 | return PTR_ERR(ptr: drvdata->csdev); |
306 | |
307 | pm_runtime_put(dev: &adev->dev); |
308 | |
309 | dev_dbg(drvdata->dev, "TPDA initialized\n" ); |
310 | return 0; |
311 | } |
312 | |
313 | static void tpda_remove(struct amba_device *adev) |
314 | { |
315 | struct tpda_drvdata *drvdata = dev_get_drvdata(dev: &adev->dev); |
316 | |
317 | coresight_trace_id_put_system_id(id: drvdata->atid); |
318 | coresight_unregister(csdev: drvdata->csdev); |
319 | } |
320 | |
321 | /* |
322 | * Different TPDA has different periph id. |
323 | * The difference is 0-7 bits' value. So ignore 0-7 bits. |
324 | */ |
325 | static struct amba_id tpda_ids[] = { |
326 | { |
327 | .id = 0x000f0f00, |
328 | .mask = 0x000fff00, |
329 | }, |
330 | { 0, 0, NULL }, |
331 | }; |
332 | |
333 | static struct amba_driver tpda_driver = { |
334 | .drv = { |
335 | .name = "coresight-tpda" , |
336 | .owner = THIS_MODULE, |
337 | .suppress_bind_attrs = true, |
338 | }, |
339 | .probe = tpda_probe, |
340 | .remove = tpda_remove, |
341 | .id_table = tpda_ids, |
342 | }; |
343 | |
344 | module_amba_driver(tpda_driver); |
345 | |
346 | MODULE_LICENSE("GPL" ); |
347 | MODULE_DESCRIPTION("Trace, Profiling & Diagnostic Aggregator driver" ); |
348 | |