diff options
Diffstat (limited to 'drivers/usb/chipidea/debug.c')
-rw-r--r-- | drivers/usb/chipidea/debug.c | 804 |
1 files changed, 804 insertions, 0 deletions
diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c new file mode 100644 index 00000000000..c4b3e15532d --- /dev/null +++ b/drivers/usb/chipidea/debug.c | |||
@@ -0,0 +1,804 @@ | |||
1 | #include <linux/delay.h> | ||
2 | #include <linux/device.h> | ||
3 | #include <linux/dmapool.h> | ||
4 | #include <linux/dma-mapping.h> | ||
5 | #include <linux/init.h> | ||
6 | #include <linux/platform_device.h> | ||
7 | #include <linux/module.h> | ||
8 | #include <linux/interrupt.h> | ||
9 | #include <linux/io.h> | ||
10 | #include <linux/irq.h> | ||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/slab.h> | ||
13 | #include <linux/pm_runtime.h> | ||
14 | #include <linux/usb/ch9.h> | ||
15 | #include <linux/usb/gadget.h> | ||
16 | #include <linux/usb/otg.h> | ||
17 | #include <linux/usb/chipidea.h> | ||
18 | |||
19 | #include "ci.h" | ||
20 | #include "udc.h" | ||
21 | #include "bits.h" | ||
22 | #include "debug.h" | ||
23 | |||
24 | /* Interrupt statistics */ | ||
25 | #define ISR_MASK 0x1F | ||
26 | static struct isr_statistics { | ||
27 | u32 test; | ||
28 | u32 ui; | ||
29 | u32 uei; | ||
30 | u32 pci; | ||
31 | u32 uri; | ||
32 | u32 sli; | ||
33 | u32 none; | ||
34 | struct { | ||
35 | u32 cnt; | ||
36 | u32 buf[ISR_MASK+1]; | ||
37 | u32 idx; | ||
38 | } hndl; | ||
39 | } isr_statistics; | ||
40 | |||
41 | void dbg_interrupt(u32 intmask) | ||
42 | { | ||
43 | if (!intmask) { | ||
44 | isr_statistics.none++; | ||
45 | return; | ||
46 | } | ||
47 | |||
48 | isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intmask; | ||
49 | isr_statistics.hndl.idx &= ISR_MASK; | ||
50 | isr_statistics.hndl.cnt++; | ||
51 | |||
52 | if (USBi_URI & intmask) | ||
53 | isr_statistics.uri++; | ||
54 | if (USBi_PCI & intmask) | ||
55 | isr_statistics.pci++; | ||
56 | if (USBi_UEI & intmask) | ||
57 | isr_statistics.uei++; | ||
58 | if (USBi_UI & intmask) | ||
59 | isr_statistics.ui++; | ||
60 | if (USBi_SLI & intmask) | ||
61 | isr_statistics.sli++; | ||
62 | } | ||
63 | |||
64 | /** | ||
65 | * hw_register_read: reads all device registers (execute without interruption) | ||
66 | * @buf: destination buffer | ||
67 | * @size: buffer size | ||
68 | * | ||
69 | * This function returns number of registers read | ||
70 | */ | ||
71 | static size_t hw_register_read(struct ci13xxx *udc, u32 *buf, size_t size) | ||
72 | { | ||
73 | unsigned i; | ||
74 | |||
75 | if (size > udc->hw_bank.size) | ||
76 | size = udc->hw_bank.size; | ||
77 | |||
78 | for (i = 0; i < size; i++) | ||
79 | buf[i] = hw_read(udc, i * sizeof(u32), ~0); | ||
80 | |||
81 | return size; | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * hw_register_write: writes to register | ||
86 | * @addr: register address | ||
87 | * @data: register value | ||
88 | * | ||
89 | * This function returns an error code | ||
90 | */ | ||
91 | static int hw_register_write(struct ci13xxx *udc, u16 addr, u32 data) | ||
92 | { | ||
93 | /* align */ | ||
94 | addr /= sizeof(u32); | ||
95 | |||
96 | if (addr >= udc->hw_bank.size) | ||
97 | return -EINVAL; | ||
98 | |||
99 | /* align */ | ||
100 | addr *= sizeof(u32); | ||
101 | |||
102 | hw_write(udc, addr, ~0, data); | ||
103 | return 0; | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * hw_intr_clear: disables interrupt & clears interrupt status (execute without | ||
108 | * interruption) | ||
109 | * @n: interrupt bit | ||
110 | * | ||
111 | * This function returns an error code | ||
112 | */ | ||
113 | static int hw_intr_clear(struct ci13xxx *udc, int n) | ||
114 | { | ||
115 | if (n >= REG_BITS) | ||
116 | return -EINVAL; | ||
117 | |||
118 | hw_write(udc, OP_USBINTR, BIT(n), 0); | ||
119 | hw_write(udc, OP_USBSTS, BIT(n), BIT(n)); | ||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * hw_intr_force: enables interrupt & forces interrupt status (execute without | ||
125 | * interruption) | ||
126 | * @n: interrupt bit | ||
127 | * | ||
128 | * This function returns an error code | ||
129 | */ | ||
130 | static int hw_intr_force(struct ci13xxx *udc, int n) | ||
131 | { | ||
132 | if (n >= REG_BITS) | ||
133 | return -EINVAL; | ||
134 | |||
135 | hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE); | ||
136 | hw_write(udc, OP_USBINTR, BIT(n), BIT(n)); | ||
137 | hw_write(udc, OP_USBSTS, BIT(n), BIT(n)); | ||
138 | hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, 0); | ||
139 | return 0; | ||
140 | } | ||
141 | |||
142 | /** | ||
143 | * show_device: prints information about device capabilities and status | ||
144 | * | ||
145 | * Check "device.h" for details | ||
146 | */ | ||
147 | static ssize_t show_device(struct device *dev, struct device_attribute *attr, | ||
148 | char *buf) | ||
149 | { | ||
150 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
151 | struct usb_gadget *gadget = &udc->gadget; | ||
152 | int n = 0; | ||
153 | |||
154 | if (attr == NULL || buf == NULL) { | ||
155 | dev_err(udc->dev, "[%s] EINVAL\n", __func__); | ||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | n += scnprintf(buf + n, PAGE_SIZE - n, "speed = %d\n", | ||
160 | gadget->speed); | ||
161 | n += scnprintf(buf + n, PAGE_SIZE - n, "max_speed = %d\n", | ||
162 | gadget->max_speed); | ||
163 | /* TODO: Scheduled for removal in 3.8. */ | ||
164 | n += scnprintf(buf + n, PAGE_SIZE - n, "is_dualspeed = %d\n", | ||
165 | gadget_is_dualspeed(gadget)); | ||
166 | n += scnprintf(buf + n, PAGE_SIZE - n, "is_otg = %d\n", | ||
167 | gadget->is_otg); | ||
168 | n += scnprintf(buf + n, PAGE_SIZE - n, "is_a_peripheral = %d\n", | ||
169 | gadget->is_a_peripheral); | ||
170 | n += scnprintf(buf + n, PAGE_SIZE - n, "b_hnp_enable = %d\n", | ||
171 | gadget->b_hnp_enable); | ||
172 | n += scnprintf(buf + n, PAGE_SIZE - n, "a_hnp_support = %d\n", | ||
173 | gadget->a_hnp_support); | ||
174 | n += scnprintf(buf + n, PAGE_SIZE - n, "a_alt_hnp_support = %d\n", | ||
175 | gadget->a_alt_hnp_support); | ||
176 | n += scnprintf(buf + n, PAGE_SIZE - n, "name = %s\n", | ||
177 | (gadget->name ? gadget->name : "")); | ||
178 | |||
179 | return n; | ||
180 | } | ||
181 | static DEVICE_ATTR(device, S_IRUSR, show_device, NULL); | ||
182 | |||
183 | /** | ||
184 | * show_driver: prints information about attached gadget (if any) | ||
185 | * | ||
186 | * Check "device.h" for details | ||
187 | */ | ||
188 | static ssize_t show_driver(struct device *dev, struct device_attribute *attr, | ||
189 | char *buf) | ||
190 | { | ||
191 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
192 | struct usb_gadget_driver *driver = udc->driver; | ||
193 | int n = 0; | ||
194 | |||
195 | if (attr == NULL || buf == NULL) { | ||
196 | dev_err(dev, "[%s] EINVAL\n", __func__); | ||
197 | return 0; | ||
198 | } | ||
199 | |||
200 | if (driver == NULL) | ||
201 | return scnprintf(buf, PAGE_SIZE, | ||
202 | "There is no gadget attached!\n"); | ||
203 | |||
204 | n += scnprintf(buf + n, PAGE_SIZE - n, "function = %s\n", | ||
205 | (driver->function ? driver->function : "")); | ||
206 | n += scnprintf(buf + n, PAGE_SIZE - n, "max speed = %d\n", | ||
207 | driver->max_speed); | ||
208 | |||
209 | return n; | ||
210 | } | ||
211 | static DEVICE_ATTR(driver, S_IRUSR, show_driver, NULL); | ||
212 | |||
213 | /* Maximum event message length */ | ||
214 | #define DBG_DATA_MSG 64UL | ||
215 | |||
216 | /* Maximum event messages */ | ||
217 | #define DBG_DATA_MAX 128UL | ||
218 | |||
219 | /* Event buffer descriptor */ | ||
220 | static struct { | ||
221 | char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */ | ||
222 | unsigned idx; /* index */ | ||
223 | unsigned tty; /* print to console? */ | ||
224 | rwlock_t lck; /* lock */ | ||
225 | } dbg_data = { | ||
226 | .idx = 0, | ||
227 | .tty = 0, | ||
228 | .lck = __RW_LOCK_UNLOCKED(lck) | ||
229 | }; | ||
230 | |||
231 | /** | ||
232 | * dbg_dec: decrements debug event index | ||
233 | * @idx: buffer index | ||
234 | */ | ||
235 | static void dbg_dec(unsigned *idx) | ||
236 | { | ||
237 | *idx = (*idx - 1) & (DBG_DATA_MAX-1); | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * dbg_inc: increments debug event index | ||
242 | * @idx: buffer index | ||
243 | */ | ||
244 | static void dbg_inc(unsigned *idx) | ||
245 | { | ||
246 | *idx = (*idx + 1) & (DBG_DATA_MAX-1); | ||
247 | } | ||
248 | |||
249 | /** | ||
250 | * dbg_print: prints the common part of the event | ||
251 | * @addr: endpoint address | ||
252 | * @name: event name | ||
253 | * @status: status | ||
254 | * @extra: extra information | ||
255 | */ | ||
256 | static void dbg_print(u8 addr, const char *name, int status, const char *extra) | ||
257 | { | ||
258 | struct timeval tval; | ||
259 | unsigned int stamp; | ||
260 | unsigned long flags; | ||
261 | |||
262 | write_lock_irqsave(&dbg_data.lck, flags); | ||
263 | |||
264 | do_gettimeofday(&tval); | ||
265 | stamp = tval.tv_sec & 0xFFFF; /* 2^32 = 4294967296. Limit to 4096s */ | ||
266 | stamp = stamp * 1000000 + tval.tv_usec; | ||
267 | |||
268 | scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, | ||
269 | "%04X\t? %02X %-7.7s %4i ?\t%s\n", | ||
270 | stamp, addr, name, status, extra); | ||
271 | |||
272 | dbg_inc(&dbg_data.idx); | ||
273 | |||
274 | write_unlock_irqrestore(&dbg_data.lck, flags); | ||
275 | |||
276 | if (dbg_data.tty != 0) | ||
277 | pr_notice("%04X\t? %02X %-7.7s %4i ?\t%s\n", | ||
278 | stamp, addr, name, status, extra); | ||
279 | } | ||
280 | |||
281 | /** | ||
282 | * dbg_done: prints a DONE event | ||
283 | * @addr: endpoint address | ||
284 | * @td: transfer descriptor | ||
285 | * @status: status | ||
286 | */ | ||
287 | void dbg_done(u8 addr, const u32 token, int status) | ||
288 | { | ||
289 | char msg[DBG_DATA_MSG]; | ||
290 | |||
291 | scnprintf(msg, sizeof(msg), "%d %02X", | ||
292 | (int)(token & TD_TOTAL_BYTES) >> ffs_nr(TD_TOTAL_BYTES), | ||
293 | (int)(token & TD_STATUS) >> ffs_nr(TD_STATUS)); | ||
294 | dbg_print(addr, "DONE", status, msg); | ||
295 | } | ||
296 | |||
297 | /** | ||
298 | * dbg_event: prints a generic event | ||
299 | * @addr: endpoint address | ||
300 | * @name: event name | ||
301 | * @status: status | ||
302 | */ | ||
303 | void dbg_event(u8 addr, const char *name, int status) | ||
304 | { | ||
305 | if (name != NULL) | ||
306 | dbg_print(addr, name, status, ""); | ||
307 | } | ||
308 | |||
309 | /* | ||
310 | * dbg_queue: prints a QUEUE event | ||
311 | * @addr: endpoint address | ||
312 | * @req: USB request | ||
313 | * @status: status | ||
314 | */ | ||
315 | void dbg_queue(u8 addr, const struct usb_request *req, int status) | ||
316 | { | ||
317 | char msg[DBG_DATA_MSG]; | ||
318 | |||
319 | if (req != NULL) { | ||
320 | scnprintf(msg, sizeof(msg), | ||
321 | "%d %d", !req->no_interrupt, req->length); | ||
322 | dbg_print(addr, "QUEUE", status, msg); | ||
323 | } | ||
324 | } | ||
325 | |||
326 | /** | ||
327 | * dbg_setup: prints a SETUP event | ||
328 | * @addr: endpoint address | ||
329 | * @req: setup request | ||
330 | */ | ||
331 | void dbg_setup(u8 addr, const struct usb_ctrlrequest *req) | ||
332 | { | ||
333 | char msg[DBG_DATA_MSG]; | ||
334 | |||
335 | if (req != NULL) { | ||
336 | scnprintf(msg, sizeof(msg), | ||
337 | "%02X %02X %04X %04X %d", req->bRequestType, | ||
338 | req->bRequest, le16_to_cpu(req->wValue), | ||
339 | le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); | ||
340 | dbg_print(addr, "SETUP", 0, msg); | ||
341 | } | ||
342 | } | ||
343 | |||
344 | /** | ||
345 | * show_events: displays the event buffer | ||
346 | * | ||
347 | * Check "device.h" for details | ||
348 | */ | ||
349 | static ssize_t show_events(struct device *dev, struct device_attribute *attr, | ||
350 | char *buf) | ||
351 | { | ||
352 | unsigned long flags; | ||
353 | unsigned i, j, n = 0; | ||
354 | |||
355 | if (attr == NULL || buf == NULL) { | ||
356 | dev_err(dev->parent, "[%s] EINVAL\n", __func__); | ||
357 | return 0; | ||
358 | } | ||
359 | |||
360 | read_lock_irqsave(&dbg_data.lck, flags); | ||
361 | |||
362 | i = dbg_data.idx; | ||
363 | for (dbg_dec(&i); i != dbg_data.idx; dbg_dec(&i)) { | ||
364 | n += strlen(dbg_data.buf[i]); | ||
365 | if (n >= PAGE_SIZE) { | ||
366 | n -= strlen(dbg_data.buf[i]); | ||
367 | break; | ||
368 | } | ||
369 | } | ||
370 | for (j = 0, dbg_inc(&i); j < n; dbg_inc(&i)) | ||
371 | j += scnprintf(buf + j, PAGE_SIZE - j, | ||
372 | "%s", dbg_data.buf[i]); | ||
373 | |||
374 | read_unlock_irqrestore(&dbg_data.lck, flags); | ||
375 | |||
376 | return n; | ||
377 | } | ||
378 | |||
379 | /** | ||
380 | * store_events: configure if events are going to be also printed to console | ||
381 | * | ||
382 | * Check "device.h" for details | ||
383 | */ | ||
384 | static ssize_t store_events(struct device *dev, struct device_attribute *attr, | ||
385 | const char *buf, size_t count) | ||
386 | { | ||
387 | unsigned tty; | ||
388 | |||
389 | if (attr == NULL || buf == NULL) { | ||
390 | dev_err(dev, "[%s] EINVAL\n", __func__); | ||
391 | goto done; | ||
392 | } | ||
393 | |||
394 | if (sscanf(buf, "%u", &tty) != 1 || tty > 1) { | ||
395 | dev_err(dev, "<1|0>: enable|disable console log\n"); | ||
396 | goto done; | ||
397 | } | ||
398 | |||
399 | dbg_data.tty = tty; | ||
400 | dev_info(dev, "tty = %u", dbg_data.tty); | ||
401 | |||
402 | done: | ||
403 | return count; | ||
404 | } | ||
405 | static DEVICE_ATTR(events, S_IRUSR | S_IWUSR, show_events, store_events); | ||
406 | |||
407 | /** | ||
408 | * show_inters: interrupt status, enable status and historic | ||
409 | * | ||
410 | * Check "device.h" for details | ||
411 | */ | ||
412 | static ssize_t show_inters(struct device *dev, struct device_attribute *attr, | ||
413 | char *buf) | ||
414 | { | ||
415 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
416 | unsigned long flags; | ||
417 | u32 intr; | ||
418 | unsigned i, j, n = 0; | ||
419 | |||
420 | if (attr == NULL || buf == NULL) { | ||
421 | dev_err(udc->dev, "[%s] EINVAL\n", __func__); | ||
422 | return 0; | ||
423 | } | ||
424 | |||
425 | spin_lock_irqsave(&udc->lock, flags); | ||
426 | |||
427 | /*n += scnprintf(buf + n, PAGE_SIZE - n, | ||
428 | "status = %08x\n", hw_read_intr_status(udc)); | ||
429 | n += scnprintf(buf + n, PAGE_SIZE - n, | ||
430 | "enable = %08x\n", hw_read_intr_enable(udc));*/ | ||
431 | |||
432 | n += scnprintf(buf + n, PAGE_SIZE - n, "*test = %d\n", | ||
433 | isr_statistics.test); | ||
434 | n += scnprintf(buf + n, PAGE_SIZE - n, "? ui = %d\n", | ||
435 | isr_statistics.ui); | ||
436 | n += scnprintf(buf + n, PAGE_SIZE - n, "? uei = %d\n", | ||
437 | isr_statistics.uei); | ||
438 | n += scnprintf(buf + n, PAGE_SIZE - n, "? pci = %d\n", | ||
439 | isr_statistics.pci); | ||
440 | n += scnprintf(buf + n, PAGE_SIZE - n, "? uri = %d\n", | ||
441 | isr_statistics.uri); | ||
442 | n += scnprintf(buf + n, PAGE_SIZE - n, "? sli = %d\n", | ||
443 | isr_statistics.sli); | ||
444 | n += scnprintf(buf + n, PAGE_SIZE - n, "*none = %d\n", | ||
445 | isr_statistics.none); | ||
446 | n += scnprintf(buf + n, PAGE_SIZE - n, "*hndl = %d\n", | ||
447 | isr_statistics.hndl.cnt); | ||
448 | |||
449 | for (i = isr_statistics.hndl.idx, j = 0; j <= ISR_MASK; j++, i++) { | ||
450 | i &= ISR_MASK; | ||
451 | intr = isr_statistics.hndl.buf[i]; | ||
452 | |||
453 | if (USBi_UI & intr) | ||
454 | n += scnprintf(buf + n, PAGE_SIZE - n, "ui "); | ||
455 | intr &= ~USBi_UI; | ||
456 | if (USBi_UEI & intr) | ||
457 | n += scnprintf(buf + n, PAGE_SIZE - n, "uei "); | ||
458 | intr &= ~USBi_UEI; | ||
459 | if (USBi_PCI & intr) | ||
460 | n += scnprintf(buf + n, PAGE_SIZE - n, "pci "); | ||
461 | intr &= ~USBi_PCI; | ||
462 | if (USBi_URI & intr) | ||
463 | n += scnprintf(buf + n, PAGE_SIZE - n, "uri "); | ||
464 | intr &= ~USBi_URI; | ||
465 | if (USBi_SLI & intr) | ||
466 | n += scnprintf(buf + n, PAGE_SIZE - n, "sli "); | ||
467 | intr &= ~USBi_SLI; | ||
468 | if (intr) | ||
469 | n += scnprintf(buf + n, PAGE_SIZE - n, "??? "); | ||
470 | if (isr_statistics.hndl.buf[i]) | ||
471 | n += scnprintf(buf + n, PAGE_SIZE - n, "\n"); | ||
472 | } | ||
473 | |||
474 | spin_unlock_irqrestore(&udc->lock, flags); | ||
475 | |||
476 | return n; | ||
477 | } | ||
478 | |||
479 | /** | ||
480 | * store_inters: enable & force or disable an individual interrutps | ||
481 | * (to be used for test purposes only) | ||
482 | * | ||
483 | * Check "device.h" for details | ||
484 | */ | ||
485 | static ssize_t store_inters(struct device *dev, struct device_attribute *attr, | ||
486 | const char *buf, size_t count) | ||
487 | { | ||
488 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
489 | unsigned long flags; | ||
490 | unsigned en, bit; | ||
491 | |||
492 | if (attr == NULL || buf == NULL) { | ||
493 | dev_err(udc->dev, "EINVAL\n"); | ||
494 | goto done; | ||
495 | } | ||
496 | |||
497 | if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) { | ||
498 | dev_err(udc->dev, "<1|0> <bit>: enable|disable interrupt\n"); | ||
499 | goto done; | ||
500 | } | ||
501 | |||
502 | spin_lock_irqsave(&udc->lock, flags); | ||
503 | if (en) { | ||
504 | if (hw_intr_force(udc, bit)) | ||
505 | dev_err(dev, "invalid bit number\n"); | ||
506 | else | ||
507 | isr_statistics.test++; | ||
508 | } else { | ||
509 | if (hw_intr_clear(udc, bit)) | ||
510 | dev_err(dev, "invalid bit number\n"); | ||
511 | } | ||
512 | spin_unlock_irqrestore(&udc->lock, flags); | ||
513 | |||
514 | done: | ||
515 | return count; | ||
516 | } | ||
517 | static DEVICE_ATTR(inters, S_IRUSR | S_IWUSR, show_inters, store_inters); | ||
518 | |||
519 | /** | ||
520 | * show_port_test: reads port test mode | ||
521 | * | ||
522 | * Check "device.h" for details | ||
523 | */ | ||
524 | static ssize_t show_port_test(struct device *dev, | ||
525 | struct device_attribute *attr, char *buf) | ||
526 | { | ||
527 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
528 | unsigned long flags; | ||
529 | unsigned mode; | ||
530 | |||
531 | if (attr == NULL || buf == NULL) { | ||
532 | dev_err(udc->dev, "EINVAL\n"); | ||
533 | return 0; | ||
534 | } | ||
535 | |||
536 | spin_lock_irqsave(&udc->lock, flags); | ||
537 | mode = hw_port_test_get(udc); | ||
538 | spin_unlock_irqrestore(&udc->lock, flags); | ||
539 | |||
540 | return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode); | ||
541 | } | ||
542 | |||
543 | /** | ||
544 | * store_port_test: writes port test mode | ||
545 | * | ||
546 | * Check "device.h" for details | ||
547 | */ | ||
548 | static ssize_t store_port_test(struct device *dev, | ||
549 | struct device_attribute *attr, | ||
550 | const char *buf, size_t count) | ||
551 | { | ||
552 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
553 | unsigned long flags; | ||
554 | unsigned mode; | ||
555 | |||
556 | if (attr == NULL || buf == NULL) { | ||
557 | dev_err(udc->dev, "[%s] EINVAL\n", __func__); | ||
558 | goto done; | ||
559 | } | ||
560 | |||
561 | if (sscanf(buf, "%u", &mode) != 1) { | ||
562 | dev_err(udc->dev, "<mode>: set port test mode"); | ||
563 | goto done; | ||
564 | } | ||
565 | |||
566 | spin_lock_irqsave(&udc->lock, flags); | ||
567 | if (hw_port_test_set(udc, mode)) | ||
568 | dev_err(udc->dev, "invalid mode\n"); | ||
569 | spin_unlock_irqrestore(&udc->lock, flags); | ||
570 | |||
571 | done: | ||
572 | return count; | ||
573 | } | ||
574 | static DEVICE_ATTR(port_test, S_IRUSR | S_IWUSR, | ||
575 | show_port_test, store_port_test); | ||
576 | |||
577 | /** | ||
578 | * show_qheads: DMA contents of all queue heads | ||
579 | * | ||
580 | * Check "device.h" for details | ||
581 | */ | ||
582 | static ssize_t show_qheads(struct device *dev, struct device_attribute *attr, | ||
583 | char *buf) | ||
584 | { | ||
585 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
586 | unsigned long flags; | ||
587 | unsigned i, j, n = 0; | ||
588 | |||
589 | if (attr == NULL || buf == NULL) { | ||
590 | dev_err(udc->dev, "[%s] EINVAL\n", __func__); | ||
591 | return 0; | ||
592 | } | ||
593 | |||
594 | spin_lock_irqsave(&udc->lock, flags); | ||
595 | for (i = 0; i < udc->hw_ep_max/2; i++) { | ||
596 | struct ci13xxx_ep *mEpRx = &udc->ci13xxx_ep[i]; | ||
597 | struct ci13xxx_ep *mEpTx = | ||
598 | &udc->ci13xxx_ep[i + udc->hw_ep_max/2]; | ||
599 | n += scnprintf(buf + n, PAGE_SIZE - n, | ||
600 | "EP=%02i: RX=%08X TX=%08X\n", | ||
601 | i, (u32)mEpRx->qh.dma, (u32)mEpTx->qh.dma); | ||
602 | for (j = 0; j < (sizeof(struct ci13xxx_qh)/sizeof(u32)); j++) { | ||
603 | n += scnprintf(buf + n, PAGE_SIZE - n, | ||
604 | " %04X: %08X %08X\n", j, | ||
605 | *((u32 *)mEpRx->qh.ptr + j), | ||
606 | *((u32 *)mEpTx->qh.ptr + j)); | ||
607 | } | ||
608 | } | ||
609 | spin_unlock_irqrestore(&udc->lock, flags); | ||
610 | |||
611 | return n; | ||
612 | } | ||
613 | static DEVICE_ATTR(qheads, S_IRUSR, show_qheads, NULL); | ||
614 | |||
615 | /** | ||
616 | * show_registers: dumps all registers | ||
617 | * | ||
618 | * Check "device.h" for details | ||
619 | */ | ||
620 | #define DUMP_ENTRIES 512 | ||
621 | static ssize_t show_registers(struct device *dev, | ||
622 | struct device_attribute *attr, char *buf) | ||
623 | { | ||
624 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
625 | unsigned long flags; | ||
626 | u32 *dump; | ||
627 | unsigned i, k, n = 0; | ||
628 | |||
629 | if (attr == NULL || buf == NULL) { | ||
630 | dev_err(udc->dev, "[%s] EINVAL\n", __func__); | ||
631 | return 0; | ||
632 | } | ||
633 | |||
634 | dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL); | ||
635 | if (!dump) { | ||
636 | dev_err(udc->dev, "%s: out of memory\n", __func__); | ||
637 | return 0; | ||
638 | } | ||
639 | |||
640 | spin_lock_irqsave(&udc->lock, flags); | ||
641 | k = hw_register_read(udc, dump, DUMP_ENTRIES); | ||
642 | spin_unlock_irqrestore(&udc->lock, flags); | ||
643 | |||
644 | for (i = 0; i < k; i++) { | ||
645 | n += scnprintf(buf + n, PAGE_SIZE - n, | ||
646 | "reg[0x%04X] = 0x%08X\n", | ||
647 | i * (unsigned)sizeof(u32), dump[i]); | ||
648 | } | ||
649 | kfree(dump); | ||
650 | |||
651 | return n; | ||
652 | } | ||
653 | |||
654 | /** | ||
655 | * store_registers: writes value to register address | ||
656 | * | ||
657 | * Check "device.h" for details | ||
658 | */ | ||
659 | static ssize_t store_registers(struct device *dev, | ||
660 | struct device_attribute *attr, | ||
661 | const char *buf, size_t count) | ||
662 | { | ||
663 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
664 | unsigned long addr, data, flags; | ||
665 | |||
666 | if (attr == NULL || buf == NULL) { | ||
667 | dev_err(udc->dev, "[%s] EINVAL\n", __func__); | ||
668 | goto done; | ||
669 | } | ||
670 | |||
671 | if (sscanf(buf, "%li %li", &addr, &data) != 2) { | ||
672 | dev_err(udc->dev, | ||
673 | "<addr> <data>: write data to register address\n"); | ||
674 | goto done; | ||
675 | } | ||
676 | |||
677 | spin_lock_irqsave(&udc->lock, flags); | ||
678 | if (hw_register_write(udc, addr, data)) | ||
679 | dev_err(udc->dev, "invalid address range\n"); | ||
680 | spin_unlock_irqrestore(&udc->lock, flags); | ||
681 | |||
682 | done: | ||
683 | return count; | ||
684 | } | ||
685 | static DEVICE_ATTR(registers, S_IRUSR | S_IWUSR, | ||
686 | show_registers, store_registers); | ||
687 | |||
688 | /** | ||
689 | * show_requests: DMA contents of all requests currently queued (all endpts) | ||
690 | * | ||
691 | * Check "device.h" for details | ||
692 | */ | ||
693 | static ssize_t show_requests(struct device *dev, struct device_attribute *attr, | ||
694 | char *buf) | ||
695 | { | ||
696 | struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); | ||
697 | unsigned long flags; | ||
698 | struct list_head *ptr = NULL; | ||
699 | struct ci13xxx_req *req = NULL; | ||
700 | unsigned i, j, n = 0, qSize = sizeof(struct ci13xxx_td)/sizeof(u32); | ||
701 | |||
702 | if (attr == NULL || buf == NULL) { | ||
703 | dev_err(udc->dev, "[%s] EINVAL\n", __func__); | ||
704 | return 0; | ||
705 | } | ||
706 | |||
707 | spin_lock_irqsave(&udc->lock, flags); | ||
708 | for (i = 0; i < udc->hw_ep_max; i++) | ||
709 | list_for_each(ptr, &udc->ci13xxx_ep[i].qh.queue) | ||
710 | { | ||
711 | req = list_entry(ptr, struct ci13xxx_req, queue); | ||
712 | |||
713 | n += scnprintf(buf + n, PAGE_SIZE - n, | ||
714 | "EP=%02i: TD=%08X %s\n", | ||
715 | i % udc->hw_ep_max/2, (u32)req->dma, | ||
716 | ((i < udc->hw_ep_max/2) ? "RX" : "TX")); | ||
717 | |||
718 | for (j = 0; j < qSize; j++) | ||
719 | n += scnprintf(buf + n, PAGE_SIZE - n, | ||
720 | " %04X: %08X\n", j, | ||
721 | *((u32 *)req->ptr + j)); | ||
722 | } | ||
723 | spin_unlock_irqrestore(&udc->lock, flags); | ||
724 | |||
725 | return n; | ||
726 | } | ||
727 | static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL); | ||
728 | |||
729 | /** | ||
730 | * dbg_create_files: initializes the attribute interface | ||
731 | * @dev: device | ||
732 | * | ||
733 | * This function returns an error code | ||
734 | */ | ||
735 | int dbg_create_files(struct device *dev) | ||
736 | { | ||
737 | int retval = 0; | ||
738 | |||
739 | if (dev == NULL) | ||
740 | return -EINVAL; | ||
741 | retval = device_create_file(dev, &dev_attr_device); | ||
742 | if (retval) | ||
743 | goto done; | ||
744 | retval = device_create_file(dev, &dev_attr_driver); | ||
745 | if (retval) | ||
746 | goto rm_device; | ||
747 | retval = device_create_file(dev, &dev_attr_events); | ||
748 | if (retval) | ||
749 | goto rm_driver; | ||
750 | retval = device_create_file(dev, &dev_attr_inters); | ||
751 | if (retval) | ||
752 | goto rm_events; | ||
753 | retval = device_create_file(dev, &dev_attr_port_test); | ||
754 | if (retval) | ||
755 | goto rm_inters; | ||
756 | retval = device_create_file(dev, &dev_attr_qheads); | ||
757 | if (retval) | ||
758 | goto rm_port_test; | ||
759 | retval = device_create_file(dev, &dev_attr_registers); | ||
760 | if (retval) | ||
761 | goto rm_qheads; | ||
762 | retval = device_create_file(dev, &dev_attr_requests); | ||
763 | if (retval) | ||
764 | goto rm_registers; | ||
765 | return 0; | ||
766 | |||
767 | rm_registers: | ||
768 | device_remove_file(dev, &dev_attr_registers); | ||
769 | rm_qheads: | ||
770 | device_remove_file(dev, &dev_attr_qheads); | ||
771 | rm_port_test: | ||
772 | device_remove_file(dev, &dev_attr_port_test); | ||
773 | rm_inters: | ||
774 | device_remove_file(dev, &dev_attr_inters); | ||
775 | rm_events: | ||
776 | device_remove_file(dev, &dev_attr_events); | ||
777 | rm_driver: | ||
778 | device_remove_file(dev, &dev_attr_driver); | ||
779 | rm_device: | ||
780 | device_remove_file(dev, &dev_attr_device); | ||
781 | done: | ||
782 | return retval; | ||
783 | } | ||
784 | |||
785 | /** | ||
786 | * dbg_remove_files: destroys the attribute interface | ||
787 | * @dev: device | ||
788 | * | ||
789 | * This function returns an error code | ||
790 | */ | ||
791 | int dbg_remove_files(struct device *dev) | ||
792 | { | ||
793 | if (dev == NULL) | ||
794 | return -EINVAL; | ||
795 | device_remove_file(dev, &dev_attr_requests); | ||
796 | device_remove_file(dev, &dev_attr_registers); | ||
797 | device_remove_file(dev, &dev_attr_qheads); | ||
798 | device_remove_file(dev, &dev_attr_port_test); | ||
799 | device_remove_file(dev, &dev_attr_inters); | ||
800 | device_remove_file(dev, &dev_attr_events); | ||
801 | device_remove_file(dev, &dev_attr_driver); | ||
802 | device_remove_file(dev, &dev_attr_device); | ||
803 | return 0; | ||
804 | } | ||