]>
Commit | Line | Data |
---|---|---|
39f40346 AS |
1 | /* |
2 | * Intel(R) Trace Hub driver core | |
3 | * | |
4 | * Copyright (C) 2014-2015 Intel Corporation. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | */ | |
15 | ||
16 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
17 | ||
18 | #include <linux/types.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/device.h> | |
21 | #include <linux/sysfs.h> | |
22 | #include <linux/kdev_t.h> | |
23 | #include <linux/debugfs.h> | |
24 | #include <linux/idr.h> | |
25 | #include <linux/pci.h> | |
142dfeb2 | 26 | #include <linux/pm_runtime.h> |
39f40346 AS |
27 | #include <linux/dma-mapping.h> |
28 | ||
29 | #include "intel_th.h" | |
30 | #include "debug.h" | |
31 | ||
c49a7591 AS |
32 | static bool host_mode __read_mostly; |
33 | module_param(host_mode, bool, 0444); | |
34 | ||
39f40346 AS |
35 | static DEFINE_IDA(intel_th_ida); |
36 | ||
37 | static int intel_th_match(struct device *dev, struct device_driver *driver) | |
38 | { | |
39 | struct intel_th_driver *thdrv = to_intel_th_driver(driver); | |
40 | struct intel_th_device *thdev = to_intel_th_device(dev); | |
41 | ||
42 | if (thdev->type == INTEL_TH_SWITCH && | |
43 | (!thdrv->enable || !thdrv->disable)) | |
44 | return 0; | |
45 | ||
46 | return !strcmp(thdev->name, driver->name); | |
47 | } | |
48 | ||
49 | static int intel_th_child_remove(struct device *dev, void *data) | |
50 | { | |
51 | device_release_driver(dev); | |
52 | ||
53 | return 0; | |
54 | } | |
55 | ||
56 | static int intel_th_probe(struct device *dev) | |
57 | { | |
58 | struct intel_th_driver *thdrv = to_intel_th_driver(dev->driver); | |
59 | struct intel_th_device *thdev = to_intel_th_device(dev); | |
60 | struct intel_th_driver *hubdrv; | |
61 | struct intel_th_device *hub = NULL; | |
62 | int ret; | |
63 | ||
64 | if (thdev->type == INTEL_TH_SWITCH) | |
65 | hub = thdev; | |
66 | else if (dev->parent) | |
67 | hub = to_intel_th_device(dev->parent); | |
68 | ||
69 | if (!hub || !hub->dev.driver) | |
70 | return -EPROBE_DEFER; | |
71 | ||
72 | hubdrv = to_intel_th_driver(hub->dev.driver); | |
73 | ||
142dfeb2 AS |
74 | pm_runtime_set_active(dev); |
75 | pm_runtime_no_callbacks(dev); | |
76 | pm_runtime_enable(dev); | |
77 | ||
39f40346 AS |
78 | ret = thdrv->probe(to_intel_th_device(dev)); |
79 | if (ret) | |
142dfeb2 | 80 | goto out_pm; |
39f40346 | 81 | |
b5edbf1e AS |
82 | if (thdrv->attr_group) { |
83 | ret = sysfs_create_group(&thdev->dev.kobj, thdrv->attr_group); | |
142dfeb2 AS |
84 | if (ret) |
85 | goto out; | |
b5edbf1e AS |
86 | } |
87 | ||
39f40346 AS |
88 | if (thdev->type == INTEL_TH_OUTPUT && |
89 | !intel_th_output_assigned(thdev)) | |
142dfeb2 | 90 | /* does not talk to hardware */ |
39f40346 AS |
91 | ret = hubdrv->assign(hub, thdev); |
92 | ||
142dfeb2 AS |
93 | out: |
94 | if (ret) | |
95 | thdrv->remove(thdev); | |
96 | ||
97 | out_pm: | |
98 | if (ret) | |
99 | pm_runtime_disable(dev); | |
100 | ||
39f40346 AS |
101 | return ret; |
102 | } | |
103 | ||
104 | static int intel_th_remove(struct device *dev) | |
105 | { | |
106 | struct intel_th_driver *thdrv = to_intel_th_driver(dev->driver); | |
107 | struct intel_th_device *thdev = to_intel_th_device(dev); | |
108 | struct intel_th_device *hub = to_intel_th_device(dev->parent); | |
109 | int err; | |
110 | ||
111 | if (thdev->type == INTEL_TH_SWITCH) { | |
112 | err = device_for_each_child(dev, thdev, intel_th_child_remove); | |
113 | if (err) | |
114 | return err; | |
115 | } | |
116 | ||
b5edbf1e AS |
117 | if (thdrv->attr_group) |
118 | sysfs_remove_group(&thdev->dev.kobj, thdrv->attr_group); | |
119 | ||
142dfeb2 AS |
120 | pm_runtime_get_sync(dev); |
121 | ||
39f40346 AS |
122 | thdrv->remove(thdev); |
123 | ||
124 | if (intel_th_output_assigned(thdev)) { | |
125 | struct intel_th_driver *hubdrv = | |
126 | to_intel_th_driver(dev->parent->driver); | |
127 | ||
128 | if (hub->dev.driver) | |
142dfeb2 | 129 | /* does not talk to hardware */ |
39f40346 AS |
130 | hubdrv->unassign(hub, thdev); |
131 | } | |
132 | ||
142dfeb2 AS |
133 | pm_runtime_disable(dev); |
134 | pm_runtime_set_active(dev); | |
135 | pm_runtime_enable(dev); | |
136 | ||
39f40346 AS |
137 | return 0; |
138 | } | |
139 | ||
140 | static struct bus_type intel_th_bus = { | |
141 | .name = "intel_th", | |
142 | .dev_attrs = NULL, | |
143 | .match = intel_th_match, | |
144 | .probe = intel_th_probe, | |
145 | .remove = intel_th_remove, | |
146 | }; | |
147 | ||
148 | static void intel_th_device_free(struct intel_th_device *thdev); | |
149 | ||
150 | static void intel_th_device_release(struct device *dev) | |
151 | { | |
152 | intel_th_device_free(to_intel_th_device(dev)); | |
153 | } | |
154 | ||
155 | static struct device_type intel_th_source_device_type = { | |
156 | .name = "intel_th_source_device", | |
157 | .release = intel_th_device_release, | |
158 | }; | |
159 | ||
14136e36 AS |
160 | static struct intel_th *to_intel_th(struct intel_th_device *thdev) |
161 | { | |
162 | /* | |
163 | * subdevice tree is flat: if this one is not a switch, its | |
164 | * parent must be | |
165 | */ | |
166 | if (thdev->type != INTEL_TH_SWITCH) | |
167 | thdev = to_intel_th_hub(thdev); | |
168 | ||
169 | if (WARN_ON_ONCE(!thdev || thdev->type != INTEL_TH_SWITCH)) | |
170 | return NULL; | |
171 | ||
172 | return dev_get_drvdata(thdev->dev.parent); | |
173 | } | |
174 | ||
39f40346 AS |
175 | static char *intel_th_output_devnode(struct device *dev, umode_t *mode, |
176 | kuid_t *uid, kgid_t *gid) | |
177 | { | |
178 | struct intel_th_device *thdev = to_intel_th_device(dev); | |
14136e36 | 179 | struct intel_th *th = to_intel_th(thdev); |
39f40346 AS |
180 | char *node; |
181 | ||
182 | if (thdev->id >= 0) | |
14136e36 AS |
183 | node = kasprintf(GFP_KERNEL, "intel_th%d/%s%d", th->id, |
184 | thdev->name, thdev->id); | |
39f40346 | 185 | else |
14136e36 AS |
186 | node = kasprintf(GFP_KERNEL, "intel_th%d/%s", th->id, |
187 | thdev->name); | |
39f40346 AS |
188 | |
189 | return node; | |
190 | } | |
191 | ||
192 | static ssize_t port_show(struct device *dev, struct device_attribute *attr, | |
193 | char *buf) | |
194 | { | |
195 | struct intel_th_device *thdev = to_intel_th_device(dev); | |
196 | ||
197 | if (thdev->output.port >= 0) | |
198 | return scnprintf(buf, PAGE_SIZE, "%u\n", thdev->output.port); | |
199 | ||
200 | return scnprintf(buf, PAGE_SIZE, "unassigned\n"); | |
201 | } | |
202 | ||
203 | static DEVICE_ATTR_RO(port); | |
204 | ||
205 | static int intel_th_output_activate(struct intel_th_device *thdev) | |
206 | { | |
f18a9531 AS |
207 | struct intel_th_driver *thdrv = |
208 | to_intel_th_driver_or_null(thdev->dev.driver); | |
142dfeb2 | 209 | int ret = 0; |
f18a9531 AS |
210 | |
211 | if (!thdrv) | |
212 | return -ENODEV; | |
39f40346 | 213 | |
e2ea295b AS |
214 | if (!try_module_get(thdrv->driver.owner)) |
215 | return -ENODEV; | |
216 | ||
142dfeb2 AS |
217 | pm_runtime_get_sync(&thdev->dev); |
218 | ||
39f40346 | 219 | if (thdrv->activate) |
142dfeb2 AS |
220 | ret = thdrv->activate(thdev); |
221 | else | |
222 | intel_th_trace_enable(thdev); | |
39f40346 | 223 | |
e609ccef | 224 | if (ret) { |
142dfeb2 | 225 | pm_runtime_put(&thdev->dev); |
e609ccef AS |
226 | module_put(thdrv->driver.owner); |
227 | } | |
39f40346 | 228 | |
142dfeb2 | 229 | return ret; |
39f40346 AS |
230 | } |
231 | ||
232 | static void intel_th_output_deactivate(struct intel_th_device *thdev) | |
233 | { | |
f18a9531 AS |
234 | struct intel_th_driver *thdrv = |
235 | to_intel_th_driver_or_null(thdev->dev.driver); | |
236 | ||
237 | if (!thdrv) | |
238 | return; | |
39f40346 AS |
239 | |
240 | if (thdrv->deactivate) | |
241 | thdrv->deactivate(thdev); | |
242 | else | |
243 | intel_th_trace_disable(thdev); | |
e2ea295b | 244 | |
142dfeb2 | 245 | pm_runtime_put(&thdev->dev); |
e2ea295b | 246 | module_put(thdrv->driver.owner); |
39f40346 AS |
247 | } |
248 | ||
249 | static ssize_t active_show(struct device *dev, struct device_attribute *attr, | |
250 | char *buf) | |
251 | { | |
252 | struct intel_th_device *thdev = to_intel_th_device(dev); | |
253 | ||
254 | return scnprintf(buf, PAGE_SIZE, "%d\n", thdev->output.active); | |
255 | } | |
256 | ||
257 | static ssize_t active_store(struct device *dev, struct device_attribute *attr, | |
258 | const char *buf, size_t size) | |
259 | { | |
260 | struct intel_th_device *thdev = to_intel_th_device(dev); | |
261 | unsigned long val; | |
262 | int ret; | |
263 | ||
264 | ret = kstrtoul(buf, 10, &val); | |
265 | if (ret) | |
266 | return ret; | |
267 | ||
268 | if (!!val != thdev->output.active) { | |
269 | if (val) | |
270 | ret = intel_th_output_activate(thdev); | |
271 | else | |
272 | intel_th_output_deactivate(thdev); | |
273 | } | |
274 | ||
275 | return ret ? ret : size; | |
276 | } | |
277 | ||
278 | static DEVICE_ATTR_RW(active); | |
279 | ||
280 | static struct attribute *intel_th_output_attrs[] = { | |
281 | &dev_attr_port.attr, | |
282 | &dev_attr_active.attr, | |
283 | NULL, | |
284 | }; | |
285 | ||
286 | ATTRIBUTE_GROUPS(intel_th_output); | |
287 | ||
288 | static struct device_type intel_th_output_device_type = { | |
289 | .name = "intel_th_output_device", | |
290 | .groups = intel_th_output_groups, | |
291 | .release = intel_th_device_release, | |
292 | .devnode = intel_th_output_devnode, | |
293 | }; | |
294 | ||
295 | static struct device_type intel_th_switch_device_type = { | |
296 | .name = "intel_th_switch_device", | |
297 | .release = intel_th_device_release, | |
298 | }; | |
299 | ||
300 | static struct device_type *intel_th_device_type[] = { | |
301 | [INTEL_TH_SOURCE] = &intel_th_source_device_type, | |
302 | [INTEL_TH_OUTPUT] = &intel_th_output_device_type, | |
303 | [INTEL_TH_SWITCH] = &intel_th_switch_device_type, | |
304 | }; | |
305 | ||
306 | int intel_th_driver_register(struct intel_th_driver *thdrv) | |
307 | { | |
308 | if (!thdrv->probe || !thdrv->remove) | |
309 | return -EINVAL; | |
310 | ||
311 | thdrv->driver.bus = &intel_th_bus; | |
312 | ||
313 | return driver_register(&thdrv->driver); | |
314 | } | |
315 | EXPORT_SYMBOL_GPL(intel_th_driver_register); | |
316 | ||
317 | void intel_th_driver_unregister(struct intel_th_driver *thdrv) | |
318 | { | |
319 | driver_unregister(&thdrv->driver); | |
320 | } | |
321 | EXPORT_SYMBOL_GPL(intel_th_driver_unregister); | |
322 | ||
323 | static struct intel_th_device * | |
324 | intel_th_device_alloc(struct intel_th *th, unsigned int type, const char *name, | |
325 | int id) | |
326 | { | |
327 | struct device *parent; | |
328 | struct intel_th_device *thdev; | |
329 | ||
330 | if (type == INTEL_TH_SWITCH) | |
331 | parent = th->dev; | |
332 | else | |
333 | parent = &th->hub->dev; | |
334 | ||
335 | thdev = kzalloc(sizeof(*thdev) + strlen(name) + 1, GFP_KERNEL); | |
336 | if (!thdev) | |
337 | return NULL; | |
338 | ||
339 | thdev->id = id; | |
340 | thdev->type = type; | |
341 | ||
342 | strcpy(thdev->name, name); | |
343 | device_initialize(&thdev->dev); | |
344 | thdev->dev.bus = &intel_th_bus; | |
345 | thdev->dev.type = intel_th_device_type[type]; | |
346 | thdev->dev.parent = parent; | |
347 | thdev->dev.dma_mask = parent->dma_mask; | |
348 | thdev->dev.dma_parms = parent->dma_parms; | |
349 | dma_set_coherent_mask(&thdev->dev, parent->coherent_dma_mask); | |
350 | if (id >= 0) | |
351 | dev_set_name(&thdev->dev, "%d-%s%d", th->id, name, id); | |
352 | else | |
353 | dev_set_name(&thdev->dev, "%d-%s", th->id, name); | |
354 | ||
355 | return thdev; | |
356 | } | |
357 | ||
358 | static int intel_th_device_add_resources(struct intel_th_device *thdev, | |
359 | struct resource *res, int nres) | |
360 | { | |
361 | struct resource *r; | |
362 | ||
363 | r = kmemdup(res, sizeof(*res) * nres, GFP_KERNEL); | |
364 | if (!r) | |
365 | return -ENOMEM; | |
366 | ||
367 | thdev->resource = r; | |
368 | thdev->num_resources = nres; | |
369 | ||
370 | return 0; | |
371 | } | |
372 | ||
373 | static void intel_th_device_remove(struct intel_th_device *thdev) | |
374 | { | |
375 | device_del(&thdev->dev); | |
376 | put_device(&thdev->dev); | |
377 | } | |
378 | ||
379 | static void intel_th_device_free(struct intel_th_device *thdev) | |
380 | { | |
381 | kfree(thdev->resource); | |
382 | kfree(thdev); | |
383 | } | |
384 | ||
385 | /* | |
386 | * Intel(R) Trace Hub subdevices | |
387 | */ | |
77c98b28 | 388 | static const struct intel_th_subdevice { |
39f40346 AS |
389 | const char *name; |
390 | struct resource res[3]; | |
391 | unsigned nres; | |
392 | unsigned type; | |
393 | unsigned otype; | |
4d02ceff | 394 | unsigned scrpd; |
39f40346 AS |
395 | int id; |
396 | } intel_th_subdevices[TH_SUBDEVICE_MAX] = { | |
397 | { | |
398 | .nres = 1, | |
399 | .res = { | |
400 | { | |
401 | .start = REG_GTH_OFFSET, | |
402 | .end = REG_GTH_OFFSET + REG_GTH_LENGTH - 1, | |
403 | .flags = IORESOURCE_MEM, | |
404 | }, | |
405 | }, | |
406 | .name = "gth", | |
407 | .type = INTEL_TH_SWITCH, | |
408 | .id = -1, | |
409 | }, | |
410 | { | |
411 | .nres = 2, | |
412 | .res = { | |
413 | { | |
414 | .start = REG_MSU_OFFSET, | |
415 | .end = REG_MSU_OFFSET + REG_MSU_LENGTH - 1, | |
416 | .flags = IORESOURCE_MEM, | |
417 | }, | |
418 | { | |
419 | .start = BUF_MSU_OFFSET, | |
420 | .end = BUF_MSU_OFFSET + BUF_MSU_LENGTH - 1, | |
421 | .flags = IORESOURCE_MEM, | |
422 | }, | |
423 | }, | |
424 | .name = "msc", | |
425 | .id = 0, | |
426 | .type = INTEL_TH_OUTPUT, | |
427 | .otype = GTH_MSU, | |
4d02ceff | 428 | .scrpd = SCRPD_MEM_IS_PRIM_DEST | SCRPD_MSC0_IS_ENABLED, |
39f40346 AS |
429 | }, |
430 | { | |
431 | .nres = 2, | |
432 | .res = { | |
433 | { | |
434 | .start = REG_MSU_OFFSET, | |
435 | .end = REG_MSU_OFFSET + REG_MSU_LENGTH - 1, | |
436 | .flags = IORESOURCE_MEM, | |
437 | }, | |
438 | { | |
439 | .start = BUF_MSU_OFFSET, | |
440 | .end = BUF_MSU_OFFSET + BUF_MSU_LENGTH - 1, | |
441 | .flags = IORESOURCE_MEM, | |
442 | }, | |
443 | }, | |
444 | .name = "msc", | |
445 | .id = 1, | |
446 | .type = INTEL_TH_OUTPUT, | |
447 | .otype = GTH_MSU, | |
4d02ceff | 448 | .scrpd = SCRPD_MEM_IS_PRIM_DEST | SCRPD_MSC1_IS_ENABLED, |
39f40346 AS |
449 | }, |
450 | { | |
451 | .nres = 2, | |
452 | .res = { | |
453 | { | |
454 | .start = REG_STH_OFFSET, | |
455 | .end = REG_STH_OFFSET + REG_STH_LENGTH - 1, | |
456 | .flags = IORESOURCE_MEM, | |
457 | }, | |
458 | { | |
459 | .start = TH_MMIO_SW, | |
460 | .end = 0, | |
461 | .flags = IORESOURCE_MEM, | |
462 | }, | |
463 | }, | |
464 | .id = -1, | |
465 | .name = "sth", | |
466 | .type = INTEL_TH_SOURCE, | |
467 | }, | |
468 | { | |
469 | .nres = 1, | |
470 | .res = { | |
471 | { | |
472 | .start = REG_PTI_OFFSET, | |
473 | .end = REG_PTI_OFFSET + REG_PTI_LENGTH - 1, | |
474 | .flags = IORESOURCE_MEM, | |
475 | }, | |
476 | }, | |
477 | .id = -1, | |
478 | .name = "pti", | |
479 | .type = INTEL_TH_OUTPUT, | |
480 | .otype = GTH_PTI, | |
4d02ceff | 481 | .scrpd = SCRPD_PTI_IS_PRIM_DEST, |
39f40346 AS |
482 | }, |
483 | { | |
484 | .nres = 1, | |
485 | .res = { | |
486 | { | |
487 | .start = REG_DCIH_OFFSET, | |
488 | .end = REG_DCIH_OFFSET + REG_DCIH_LENGTH - 1, | |
489 | .flags = IORESOURCE_MEM, | |
490 | }, | |
491 | }, | |
492 | .id = -1, | |
493 | .name = "dcih", | |
494 | .type = INTEL_TH_OUTPUT, | |
495 | }, | |
496 | }; | |
497 | ||
a36aa80f AS |
498 | #ifdef CONFIG_MODULES |
499 | static void __intel_th_request_hub_module(struct work_struct *work) | |
500 | { | |
501 | struct intel_th *th = container_of(work, struct intel_th, | |
502 | request_module_work); | |
503 | ||
504 | request_module("intel_th_%s", th->hub->name); | |
505 | } | |
506 | ||
507 | static int intel_th_request_hub_module(struct intel_th *th) | |
508 | { | |
509 | INIT_WORK(&th->request_module_work, __intel_th_request_hub_module); | |
510 | schedule_work(&th->request_module_work); | |
511 | ||
512 | return 0; | |
513 | } | |
514 | ||
515 | static void intel_th_request_hub_module_flush(struct intel_th *th) | |
516 | { | |
517 | flush_work(&th->request_module_work); | |
518 | } | |
519 | #else | |
520 | static inline int intel_th_request_hub_module(struct intel_th *th) | |
521 | { | |
522 | return -EINVAL; | |
523 | } | |
524 | ||
525 | static inline void intel_th_request_hub_module_flush(struct intel_th *th) | |
526 | { | |
527 | } | |
528 | #endif /* CONFIG_MODULES */ | |
529 | ||
39f40346 AS |
530 | static int intel_th_populate(struct intel_th *th, struct resource *devres, |
531 | unsigned int ndevres, int irq) | |
532 | { | |
533 | struct resource res[3]; | |
534 | unsigned int req = 0; | |
c49a7591 | 535 | int src, dst, err; |
39f40346 AS |
536 | |
537 | /* create devices for each intel_th_subdevice */ | |
c49a7591 AS |
538 | for (src = 0, dst = 0; src < ARRAY_SIZE(intel_th_subdevices); src++) { |
539 | const struct intel_th_subdevice *subdev = | |
540 | &intel_th_subdevices[src]; | |
39f40346 AS |
541 | struct intel_th_device *thdev; |
542 | int r; | |
543 | ||
c49a7591 AS |
544 | /* only allow SOURCE and SWITCH devices in host mode */ |
545 | if (host_mode && subdev->type == INTEL_TH_OUTPUT) | |
546 | continue; | |
547 | ||
39f40346 AS |
548 | thdev = intel_th_device_alloc(th, subdev->type, subdev->name, |
549 | subdev->id); | |
550 | if (!thdev) { | |
551 | err = -ENOMEM; | |
552 | goto kill_subdevs; | |
553 | } | |
554 | ||
555 | memcpy(res, subdev->res, | |
556 | sizeof(struct resource) * subdev->nres); | |
557 | ||
558 | for (r = 0; r < subdev->nres; r++) { | |
559 | int bar = TH_MMIO_CONFIG; | |
560 | ||
561 | /* | |
562 | * Take .end == 0 to mean 'take the whole bar', | |
563 | * .start then tells us which bar it is. Default to | |
564 | * TH_MMIO_CONFIG. | |
565 | */ | |
566 | if (!res[r].end && res[r].flags == IORESOURCE_MEM) { | |
567 | bar = res[r].start; | |
568 | res[r].start = 0; | |
569 | res[r].end = resource_size(&devres[bar]) - 1; | |
570 | } | |
571 | ||
572 | if (res[r].flags & IORESOURCE_MEM) { | |
573 | res[r].start += devres[bar].start; | |
574 | res[r].end += devres[bar].start; | |
575 | ||
576 | dev_dbg(th->dev, "%s:%d @ %pR\n", | |
577 | subdev->name, r, &res[r]); | |
578 | } else if (res[r].flags & IORESOURCE_IRQ) { | |
579 | res[r].start = irq; | |
580 | } | |
581 | } | |
582 | ||
583 | err = intel_th_device_add_resources(thdev, res, subdev->nres); | |
584 | if (err) { | |
585 | put_device(&thdev->dev); | |
586 | goto kill_subdevs; | |
587 | } | |
588 | ||
589 | if (subdev->type == INTEL_TH_OUTPUT) { | |
c49a7591 | 590 | thdev->dev.devt = MKDEV(th->major, dst); |
39f40346 AS |
591 | thdev->output.type = subdev->otype; |
592 | thdev->output.port = -1; | |
4d02ceff | 593 | thdev->output.scratchpad = subdev->scrpd; |
c49a7591 AS |
594 | } else if (subdev->type == INTEL_TH_SWITCH) { |
595 | thdev->host_mode = host_mode; | |
39f40346 AS |
596 | } |
597 | ||
598 | err = device_add(&thdev->dev); | |
599 | if (err) { | |
600 | put_device(&thdev->dev); | |
601 | goto kill_subdevs; | |
602 | } | |
603 | ||
604 | /* need switch driver to be loaded to enumerate the rest */ | |
605 | if (subdev->type == INTEL_TH_SWITCH && !req) { | |
606 | th->hub = thdev; | |
a36aa80f | 607 | err = intel_th_request_hub_module(th); |
39f40346 AS |
608 | if (!err) |
609 | req++; | |
610 | } | |
611 | ||
c49a7591 | 612 | th->thdev[dst++] = thdev; |
39f40346 AS |
613 | } |
614 | ||
615 | return 0; | |
616 | ||
617 | kill_subdevs: | |
c49a7591 AS |
618 | for (; dst >= 0; dst--) |
619 | intel_th_device_remove(th->thdev[dst]); | |
39f40346 AS |
620 | |
621 | return err; | |
622 | } | |
623 | ||
624 | static int match_devt(struct device *dev, void *data) | |
625 | { | |
626 | dev_t devt = (dev_t)(unsigned long)data; | |
627 | ||
628 | return dev->devt == devt; | |
629 | } | |
630 | ||
631 | static int intel_th_output_open(struct inode *inode, struct file *file) | |
632 | { | |
633 | const struct file_operations *fops; | |
634 | struct intel_th_driver *thdrv; | |
635 | struct device *dev; | |
636 | int err; | |
637 | ||
638 | dev = bus_find_device(&intel_th_bus, NULL, | |
639 | (void *)(unsigned long)inode->i_rdev, | |
640 | match_devt); | |
641 | if (!dev || !dev->driver) | |
642 | return -ENODEV; | |
643 | ||
644 | thdrv = to_intel_th_driver(dev->driver); | |
645 | fops = fops_get(thdrv->fops); | |
646 | if (!fops) | |
647 | return -ENODEV; | |
648 | ||
649 | replace_fops(file, fops); | |
650 | ||
651 | file->private_data = to_intel_th_device(dev); | |
652 | ||
653 | if (file->f_op->open) { | |
654 | err = file->f_op->open(inode, file); | |
655 | return err; | |
656 | } | |
657 | ||
658 | return 0; | |
659 | } | |
660 | ||
661 | static const struct file_operations intel_th_output_fops = { | |
662 | .open = intel_th_output_open, | |
663 | .llseek = noop_llseek, | |
664 | }; | |
665 | ||
666 | /** | |
667 | * intel_th_alloc() - allocate a new Intel TH device and its subdevices | |
668 | * @dev: parent device | |
669 | * @devres: parent's resources | |
670 | * @ndevres: number of resources | |
671 | * @irq: irq number | |
672 | */ | |
673 | struct intel_th * | |
674 | intel_th_alloc(struct device *dev, struct resource *devres, | |
675 | unsigned int ndevres, int irq) | |
676 | { | |
677 | struct intel_th *th; | |
678 | int err; | |
679 | ||
680 | th = kzalloc(sizeof(*th), GFP_KERNEL); | |
681 | if (!th) | |
682 | return ERR_PTR(-ENOMEM); | |
683 | ||
684 | th->id = ida_simple_get(&intel_th_ida, 0, 0, GFP_KERNEL); | |
685 | if (th->id < 0) { | |
686 | err = th->id; | |
687 | goto err_alloc; | |
688 | } | |
689 | ||
690 | th->major = __register_chrdev(0, 0, TH_POSSIBLE_OUTPUTS, | |
691 | "intel_th/output", &intel_th_output_fops); | |
692 | if (th->major < 0) { | |
693 | err = th->major; | |
694 | goto err_ida; | |
695 | } | |
696 | th->dev = dev; | |
697 | ||
d7b17871 AS |
698 | dev_set_drvdata(dev, th); |
699 | ||
142dfeb2 AS |
700 | pm_runtime_no_callbacks(dev); |
701 | pm_runtime_put(dev); | |
702 | pm_runtime_allow(dev); | |
703 | ||
39f40346 AS |
704 | err = intel_th_populate(th, devres, ndevres, irq); |
705 | if (err) | |
706 | goto err_chrdev; | |
707 | ||
708 | return th; | |
709 | ||
710 | err_chrdev: | |
142dfeb2 AS |
711 | pm_runtime_forbid(dev); |
712 | ||
39f40346 AS |
713 | __unregister_chrdev(th->major, 0, TH_POSSIBLE_OUTPUTS, |
714 | "intel_th/output"); | |
715 | ||
716 | err_ida: | |
717 | ida_simple_remove(&intel_th_ida, th->id); | |
718 | ||
719 | err_alloc: | |
720 | kfree(th); | |
721 | ||
722 | return ERR_PTR(err); | |
723 | } | |
724 | EXPORT_SYMBOL_GPL(intel_th_alloc); | |
725 | ||
726 | void intel_th_free(struct intel_th *th) | |
727 | { | |
728 | int i; | |
729 | ||
a36aa80f | 730 | intel_th_request_hub_module_flush(th); |
39f40346 | 731 | for (i = 0; i < TH_SUBDEVICE_MAX; i++) |
c49a7591 | 732 | if (th->thdev[i] && th->thdev[i] != th->hub) |
39f40346 AS |
733 | intel_th_device_remove(th->thdev[i]); |
734 | ||
735 | intel_th_device_remove(th->hub); | |
736 | ||
142dfeb2 AS |
737 | pm_runtime_get_sync(th->dev); |
738 | pm_runtime_forbid(th->dev); | |
739 | ||
39f40346 AS |
740 | __unregister_chrdev(th->major, 0, TH_POSSIBLE_OUTPUTS, |
741 | "intel_th/output"); | |
742 | ||
743 | ida_simple_remove(&intel_th_ida, th->id); | |
744 | ||
745 | kfree(th); | |
746 | } | |
747 | EXPORT_SYMBOL_GPL(intel_th_free); | |
748 | ||
749 | /** | |
750 | * intel_th_trace_enable() - enable tracing for an output device | |
751 | * @thdev: output device that requests tracing be enabled | |
752 | */ | |
753 | int intel_th_trace_enable(struct intel_th_device *thdev) | |
754 | { | |
755 | struct intel_th_device *hub = to_intel_th_device(thdev->dev.parent); | |
756 | struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); | |
757 | ||
758 | if (WARN_ON_ONCE(hub->type != INTEL_TH_SWITCH)) | |
759 | return -EINVAL; | |
760 | ||
761 | if (WARN_ON_ONCE(thdev->type != INTEL_TH_OUTPUT)) | |
762 | return -EINVAL; | |
763 | ||
142dfeb2 | 764 | pm_runtime_get_sync(&thdev->dev); |
39f40346 AS |
765 | hubdrv->enable(hub, &thdev->output); |
766 | ||
767 | return 0; | |
768 | } | |
769 | EXPORT_SYMBOL_GPL(intel_th_trace_enable); | |
770 | ||
771 | /** | |
772 | * intel_th_trace_disable() - disable tracing for an output device | |
773 | * @thdev: output device that requests tracing be disabled | |
774 | */ | |
775 | int intel_th_trace_disable(struct intel_th_device *thdev) | |
776 | { | |
777 | struct intel_th_device *hub = to_intel_th_device(thdev->dev.parent); | |
778 | struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); | |
779 | ||
780 | WARN_ON_ONCE(hub->type != INTEL_TH_SWITCH); | |
781 | if (WARN_ON_ONCE(thdev->type != INTEL_TH_OUTPUT)) | |
782 | return -EINVAL; | |
783 | ||
784 | hubdrv->disable(hub, &thdev->output); | |
142dfeb2 | 785 | pm_runtime_put(&thdev->dev); |
39f40346 AS |
786 | |
787 | return 0; | |
788 | } | |
789 | EXPORT_SYMBOL_GPL(intel_th_trace_disable); | |
790 | ||
791 | int intel_th_set_output(struct intel_th_device *thdev, | |
792 | unsigned int master) | |
793 | { | |
794 | struct intel_th_device *hub = to_intel_th_device(thdev->dev.parent); | |
795 | struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); | |
796 | ||
797 | if (!hubdrv->set_output) | |
798 | return -ENOTSUPP; | |
799 | ||
800 | return hubdrv->set_output(hub, master); | |
801 | } | |
802 | EXPORT_SYMBOL_GPL(intel_th_set_output); | |
803 | ||
804 | static int __init intel_th_init(void) | |
805 | { | |
806 | intel_th_debug_init(); | |
807 | ||
808 | return bus_register(&intel_th_bus); | |
809 | } | |
810 | subsys_initcall(intel_th_init); | |
811 | ||
812 | static void __exit intel_th_exit(void) | |
813 | { | |
814 | intel_th_debug_done(); | |
815 | ||
816 | bus_unregister(&intel_th_bus); | |
817 | } | |
818 | module_exit(intel_th_exit); | |
819 | ||
820 | MODULE_LICENSE("GPL v2"); | |
821 | MODULE_DESCRIPTION("Intel(R) Trace Hub controller driver"); | |
822 | MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>"); |