1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2019 Linaro Limited, All rights reserved. |
4 | * Author: Mike Leach <mike.leach@linaro.org> |
5 | */ |
6 | |
7 | #include <linux/atomic.h> |
8 | #include <linux/coresight.h> |
9 | #include <linux/device.h> |
10 | #include <linux/io.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/spinlock.h> |
13 | #include <linux/sysfs.h> |
14 | |
15 | #include "coresight-cti.h" |
16 | |
17 | /* |
18 | * Declare the number of static declared attribute groups |
19 | * Value includes groups + NULL value at end of table. |
20 | */ |
21 | #define CORESIGHT_CTI_STATIC_GROUPS_MAX 5 |
22 | |
23 | /* |
24 | * List of trigger signal type names. Match the constants declared in |
25 | * include\dt-bindings\arm\coresight-cti-dt.h |
26 | */ |
27 | static const char * const sig_type_names[] = { |
28 | "genio" , /* GEN_IO */ |
29 | "intreq" , /* GEN_INTREQ */ |
30 | "intack" , /* GEN_INTACK */ |
31 | "haltreq" , /* GEN_HALTREQ */ |
32 | "restartreq" , /* GEN_RESTARTREQ */ |
33 | "pe_edbgreq" , /* PE_EDBGREQ */ |
34 | "pe_dbgrestart" ,/* PE_DBGRESTART */ |
35 | "pe_ctiirq" , /* PE_CTIIRQ */ |
36 | "pe_pmuirq" , /* PE_PMUIRQ */ |
37 | "pe_dbgtrigger" ,/* PE_DBGTRIGGER */ |
38 | "etm_extout" , /* ETM_EXTOUT */ |
39 | "etm_extin" , /* ETM_EXTIN */ |
40 | "snk_full" , /* SNK_FULL */ |
41 | "snk_acqcomp" , /* SNK_ACQCOMP */ |
42 | "snk_flushcomp" ,/* SNK_FLUSHCOMP */ |
43 | "snk_flushin" , /* SNK_FLUSHIN */ |
44 | "snk_trigin" , /* SNK_TRIGIN */ |
45 | "stm_asyncout" , /* STM_ASYNCOUT */ |
46 | "stm_tout_spte" ,/* STM_TOUT_SPTE */ |
47 | "stm_tout_sw" , /* STM_TOUT_SW */ |
48 | "stm_tout_hete" ,/* STM_TOUT_HETE */ |
49 | "stm_hwevent" , /* STM_HWEVENT */ |
50 | "ela_tstart" , /* ELA_TSTART */ |
51 | "ela_tstop" , /* ELA_TSTOP */ |
52 | "ela_dbgreq" , /* ELA_DBGREQ */ |
53 | }; |
54 | |
55 | /* Show function pointer used in the connections dynamic declared attributes*/ |
56 | typedef ssize_t (*p_show_fn)(struct device *dev, struct device_attribute *attr, |
57 | char *buf); |
58 | |
59 | /* Connection attribute types */ |
60 | enum cti_conn_attr_type { |
61 | CTI_CON_ATTR_NAME, |
62 | CTI_CON_ATTR_TRIGIN_SIG, |
63 | CTI_CON_ATTR_TRIGOUT_SIG, |
64 | CTI_CON_ATTR_TRIGIN_TYPES, |
65 | CTI_CON_ATTR_TRIGOUT_TYPES, |
66 | CTI_CON_ATTR_MAX, |
67 | }; |
68 | |
69 | /* Names for the connection attributes */ |
70 | static const char * const con_attr_names[CTI_CON_ATTR_MAX] = { |
71 | "name" , |
72 | "in_signals" , |
73 | "out_signals" , |
74 | "in_types" , |
75 | "out_types" , |
76 | }; |
77 | |
78 | /* basic attributes */ |
79 | static ssize_t enable_show(struct device *dev, |
80 | struct device_attribute *attr, |
81 | char *buf) |
82 | { |
83 | int enable_req; |
84 | bool enabled, powered; |
85 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
86 | |
87 | spin_lock(lock: &drvdata->spinlock); |
88 | enable_req = drvdata->config.enable_req_count; |
89 | powered = drvdata->config.hw_powered; |
90 | enabled = drvdata->config.hw_enabled; |
91 | spin_unlock(lock: &drvdata->spinlock); |
92 | |
93 | if (powered) |
94 | return sprintf(buf, fmt: "%d\n" , enabled); |
95 | else |
96 | return sprintf(buf, fmt: "%d\n" , !!enable_req); |
97 | } |
98 | |
99 | static ssize_t enable_store(struct device *dev, |
100 | struct device_attribute *attr, |
101 | const char *buf, size_t size) |
102 | { |
103 | int ret = 0; |
104 | unsigned long val; |
105 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
106 | |
107 | ret = kstrtoul(s: buf, base: 0, res: &val); |
108 | if (ret) |
109 | return ret; |
110 | |
111 | if (val) { |
112 | ret = pm_runtime_resume_and_get(dev: dev->parent); |
113 | if (ret) |
114 | return ret; |
115 | ret = cti_enable(csdev: drvdata->csdev, mode: CS_MODE_SYSFS, NULL); |
116 | if (ret) |
117 | pm_runtime_put(dev: dev->parent); |
118 | } else { |
119 | ret = cti_disable(csdev: drvdata->csdev, NULL); |
120 | if (!ret) |
121 | pm_runtime_put(dev: dev->parent); |
122 | } |
123 | |
124 | if (ret) |
125 | return ret; |
126 | return size; |
127 | } |
128 | static DEVICE_ATTR_RW(enable); |
129 | |
130 | static ssize_t powered_show(struct device *dev, |
131 | struct device_attribute *attr, |
132 | char *buf) |
133 | { |
134 | bool powered; |
135 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
136 | |
137 | spin_lock(lock: &drvdata->spinlock); |
138 | powered = drvdata->config.hw_powered; |
139 | spin_unlock(lock: &drvdata->spinlock); |
140 | |
141 | return sprintf(buf, fmt: "%d\n" , powered); |
142 | } |
143 | static DEVICE_ATTR_RO(powered); |
144 | |
145 | static ssize_t ctmid_show(struct device *dev, |
146 | struct device_attribute *attr, char *buf) |
147 | { |
148 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
149 | |
150 | return sprintf(buf, fmt: "%d\n" , drvdata->ctidev.ctm_id); |
151 | } |
152 | static DEVICE_ATTR_RO(ctmid); |
153 | |
154 | static ssize_t nr_trigger_cons_show(struct device *dev, |
155 | struct device_attribute *attr, |
156 | char *buf) |
157 | { |
158 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
159 | |
160 | return sprintf(buf, fmt: "%d\n" , drvdata->ctidev.nr_trig_con); |
161 | } |
162 | static DEVICE_ATTR_RO(nr_trigger_cons); |
163 | |
164 | /* attribute and group sysfs tables. */ |
165 | static struct attribute *coresight_cti_attrs[] = { |
166 | &dev_attr_enable.attr, |
167 | &dev_attr_powered.attr, |
168 | &dev_attr_ctmid.attr, |
169 | &dev_attr_nr_trigger_cons.attr, |
170 | NULL, |
171 | }; |
172 | |
173 | /* register based attributes */ |
174 | |
175 | /* Read registers with power check only (no enable check). */ |
176 | static ssize_t coresight_cti_reg_show(struct device *dev, |
177 | struct device_attribute *attr, char *buf) |
178 | { |
179 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
180 | struct cs_off_attribute *cti_attr = container_of(attr, struct cs_off_attribute, attr); |
181 | u32 val = 0; |
182 | |
183 | pm_runtime_get_sync(dev: dev->parent); |
184 | spin_lock(lock: &drvdata->spinlock); |
185 | if (drvdata->config.hw_powered) |
186 | val = readl_relaxed(drvdata->base + cti_attr->off); |
187 | spin_unlock(lock: &drvdata->spinlock); |
188 | pm_runtime_put_sync(dev: dev->parent); |
189 | return sysfs_emit(buf, fmt: "0x%x\n" , val); |
190 | } |
191 | |
192 | /* Write registers with power check only (no enable check). */ |
193 | static __maybe_unused ssize_t coresight_cti_reg_store(struct device *dev, |
194 | struct device_attribute *attr, |
195 | const char *buf, size_t size) |
196 | { |
197 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
198 | struct cs_off_attribute *cti_attr = container_of(attr, struct cs_off_attribute, attr); |
199 | unsigned long val = 0; |
200 | |
201 | if (kstrtoul(s: buf, base: 0, res: &val)) |
202 | return -EINVAL; |
203 | |
204 | pm_runtime_get_sync(dev: dev->parent); |
205 | spin_lock(lock: &drvdata->spinlock); |
206 | if (drvdata->config.hw_powered) |
207 | cti_write_single_reg(drvdata, offset: cti_attr->off, value: val); |
208 | spin_unlock(lock: &drvdata->spinlock); |
209 | pm_runtime_put_sync(dev: dev->parent); |
210 | return size; |
211 | } |
212 | |
213 | #define coresight_cti_reg(name, offset) \ |
214 | (&((struct cs_off_attribute[]) { \ |
215 | { \ |
216 | __ATTR(name, 0444, coresight_cti_reg_show, NULL), \ |
217 | offset \ |
218 | } \ |
219 | })[0].attr.attr) |
220 | |
221 | #define coresight_cti_reg_rw(name, offset) \ |
222 | (&((struct cs_off_attribute[]) { \ |
223 | { \ |
224 | __ATTR(name, 0644, coresight_cti_reg_show, \ |
225 | coresight_cti_reg_store), \ |
226 | offset \ |
227 | } \ |
228 | })[0].attr.attr) |
229 | |
230 | #define coresight_cti_reg_wo(name, offset) \ |
231 | (&((struct cs_off_attribute[]) { \ |
232 | { \ |
233 | __ATTR(name, 0200, NULL, coresight_cti_reg_store), \ |
234 | offset \ |
235 | } \ |
236 | })[0].attr.attr) |
237 | |
238 | /* coresight management registers */ |
239 | static struct attribute *coresight_cti_mgmt_attrs[] = { |
240 | coresight_cti_reg(devaff0, CTIDEVAFF0), |
241 | coresight_cti_reg(devaff1, CTIDEVAFF1), |
242 | coresight_cti_reg(authstatus, CORESIGHT_AUTHSTATUS), |
243 | coresight_cti_reg(devarch, CORESIGHT_DEVARCH), |
244 | coresight_cti_reg(devid, CORESIGHT_DEVID), |
245 | coresight_cti_reg(devtype, CORESIGHT_DEVTYPE), |
246 | coresight_cti_reg(pidr0, CORESIGHT_PERIPHIDR0), |
247 | coresight_cti_reg(pidr1, CORESIGHT_PERIPHIDR1), |
248 | coresight_cti_reg(pidr2, CORESIGHT_PERIPHIDR2), |
249 | coresight_cti_reg(pidr3, CORESIGHT_PERIPHIDR3), |
250 | coresight_cti_reg(pidr4, CORESIGHT_PERIPHIDR4), |
251 | NULL, |
252 | }; |
253 | |
254 | /* CTI low level programming registers */ |
255 | |
256 | /* |
257 | * Show a simple 32 bit value if enabled and powered. |
258 | * If inaccessible & pcached_val not NULL then show cached value. |
259 | */ |
260 | static ssize_t cti_reg32_show(struct device *dev, char *buf, |
261 | u32 *pcached_val, int reg_offset) |
262 | { |
263 | u32 val = 0; |
264 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
265 | struct cti_config *config = &drvdata->config; |
266 | |
267 | spin_lock(lock: &drvdata->spinlock); |
268 | if ((reg_offset >= 0) && cti_active(cfg: config)) { |
269 | CS_UNLOCK(addr: drvdata->base); |
270 | val = readl_relaxed(drvdata->base + reg_offset); |
271 | if (pcached_val) |
272 | *pcached_val = val; |
273 | CS_LOCK(addr: drvdata->base); |
274 | } else if (pcached_val) { |
275 | val = *pcached_val; |
276 | } |
277 | spin_unlock(lock: &drvdata->spinlock); |
278 | return sprintf(buf, fmt: "%#x\n" , val); |
279 | } |
280 | |
281 | /* |
282 | * Store a simple 32 bit value. |
283 | * If pcached_val not NULL, then copy to here too, |
284 | * if reg_offset >= 0 then write through if enabled. |
285 | */ |
286 | static ssize_t cti_reg32_store(struct device *dev, const char *buf, |
287 | size_t size, u32 *pcached_val, int reg_offset) |
288 | { |
289 | unsigned long val; |
290 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
291 | struct cti_config *config = &drvdata->config; |
292 | |
293 | if (kstrtoul(s: buf, base: 0, res: &val)) |
294 | return -EINVAL; |
295 | |
296 | spin_lock(lock: &drvdata->spinlock); |
297 | /* local store */ |
298 | if (pcached_val) |
299 | *pcached_val = (u32)val; |
300 | |
301 | /* write through if offset and enabled */ |
302 | if ((reg_offset >= 0) && cti_active(cfg: config)) |
303 | cti_write_single_reg(drvdata, offset: reg_offset, value: val); |
304 | spin_unlock(lock: &drvdata->spinlock); |
305 | return size; |
306 | } |
307 | |
308 | /* Standard macro for simple rw cti config registers */ |
309 | #define cti_config_reg32_rw(name, cfgname, offset) \ |
310 | static ssize_t name##_show(struct device *dev, \ |
311 | struct device_attribute *attr, \ |
312 | char *buf) \ |
313 | { \ |
314 | struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \ |
315 | return cti_reg32_show(dev, buf, \ |
316 | &drvdata->config.cfgname, offset); \ |
317 | } \ |
318 | \ |
319 | static ssize_t name##_store(struct device *dev, \ |
320 | struct device_attribute *attr, \ |
321 | const char *buf, size_t size) \ |
322 | { \ |
323 | struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \ |
324 | return cti_reg32_store(dev, buf, size, \ |
325 | &drvdata->config.cfgname, offset); \ |
326 | } \ |
327 | static DEVICE_ATTR_RW(name) |
328 | |
329 | static ssize_t inout_sel_show(struct device *dev, |
330 | struct device_attribute *attr, |
331 | char *buf) |
332 | { |
333 | u32 val; |
334 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
335 | |
336 | val = (u32)drvdata->config.ctiinout_sel; |
337 | return sprintf(buf, fmt: "%d\n" , val); |
338 | } |
339 | |
340 | static ssize_t inout_sel_store(struct device *dev, |
341 | struct device_attribute *attr, |
342 | const char *buf, size_t size) |
343 | { |
344 | unsigned long val; |
345 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
346 | |
347 | if (kstrtoul(s: buf, base: 0, res: &val)) |
348 | return -EINVAL; |
349 | if (val > (CTIINOUTEN_MAX - 1)) |
350 | return -EINVAL; |
351 | |
352 | spin_lock(lock: &drvdata->spinlock); |
353 | drvdata->config.ctiinout_sel = val; |
354 | spin_unlock(lock: &drvdata->spinlock); |
355 | return size; |
356 | } |
357 | static DEVICE_ATTR_RW(inout_sel); |
358 | |
359 | static ssize_t inen_show(struct device *dev, |
360 | struct device_attribute *attr, |
361 | char *buf) |
362 | { |
363 | unsigned long val; |
364 | int index; |
365 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
366 | |
367 | spin_lock(lock: &drvdata->spinlock); |
368 | index = drvdata->config.ctiinout_sel; |
369 | val = drvdata->config.ctiinen[index]; |
370 | spin_unlock(lock: &drvdata->spinlock); |
371 | return sprintf(buf, fmt: "%#lx\n" , val); |
372 | } |
373 | |
374 | static ssize_t inen_store(struct device *dev, |
375 | struct device_attribute *attr, |
376 | const char *buf, size_t size) |
377 | { |
378 | unsigned long val; |
379 | int index; |
380 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
381 | struct cti_config *config = &drvdata->config; |
382 | |
383 | if (kstrtoul(s: buf, base: 0, res: &val)) |
384 | return -EINVAL; |
385 | |
386 | spin_lock(lock: &drvdata->spinlock); |
387 | index = config->ctiinout_sel; |
388 | config->ctiinen[index] = val; |
389 | |
390 | /* write through if enabled */ |
391 | if (cti_active(cfg: config)) |
392 | cti_write_single_reg(drvdata, CTIINEN(index), value: val); |
393 | spin_unlock(lock: &drvdata->spinlock); |
394 | return size; |
395 | } |
396 | static DEVICE_ATTR_RW(inen); |
397 | |
398 | static ssize_t outen_show(struct device *dev, |
399 | struct device_attribute *attr, |
400 | char *buf) |
401 | { |
402 | unsigned long val; |
403 | int index; |
404 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
405 | |
406 | spin_lock(lock: &drvdata->spinlock); |
407 | index = drvdata->config.ctiinout_sel; |
408 | val = drvdata->config.ctiouten[index]; |
409 | spin_unlock(lock: &drvdata->spinlock); |
410 | return sprintf(buf, fmt: "%#lx\n" , val); |
411 | } |
412 | |
413 | static ssize_t outen_store(struct device *dev, |
414 | struct device_attribute *attr, |
415 | const char *buf, size_t size) |
416 | { |
417 | unsigned long val; |
418 | int index; |
419 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
420 | struct cti_config *config = &drvdata->config; |
421 | |
422 | if (kstrtoul(s: buf, base: 0, res: &val)) |
423 | return -EINVAL; |
424 | |
425 | spin_lock(lock: &drvdata->spinlock); |
426 | index = config->ctiinout_sel; |
427 | config->ctiouten[index] = val; |
428 | |
429 | /* write through if enabled */ |
430 | if (cti_active(cfg: config)) |
431 | cti_write_single_reg(drvdata, CTIOUTEN(index), value: val); |
432 | spin_unlock(lock: &drvdata->spinlock); |
433 | return size; |
434 | } |
435 | static DEVICE_ATTR_RW(outen); |
436 | |
437 | static ssize_t intack_store(struct device *dev, |
438 | struct device_attribute *attr, |
439 | const char *buf, size_t size) |
440 | { |
441 | unsigned long val; |
442 | |
443 | if (kstrtoul(s: buf, base: 0, res: &val)) |
444 | return -EINVAL; |
445 | |
446 | cti_write_intack(dev, ackval: val); |
447 | return size; |
448 | } |
449 | static DEVICE_ATTR_WO(intack); |
450 | |
451 | cti_config_reg32_rw(gate, ctigate, CTIGATE); |
452 | cti_config_reg32_rw(asicctl, asicctl, ASICCTL); |
453 | cti_config_reg32_rw(appset, ctiappset, CTIAPPSET); |
454 | |
455 | static ssize_t appclear_store(struct device *dev, |
456 | struct device_attribute *attr, |
457 | const char *buf, size_t size) |
458 | { |
459 | unsigned long val; |
460 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
461 | struct cti_config *config = &drvdata->config; |
462 | |
463 | if (kstrtoul(s: buf, base: 0, res: &val)) |
464 | return -EINVAL; |
465 | |
466 | spin_lock(lock: &drvdata->spinlock); |
467 | |
468 | /* a 1'b1 in appclr clears down the same bit in appset*/ |
469 | config->ctiappset &= ~val; |
470 | |
471 | /* write through if enabled */ |
472 | if (cti_active(cfg: config)) |
473 | cti_write_single_reg(drvdata, CTIAPPCLEAR, value: val); |
474 | spin_unlock(lock: &drvdata->spinlock); |
475 | return size; |
476 | } |
477 | static DEVICE_ATTR_WO(appclear); |
478 | |
479 | static ssize_t apppulse_store(struct device *dev, |
480 | struct device_attribute *attr, |
481 | const char *buf, size_t size) |
482 | { |
483 | unsigned long val; |
484 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
485 | struct cti_config *config = &drvdata->config; |
486 | |
487 | if (kstrtoul(s: buf, base: 0, res: &val)) |
488 | return -EINVAL; |
489 | |
490 | spin_lock(lock: &drvdata->spinlock); |
491 | |
492 | /* write through if enabled */ |
493 | if (cti_active(cfg: config)) |
494 | cti_write_single_reg(drvdata, CTIAPPPULSE, value: val); |
495 | spin_unlock(lock: &drvdata->spinlock); |
496 | return size; |
497 | } |
498 | static DEVICE_ATTR_WO(apppulse); |
499 | |
500 | /* |
501 | * Define CONFIG_CORESIGHT_CTI_INTEGRATION_REGS to enable the access to the |
502 | * integration control registers. Normally only used to investigate connection |
503 | * data. |
504 | */ |
505 | static struct attribute *coresight_cti_regs_attrs[] = { |
506 | &dev_attr_inout_sel.attr, |
507 | &dev_attr_inen.attr, |
508 | &dev_attr_outen.attr, |
509 | &dev_attr_gate.attr, |
510 | &dev_attr_asicctl.attr, |
511 | &dev_attr_intack.attr, |
512 | &dev_attr_appset.attr, |
513 | &dev_attr_appclear.attr, |
514 | &dev_attr_apppulse.attr, |
515 | coresight_cti_reg(triginstatus, CTITRIGINSTATUS), |
516 | coresight_cti_reg(trigoutstatus, CTITRIGOUTSTATUS), |
517 | coresight_cti_reg(chinstatus, CTICHINSTATUS), |
518 | coresight_cti_reg(choutstatus, CTICHOUTSTATUS), |
519 | #ifdef CONFIG_CORESIGHT_CTI_INTEGRATION_REGS |
520 | coresight_cti_reg_rw(itctrl, CORESIGHT_ITCTRL), |
521 | coresight_cti_reg(ittrigin, ITTRIGIN), |
522 | coresight_cti_reg(itchin, ITCHIN), |
523 | coresight_cti_reg_rw(ittrigout, ITTRIGOUT), |
524 | coresight_cti_reg_rw(itchout, ITCHOUT), |
525 | coresight_cti_reg(itchoutack, ITCHOUTACK), |
526 | coresight_cti_reg(ittrigoutack, ITTRIGOUTACK), |
527 | coresight_cti_reg_wo(ittriginack, ITTRIGINACK), |
528 | coresight_cti_reg_wo(itchinack, ITCHINACK), |
529 | #endif |
530 | NULL, |
531 | }; |
532 | |
533 | /* CTI channel x-trigger programming */ |
534 | static int |
535 | cti_trig_op_parse(struct device *dev, enum cti_chan_op op, |
536 | enum cti_trig_dir dir, const char *buf, size_t size) |
537 | { |
538 | u32 chan_idx; |
539 | u32 trig_idx; |
540 | int items, err = -EINVAL; |
541 | |
542 | /* extract chan idx and trigger idx */ |
543 | items = sscanf(buf, "%d %d" , &chan_idx, &trig_idx); |
544 | if (items == 2) { |
545 | err = cti_channel_trig_op(dev, op, direction: dir, channel_idx: chan_idx, trigger_idx: trig_idx); |
546 | if (!err) |
547 | err = size; |
548 | } |
549 | return err; |
550 | } |
551 | |
552 | static ssize_t trigin_attach_store(struct device *dev, |
553 | struct device_attribute *attr, |
554 | const char *buf, size_t size) |
555 | { |
556 | return cti_trig_op_parse(dev, op: CTI_CHAN_ATTACH, dir: CTI_TRIG_IN, |
557 | buf, size); |
558 | } |
559 | static DEVICE_ATTR_WO(trigin_attach); |
560 | |
561 | static ssize_t trigin_detach_store(struct device *dev, |
562 | struct device_attribute *attr, |
563 | const char *buf, size_t size) |
564 | { |
565 | return cti_trig_op_parse(dev, op: CTI_CHAN_DETACH, dir: CTI_TRIG_IN, |
566 | buf, size); |
567 | } |
568 | static DEVICE_ATTR_WO(trigin_detach); |
569 | |
570 | static ssize_t trigout_attach_store(struct device *dev, |
571 | struct device_attribute *attr, |
572 | const char *buf, size_t size) |
573 | { |
574 | return cti_trig_op_parse(dev, op: CTI_CHAN_ATTACH, dir: CTI_TRIG_OUT, |
575 | buf, size); |
576 | } |
577 | static DEVICE_ATTR_WO(trigout_attach); |
578 | |
579 | static ssize_t trigout_detach_store(struct device *dev, |
580 | struct device_attribute *attr, |
581 | const char *buf, size_t size) |
582 | { |
583 | return cti_trig_op_parse(dev, op: CTI_CHAN_DETACH, dir: CTI_TRIG_OUT, |
584 | buf, size); |
585 | } |
586 | static DEVICE_ATTR_WO(trigout_detach); |
587 | |
588 | |
589 | static ssize_t chan_gate_enable_store(struct device *dev, |
590 | struct device_attribute *attr, |
591 | const char *buf, size_t size) |
592 | { |
593 | int err = 0, channel = 0; |
594 | |
595 | if (kstrtoint(s: buf, base: 0, res: &channel)) |
596 | return -EINVAL; |
597 | |
598 | err = cti_channel_gate_op(dev, op: CTI_GATE_CHAN_ENABLE, channel_idx: channel); |
599 | return err ? err : size; |
600 | } |
601 | |
602 | static ssize_t chan_gate_enable_show(struct device *dev, |
603 | struct device_attribute *attr, |
604 | char *buf) |
605 | { |
606 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
607 | struct cti_config *cfg = &drvdata->config; |
608 | unsigned long ctigate_bitmask = cfg->ctigate; |
609 | int size = 0; |
610 | |
611 | if (cfg->ctigate == 0) |
612 | size = sprintf(buf, fmt: "\n" ); |
613 | else |
614 | size = bitmap_print_to_pagebuf(list: true, buf, maskp: &ctigate_bitmask, |
615 | nmaskbits: cfg->nr_ctm_channels); |
616 | return size; |
617 | } |
618 | static DEVICE_ATTR_RW(chan_gate_enable); |
619 | |
620 | static ssize_t chan_gate_disable_store(struct device *dev, |
621 | struct device_attribute *attr, |
622 | const char *buf, size_t size) |
623 | { |
624 | int err = 0, channel = 0; |
625 | |
626 | if (kstrtoint(s: buf, base: 0, res: &channel)) |
627 | return -EINVAL; |
628 | |
629 | err = cti_channel_gate_op(dev, op: CTI_GATE_CHAN_DISABLE, channel_idx: channel); |
630 | return err ? err : size; |
631 | } |
632 | static DEVICE_ATTR_WO(chan_gate_disable); |
633 | |
634 | static int |
635 | chan_op_parse(struct device *dev, enum cti_chan_set_op op, const char *buf) |
636 | { |
637 | int err = 0, channel = 0; |
638 | |
639 | if (kstrtoint(s: buf, base: 0, res: &channel)) |
640 | return -EINVAL; |
641 | |
642 | err = cti_channel_setop(dev, op, channel_idx: channel); |
643 | return err; |
644 | |
645 | } |
646 | |
647 | static ssize_t chan_set_store(struct device *dev, |
648 | struct device_attribute *attr, |
649 | const char *buf, size_t size) |
650 | { |
651 | int err = chan_op_parse(dev, op: CTI_CHAN_SET, buf); |
652 | |
653 | return err ? err : size; |
654 | } |
655 | static DEVICE_ATTR_WO(chan_set); |
656 | |
657 | static ssize_t chan_clear_store(struct device *dev, |
658 | struct device_attribute *attr, |
659 | const char *buf, size_t size) |
660 | { |
661 | int err = chan_op_parse(dev, op: CTI_CHAN_CLR, buf); |
662 | |
663 | return err ? err : size; |
664 | } |
665 | static DEVICE_ATTR_WO(chan_clear); |
666 | |
667 | static ssize_t chan_pulse_store(struct device *dev, |
668 | struct device_attribute *attr, |
669 | const char *buf, size_t size) |
670 | { |
671 | int err = chan_op_parse(dev, op: CTI_CHAN_PULSE, buf); |
672 | |
673 | return err ? err : size; |
674 | } |
675 | static DEVICE_ATTR_WO(chan_pulse); |
676 | |
677 | static ssize_t trig_filter_enable_show(struct device *dev, |
678 | struct device_attribute *attr, |
679 | char *buf) |
680 | { |
681 | u32 val; |
682 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
683 | |
684 | spin_lock(lock: &drvdata->spinlock); |
685 | val = drvdata->config.trig_filter_enable; |
686 | spin_unlock(lock: &drvdata->spinlock); |
687 | return sprintf(buf, fmt: "%d\n" , val); |
688 | } |
689 | |
690 | static ssize_t trig_filter_enable_store(struct device *dev, |
691 | struct device_attribute *attr, |
692 | const char *buf, size_t size) |
693 | { |
694 | unsigned long val; |
695 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
696 | |
697 | if (kstrtoul(s: buf, base: 0, res: &val)) |
698 | return -EINVAL; |
699 | |
700 | spin_lock(lock: &drvdata->spinlock); |
701 | drvdata->config.trig_filter_enable = !!val; |
702 | spin_unlock(lock: &drvdata->spinlock); |
703 | return size; |
704 | } |
705 | static DEVICE_ATTR_RW(trig_filter_enable); |
706 | |
707 | static ssize_t trigout_filtered_show(struct device *dev, |
708 | struct device_attribute *attr, |
709 | char *buf) |
710 | { |
711 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
712 | struct cti_config *cfg = &drvdata->config; |
713 | int size = 0, nr_trig_max = cfg->nr_trig_max; |
714 | unsigned long mask = cfg->trig_out_filter; |
715 | |
716 | if (mask) |
717 | size = bitmap_print_to_pagebuf(list: true, buf, maskp: &mask, nmaskbits: nr_trig_max); |
718 | return size; |
719 | } |
720 | static DEVICE_ATTR_RO(trigout_filtered); |
721 | |
722 | /* clear all xtrigger / channel programming */ |
723 | static ssize_t chan_xtrigs_reset_store(struct device *dev, |
724 | struct device_attribute *attr, |
725 | const char *buf, size_t size) |
726 | { |
727 | int i; |
728 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
729 | struct cti_config *config = &drvdata->config; |
730 | |
731 | spin_lock(lock: &drvdata->spinlock); |
732 | |
733 | /* clear the CTI trigger / channel programming registers */ |
734 | for (i = 0; i < config->nr_trig_max; i++) { |
735 | config->ctiinen[i] = 0; |
736 | config->ctiouten[i] = 0; |
737 | } |
738 | |
739 | /* clear the other regs */ |
740 | config->ctigate = GENMASK(config->nr_ctm_channels - 1, 0); |
741 | config->asicctl = 0; |
742 | config->ctiappset = 0; |
743 | config->ctiinout_sel = 0; |
744 | config->xtrig_rchan_sel = 0; |
745 | |
746 | /* if enabled then write through */ |
747 | if (cti_active(cfg: config)) |
748 | cti_write_all_hw_regs(drvdata); |
749 | |
750 | spin_unlock(lock: &drvdata->spinlock); |
751 | return size; |
752 | } |
753 | static DEVICE_ATTR_WO(chan_xtrigs_reset); |
754 | |
755 | /* |
756 | * Write to select a channel to view, read to display the |
757 | * cross triggers for the selected channel. |
758 | */ |
759 | static ssize_t chan_xtrigs_sel_store(struct device *dev, |
760 | struct device_attribute *attr, |
761 | const char *buf, size_t size) |
762 | { |
763 | unsigned long val; |
764 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
765 | |
766 | if (kstrtoul(s: buf, base: 0, res: &val)) |
767 | return -EINVAL; |
768 | if (val > (drvdata->config.nr_ctm_channels - 1)) |
769 | return -EINVAL; |
770 | |
771 | spin_lock(lock: &drvdata->spinlock); |
772 | drvdata->config.xtrig_rchan_sel = val; |
773 | spin_unlock(lock: &drvdata->spinlock); |
774 | return size; |
775 | } |
776 | |
777 | static ssize_t chan_xtrigs_sel_show(struct device *dev, |
778 | struct device_attribute *attr, |
779 | char *buf) |
780 | { |
781 | unsigned long val; |
782 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
783 | |
784 | spin_lock(lock: &drvdata->spinlock); |
785 | val = drvdata->config.xtrig_rchan_sel; |
786 | spin_unlock(lock: &drvdata->spinlock); |
787 | |
788 | return sprintf(buf, fmt: "%ld\n" , val); |
789 | } |
790 | static DEVICE_ATTR_RW(chan_xtrigs_sel); |
791 | |
792 | static ssize_t chan_xtrigs_in_show(struct device *dev, |
793 | struct device_attribute *attr, |
794 | char *buf) |
795 | { |
796 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
797 | struct cti_config *cfg = &drvdata->config; |
798 | int used = 0, reg_idx; |
799 | int nr_trig_max = drvdata->config.nr_trig_max; |
800 | u32 chan_mask = BIT(cfg->xtrig_rchan_sel); |
801 | |
802 | for (reg_idx = 0; reg_idx < nr_trig_max; reg_idx++) { |
803 | if (chan_mask & cfg->ctiinen[reg_idx]) |
804 | used += sprintf(buf: buf + used, fmt: "%d " , reg_idx); |
805 | } |
806 | |
807 | used += sprintf(buf: buf + used, fmt: "\n" ); |
808 | return used; |
809 | } |
810 | static DEVICE_ATTR_RO(chan_xtrigs_in); |
811 | |
812 | static ssize_t chan_xtrigs_out_show(struct device *dev, |
813 | struct device_attribute *attr, |
814 | char *buf) |
815 | { |
816 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
817 | struct cti_config *cfg = &drvdata->config; |
818 | int used = 0, reg_idx; |
819 | int nr_trig_max = drvdata->config.nr_trig_max; |
820 | u32 chan_mask = BIT(cfg->xtrig_rchan_sel); |
821 | |
822 | for (reg_idx = 0; reg_idx < nr_trig_max; reg_idx++) { |
823 | if (chan_mask & cfg->ctiouten[reg_idx]) |
824 | used += sprintf(buf: buf + used, fmt: "%d " , reg_idx); |
825 | } |
826 | |
827 | used += sprintf(buf: buf + used, fmt: "\n" ); |
828 | return used; |
829 | } |
830 | static DEVICE_ATTR_RO(chan_xtrigs_out); |
831 | |
832 | static ssize_t print_chan_list(struct device *dev, |
833 | char *buf, bool inuse) |
834 | { |
835 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
836 | struct cti_config *config = &drvdata->config; |
837 | int size, i; |
838 | unsigned long inuse_bits = 0, chan_mask; |
839 | |
840 | /* scan regs to get bitmap of channels in use. */ |
841 | spin_lock(lock: &drvdata->spinlock); |
842 | for (i = 0; i < config->nr_trig_max; i++) { |
843 | inuse_bits |= config->ctiinen[i]; |
844 | inuse_bits |= config->ctiouten[i]; |
845 | } |
846 | spin_unlock(lock: &drvdata->spinlock); |
847 | |
848 | /* inverse bits if printing free channels */ |
849 | if (!inuse) |
850 | inuse_bits = ~inuse_bits; |
851 | |
852 | /* list of channels, or 'none' */ |
853 | chan_mask = GENMASK(config->nr_ctm_channels - 1, 0); |
854 | if (inuse_bits & chan_mask) |
855 | size = bitmap_print_to_pagebuf(list: true, buf, maskp: &inuse_bits, |
856 | nmaskbits: config->nr_ctm_channels); |
857 | else |
858 | size = sprintf(buf, fmt: "\n" ); |
859 | return size; |
860 | } |
861 | |
862 | static ssize_t chan_inuse_show(struct device *dev, |
863 | struct device_attribute *attr, |
864 | char *buf) |
865 | { |
866 | return print_chan_list(dev, buf, inuse: true); |
867 | } |
868 | static DEVICE_ATTR_RO(chan_inuse); |
869 | |
870 | static ssize_t chan_free_show(struct device *dev, |
871 | struct device_attribute *attr, |
872 | char *buf) |
873 | { |
874 | return print_chan_list(dev, buf, inuse: false); |
875 | } |
876 | static DEVICE_ATTR_RO(chan_free); |
877 | |
878 | static struct attribute *coresight_cti_channel_attrs[] = { |
879 | &dev_attr_trigin_attach.attr, |
880 | &dev_attr_trigin_detach.attr, |
881 | &dev_attr_trigout_attach.attr, |
882 | &dev_attr_trigout_detach.attr, |
883 | &dev_attr_trig_filter_enable.attr, |
884 | &dev_attr_trigout_filtered.attr, |
885 | &dev_attr_chan_gate_enable.attr, |
886 | &dev_attr_chan_gate_disable.attr, |
887 | &dev_attr_chan_set.attr, |
888 | &dev_attr_chan_clear.attr, |
889 | &dev_attr_chan_pulse.attr, |
890 | &dev_attr_chan_inuse.attr, |
891 | &dev_attr_chan_free.attr, |
892 | &dev_attr_chan_xtrigs_sel.attr, |
893 | &dev_attr_chan_xtrigs_in.attr, |
894 | &dev_attr_chan_xtrigs_out.attr, |
895 | &dev_attr_chan_xtrigs_reset.attr, |
896 | NULL, |
897 | }; |
898 | |
899 | /* Create the connections trigger groups and attrs dynamically */ |
900 | /* |
901 | * Each connection has dynamic group triggers<N> + name, trigin/out sigs/types |
902 | * attributes, + each device has static nr_trigger_cons giving the number |
903 | * of groups. e.g. in sysfs:- |
904 | * /cti_<name>/triggers0 |
905 | * /cti_<name>/triggers1 |
906 | * /cti_<name>/nr_trigger_cons |
907 | * where nr_trigger_cons = 2 |
908 | */ |
909 | static ssize_t con_name_show(struct device *dev, |
910 | struct device_attribute *attr, |
911 | char *buf) |
912 | { |
913 | struct dev_ext_attribute *ext_attr = |
914 | container_of(attr, struct dev_ext_attribute, attr); |
915 | struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; |
916 | |
917 | return sprintf(buf, fmt: "%s\n" , con->con_dev_name); |
918 | } |
919 | |
920 | static ssize_t trigin_sig_show(struct device *dev, |
921 | struct device_attribute *attr, |
922 | char *buf) |
923 | { |
924 | struct dev_ext_attribute *ext_attr = |
925 | container_of(attr, struct dev_ext_attribute, attr); |
926 | struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; |
927 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
928 | struct cti_config *cfg = &drvdata->config; |
929 | unsigned long mask = con->con_in->used_mask; |
930 | |
931 | return bitmap_print_to_pagebuf(list: true, buf, maskp: &mask, nmaskbits: cfg->nr_trig_max); |
932 | } |
933 | |
934 | static ssize_t trigout_sig_show(struct device *dev, |
935 | struct device_attribute *attr, |
936 | char *buf) |
937 | { |
938 | struct dev_ext_attribute *ext_attr = |
939 | container_of(attr, struct dev_ext_attribute, attr); |
940 | struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; |
941 | struct cti_drvdata *drvdata = dev_get_drvdata(dev: dev->parent); |
942 | struct cti_config *cfg = &drvdata->config; |
943 | unsigned long mask = con->con_out->used_mask; |
944 | |
945 | return bitmap_print_to_pagebuf(list: true, buf, maskp: &mask, nmaskbits: cfg->nr_trig_max); |
946 | } |
947 | |
948 | /* convert a sig type id to a name */ |
949 | static const char * |
950 | cti_sig_type_name(struct cti_trig_con *con, int used_count, bool in) |
951 | { |
952 | int idx = 0; |
953 | struct cti_trig_grp *grp = in ? con->con_in : con->con_out; |
954 | |
955 | if (used_count < grp->nr_sigs) |
956 | idx = grp->sig_types[used_count]; |
957 | return sig_type_names[idx]; |
958 | } |
959 | |
960 | static ssize_t trigin_type_show(struct device *dev, |
961 | struct device_attribute *attr, |
962 | char *buf) |
963 | { |
964 | struct dev_ext_attribute *ext_attr = |
965 | container_of(attr, struct dev_ext_attribute, attr); |
966 | struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; |
967 | int sig_idx, used = 0; |
968 | const char *name; |
969 | |
970 | for (sig_idx = 0; sig_idx < con->con_in->nr_sigs; sig_idx++) { |
971 | name = cti_sig_type_name(con, used_count: sig_idx, in: true); |
972 | used += sprintf(buf: buf + used, fmt: "%s " , name); |
973 | } |
974 | used += sprintf(buf: buf + used, fmt: "\n" ); |
975 | return used; |
976 | } |
977 | |
978 | static ssize_t trigout_type_show(struct device *dev, |
979 | struct device_attribute *attr, |
980 | char *buf) |
981 | { |
982 | struct dev_ext_attribute *ext_attr = |
983 | container_of(attr, struct dev_ext_attribute, attr); |
984 | struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; |
985 | int sig_idx, used = 0; |
986 | const char *name; |
987 | |
988 | for (sig_idx = 0; sig_idx < con->con_out->nr_sigs; sig_idx++) { |
989 | name = cti_sig_type_name(con, used_count: sig_idx, in: false); |
990 | used += sprintf(buf: buf + used, fmt: "%s " , name); |
991 | } |
992 | used += sprintf(buf: buf + used, fmt: "\n" ); |
993 | return used; |
994 | } |
995 | |
996 | /* |
997 | * Array of show function names declared above to allow selection |
998 | * for the connection attributes |
999 | */ |
1000 | static p_show_fn show_fns[CTI_CON_ATTR_MAX] = { |
1001 | con_name_show, |
1002 | trigin_sig_show, |
1003 | trigout_sig_show, |
1004 | trigin_type_show, |
1005 | trigout_type_show, |
1006 | }; |
1007 | |
1008 | static int cti_create_con_sysfs_attr(struct device *dev, |
1009 | struct cti_trig_con *con, |
1010 | enum cti_conn_attr_type attr_type, |
1011 | int attr_idx) |
1012 | { |
1013 | struct dev_ext_attribute *eattr; |
1014 | char *name; |
1015 | |
1016 | eattr = devm_kzalloc(dev, size: sizeof(struct dev_ext_attribute), |
1017 | GFP_KERNEL); |
1018 | if (eattr) { |
1019 | name = devm_kstrdup(dev, s: con_attr_names[attr_type], |
1020 | GFP_KERNEL); |
1021 | if (name) { |
1022 | /* fill out the underlying attribute struct */ |
1023 | eattr->attr.attr.name = name; |
1024 | eattr->attr.attr.mode = 0444; |
1025 | |
1026 | /* now the device_attribute struct */ |
1027 | eattr->attr.show = show_fns[attr_type]; |
1028 | } else { |
1029 | return -ENOMEM; |
1030 | } |
1031 | } else { |
1032 | return -ENOMEM; |
1033 | } |
1034 | eattr->var = con; |
1035 | con->con_attrs[attr_idx] = &eattr->attr.attr; |
1036 | /* |
1037 | * Initialize the dynamically allocated attribute |
1038 | * to avoid LOCKDEP splat. See include/linux/sysfs.h |
1039 | * for more details. |
1040 | */ |
1041 | sysfs_attr_init(con->con_attrs[attr_idx]); |
1042 | |
1043 | return 0; |
1044 | } |
1045 | |
1046 | static struct attribute_group * |
1047 | cti_create_con_sysfs_group(struct device *dev, struct cti_device *ctidev, |
1048 | int con_idx, struct cti_trig_con *tc) |
1049 | { |
1050 | struct attribute_group *group = NULL; |
1051 | int grp_idx; |
1052 | |
1053 | group = devm_kzalloc(dev, size: sizeof(struct attribute_group), GFP_KERNEL); |
1054 | if (!group) |
1055 | return NULL; |
1056 | |
1057 | group->name = devm_kasprintf(dev, GFP_KERNEL, fmt: "triggers%d" , con_idx); |
1058 | if (!group->name) |
1059 | return NULL; |
1060 | |
1061 | grp_idx = con_idx + CORESIGHT_CTI_STATIC_GROUPS_MAX - 1; |
1062 | ctidev->con_groups[grp_idx] = group; |
1063 | tc->attr_group = group; |
1064 | return group; |
1065 | } |
1066 | |
1067 | /* create a triggers connection group and the attributes for that group */ |
1068 | static int cti_create_con_attr_set(struct device *dev, int con_idx, |
1069 | struct cti_device *ctidev, |
1070 | struct cti_trig_con *tc) |
1071 | { |
1072 | struct attribute_group *attr_group = NULL; |
1073 | int attr_idx = 0; |
1074 | int err = -ENOMEM; |
1075 | |
1076 | attr_group = cti_create_con_sysfs_group(dev, ctidev, con_idx, tc); |
1077 | if (!attr_group) |
1078 | return -ENOMEM; |
1079 | |
1080 | /* allocate NULL terminated array of attributes */ |
1081 | tc->con_attrs = devm_kcalloc(dev, n: CTI_CON_ATTR_MAX + 1, |
1082 | size: sizeof(struct attribute *), GFP_KERNEL); |
1083 | if (!tc->con_attrs) |
1084 | return -ENOMEM; |
1085 | |
1086 | err = cti_create_con_sysfs_attr(dev, con: tc, attr_type: CTI_CON_ATTR_NAME, |
1087 | attr_idx: attr_idx++); |
1088 | if (err) |
1089 | return err; |
1090 | |
1091 | if (tc->con_in->nr_sigs > 0) { |
1092 | err = cti_create_con_sysfs_attr(dev, con: tc, |
1093 | attr_type: CTI_CON_ATTR_TRIGIN_SIG, |
1094 | attr_idx: attr_idx++); |
1095 | if (err) |
1096 | return err; |
1097 | |
1098 | err = cti_create_con_sysfs_attr(dev, con: tc, |
1099 | attr_type: CTI_CON_ATTR_TRIGIN_TYPES, |
1100 | attr_idx: attr_idx++); |
1101 | if (err) |
1102 | return err; |
1103 | } |
1104 | |
1105 | if (tc->con_out->nr_sigs > 0) { |
1106 | err = cti_create_con_sysfs_attr(dev, con: tc, |
1107 | attr_type: CTI_CON_ATTR_TRIGOUT_SIG, |
1108 | attr_idx: attr_idx++); |
1109 | if (err) |
1110 | return err; |
1111 | |
1112 | err = cti_create_con_sysfs_attr(dev, con: tc, |
1113 | attr_type: CTI_CON_ATTR_TRIGOUT_TYPES, |
1114 | attr_idx: attr_idx++); |
1115 | if (err) |
1116 | return err; |
1117 | } |
1118 | attr_group->attrs = tc->con_attrs; |
1119 | return 0; |
1120 | } |
1121 | |
1122 | /* create the array of group pointers for the CTI sysfs groups */ |
1123 | static int cti_create_cons_groups(struct device *dev, struct cti_device *ctidev) |
1124 | { |
1125 | int nr_groups; |
1126 | |
1127 | /* nr groups = dynamic + static + NULL terminator */ |
1128 | nr_groups = ctidev->nr_trig_con + CORESIGHT_CTI_STATIC_GROUPS_MAX; |
1129 | ctidev->con_groups = devm_kcalloc(dev, n: nr_groups, |
1130 | size: sizeof(struct attribute_group *), |
1131 | GFP_KERNEL); |
1132 | if (!ctidev->con_groups) |
1133 | return -ENOMEM; |
1134 | return 0; |
1135 | } |
1136 | |
1137 | int cti_create_cons_sysfs(struct device *dev, struct cti_drvdata *drvdata) |
1138 | { |
1139 | struct cti_device *ctidev = &drvdata->ctidev; |
1140 | int err, con_idx = 0, i; |
1141 | struct cti_trig_con *tc; |
1142 | |
1143 | err = cti_create_cons_groups(dev, ctidev); |
1144 | if (err) |
1145 | return err; |
1146 | |
1147 | /* populate first locations with the static set of groups */ |
1148 | for (i = 0; i < (CORESIGHT_CTI_STATIC_GROUPS_MAX - 1); i++) |
1149 | ctidev->con_groups[i] = coresight_cti_groups[i]; |
1150 | |
1151 | /* add dynamic set for each connection */ |
1152 | list_for_each_entry(tc, &ctidev->trig_cons, node) { |
1153 | err = cti_create_con_attr_set(dev, con_idx: con_idx++, ctidev, tc); |
1154 | if (err) |
1155 | break; |
1156 | } |
1157 | return err; |
1158 | } |
1159 | |
1160 | /* attribute and group sysfs tables. */ |
1161 | static const struct attribute_group coresight_cti_group = { |
1162 | .attrs = coresight_cti_attrs, |
1163 | }; |
1164 | |
1165 | static const struct attribute_group coresight_cti_mgmt_group = { |
1166 | .attrs = coresight_cti_mgmt_attrs, |
1167 | .name = "mgmt" , |
1168 | }; |
1169 | |
1170 | static const struct attribute_group coresight_cti_regs_group = { |
1171 | .attrs = coresight_cti_regs_attrs, |
1172 | .name = "regs" , |
1173 | }; |
1174 | |
1175 | static const struct attribute_group coresight_cti_channels_group = { |
1176 | .attrs = coresight_cti_channel_attrs, |
1177 | .name = "channels" , |
1178 | }; |
1179 | |
1180 | const struct attribute_group * |
1181 | coresight_cti_groups[CORESIGHT_CTI_STATIC_GROUPS_MAX] = { |
1182 | &coresight_cti_group, |
1183 | &coresight_cti_mgmt_group, |
1184 | &coresight_cti_regs_group, |
1185 | &coresight_cti_channels_group, |
1186 | NULL, |
1187 | }; |
1188 | |