1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * MUSB OTG driver debugfs support |
4 | * |
5 | * Copyright 2010 Nokia Corporation |
6 | * Contact: Felipe Balbi <felipe.balbi@nokia.com> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/init.h> |
12 | #include <linux/debugfs.h> |
13 | #include <linux/seq_file.h> |
14 | |
15 | #include <linux/uaccess.h> |
16 | |
17 | #include "musb_core.h" |
18 | #include "musb_debug.h" |
19 | |
20 | struct musb_register_map { |
21 | char *name; |
22 | unsigned offset; |
23 | unsigned size; |
24 | }; |
25 | |
26 | static const struct musb_register_map musb_regmap[] = { |
27 | { "FAddr" , MUSB_FADDR, 8 }, |
28 | { "Power" , MUSB_POWER, 8 }, |
29 | { "Frame" , MUSB_FRAME, 16 }, |
30 | { "Index" , MUSB_INDEX, 8 }, |
31 | { "Testmode" , MUSB_TESTMODE, 8 }, |
32 | { "TxMaxPp" , MUSB_TXMAXP, 16 }, |
33 | { "TxCSRp" , MUSB_TXCSR, 16 }, |
34 | { "RxMaxPp" , MUSB_RXMAXP, 16 }, |
35 | { "RxCSR" , MUSB_RXCSR, 16 }, |
36 | { "RxCount" , MUSB_RXCOUNT, 16 }, |
37 | { "IntrRxE" , MUSB_INTRRXE, 16 }, |
38 | { "IntrTxE" , MUSB_INTRTXE, 16 }, |
39 | { "IntrUsbE" , MUSB_INTRUSBE, 8 }, |
40 | { "DevCtl" , MUSB_DEVCTL, 8 }, |
41 | { "VControl" , 0x68, 32 }, |
42 | { "HWVers" , MUSB_HWVERS, 16 }, |
43 | { "LinkInfo" , MUSB_LINKINFO, 8 }, |
44 | { "VPLen" , MUSB_VPLEN, 8 }, |
45 | { "HS_EOF1" , MUSB_HS_EOF1, 8 }, |
46 | { "FS_EOF1" , MUSB_FS_EOF1, 8 }, |
47 | { "LS_EOF1" , MUSB_LS_EOF1, 8 }, |
48 | { "SOFT_RST" , 0x7F, 8 }, |
49 | { "DMA_CNTLch0" , 0x204, 16 }, |
50 | { "DMA_ADDRch0" , 0x208, 32 }, |
51 | { "DMA_COUNTch0" , 0x20C, 32 }, |
52 | { "DMA_CNTLch1" , 0x214, 16 }, |
53 | { "DMA_ADDRch1" , 0x218, 32 }, |
54 | { "DMA_COUNTch1" , 0x21C, 32 }, |
55 | { "DMA_CNTLch2" , 0x224, 16 }, |
56 | { "DMA_ADDRch2" , 0x228, 32 }, |
57 | { "DMA_COUNTch2" , 0x22C, 32 }, |
58 | { "DMA_CNTLch3" , 0x234, 16 }, |
59 | { "DMA_ADDRch3" , 0x238, 32 }, |
60 | { "DMA_COUNTch3" , 0x23C, 32 }, |
61 | { "DMA_CNTLch4" , 0x244, 16 }, |
62 | { "DMA_ADDRch4" , 0x248, 32 }, |
63 | { "DMA_COUNTch4" , 0x24C, 32 }, |
64 | { "DMA_CNTLch5" , 0x254, 16 }, |
65 | { "DMA_ADDRch5" , 0x258, 32 }, |
66 | { "DMA_COUNTch5" , 0x25C, 32 }, |
67 | { "DMA_CNTLch6" , 0x264, 16 }, |
68 | { "DMA_ADDRch6" , 0x268, 32 }, |
69 | { "DMA_COUNTch6" , 0x26C, 32 }, |
70 | { "DMA_CNTLch7" , 0x274, 16 }, |
71 | { "DMA_ADDRch7" , 0x278, 32 }, |
72 | { "DMA_COUNTch7" , 0x27C, 32 }, |
73 | { "ConfigData" , MUSB_CONFIGDATA,8 }, |
74 | { "BabbleCtl" , MUSB_BABBLE_CTL,8 }, |
75 | { "TxFIFOsz" , MUSB_TXFIFOSZ, 8 }, |
76 | { "RxFIFOsz" , MUSB_RXFIFOSZ, 8 }, |
77 | { "TxFIFOadd" , MUSB_TXFIFOADD, 16 }, |
78 | { "RxFIFOadd" , MUSB_RXFIFOADD, 16 }, |
79 | { "EPInfo" , MUSB_EPINFO, 8 }, |
80 | { "RAMInfo" , MUSB_RAMINFO, 8 }, |
81 | { } /* Terminating Entry */ |
82 | }; |
83 | |
84 | static int musb_regdump_show(struct seq_file *s, void *unused) |
85 | { |
86 | struct musb *musb = s->private; |
87 | unsigned i; |
88 | |
89 | seq_printf(m: s, fmt: "MUSB (M)HDRC Register Dump\n" ); |
90 | pm_runtime_get_sync(dev: musb->controller); |
91 | |
92 | for (i = 0; i < ARRAY_SIZE(musb_regmap); i++) { |
93 | switch (musb_regmap[i].size) { |
94 | case 8: |
95 | seq_printf(m: s, fmt: "%-12s: %02x\n" , musb_regmap[i].name, |
96 | musb_readb(musb->mregs, musb_regmap[i].offset)); |
97 | break; |
98 | case 16: |
99 | seq_printf(m: s, fmt: "%-12s: %04x\n" , musb_regmap[i].name, |
100 | musb_readw(musb->mregs, musb_regmap[i].offset)); |
101 | break; |
102 | case 32: |
103 | seq_printf(m: s, fmt: "%-12s: %08x\n" , musb_regmap[i].name, |
104 | musb_readl(addr: musb->mregs, offset: musb_regmap[i].offset)); |
105 | break; |
106 | } |
107 | } |
108 | |
109 | pm_runtime_mark_last_busy(dev: musb->controller); |
110 | pm_runtime_put_autosuspend(dev: musb->controller); |
111 | return 0; |
112 | } |
113 | DEFINE_SHOW_ATTRIBUTE(musb_regdump); |
114 | |
115 | static int musb_test_mode_show(struct seq_file *s, void *unused) |
116 | { |
117 | struct musb *musb = s->private; |
118 | unsigned test; |
119 | |
120 | pm_runtime_get_sync(dev: musb->controller); |
121 | test = musb_readb(musb->mregs, MUSB_TESTMODE); |
122 | pm_runtime_mark_last_busy(dev: musb->controller); |
123 | pm_runtime_put_autosuspend(dev: musb->controller); |
124 | |
125 | if (test == (MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_FS)) |
126 | seq_printf(m: s, fmt: "force host full-speed\n" ); |
127 | |
128 | else if (test == (MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_HS)) |
129 | seq_printf(m: s, fmt: "force host high-speed\n" ); |
130 | |
131 | else if (test == MUSB_TEST_FORCE_HOST) |
132 | seq_printf(m: s, fmt: "force host\n" ); |
133 | |
134 | else if (test == MUSB_TEST_FIFO_ACCESS) |
135 | seq_printf(m: s, fmt: "fifo access\n" ); |
136 | |
137 | else if (test == MUSB_TEST_FORCE_FS) |
138 | seq_printf(m: s, fmt: "force full-speed\n" ); |
139 | |
140 | else if (test == MUSB_TEST_FORCE_HS) |
141 | seq_printf(m: s, fmt: "force high-speed\n" ); |
142 | |
143 | else if (test == MUSB_TEST_PACKET) |
144 | seq_printf(m: s, fmt: "test packet\n" ); |
145 | |
146 | else if (test == MUSB_TEST_K) |
147 | seq_printf(m: s, fmt: "test K\n" ); |
148 | |
149 | else if (test == MUSB_TEST_J) |
150 | seq_printf(m: s, fmt: "test J\n" ); |
151 | |
152 | else if (test == MUSB_TEST_SE0_NAK) |
153 | seq_printf(m: s, fmt: "test SE0 NAK\n" ); |
154 | |
155 | return 0; |
156 | } |
157 | |
158 | static int musb_test_mode_open(struct inode *inode, struct file *file) |
159 | { |
160 | return single_open(file, musb_test_mode_show, inode->i_private); |
161 | } |
162 | |
163 | static ssize_t musb_test_mode_write(struct file *file, |
164 | const char __user *ubuf, size_t count, loff_t *ppos) |
165 | { |
166 | struct seq_file *s = file->private_data; |
167 | struct musb *musb = s->private; |
168 | u8 test; |
169 | char buf[24]; |
170 | |
171 | memset(buf, 0x00, sizeof(buf)); |
172 | |
173 | if (copy_from_user(to: buf, from: ubuf, min_t(size_t, sizeof(buf) - 1, count))) |
174 | return -EFAULT; |
175 | |
176 | pm_runtime_get_sync(dev: musb->controller); |
177 | test = musb_readb(musb->mregs, MUSB_TESTMODE); |
178 | if (test) { |
179 | dev_err(musb->controller, "Error: test mode is already set. " |
180 | "Please do USB Bus Reset to start a new test.\n" ); |
181 | goto ret; |
182 | } |
183 | |
184 | if (strstarts(str: buf, prefix: "force host full-speed" )) |
185 | test = MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_FS; |
186 | |
187 | else if (strstarts(str: buf, prefix: "force host high-speed" )) |
188 | test = MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_HS; |
189 | |
190 | else if (strstarts(str: buf, prefix: "force host" )) |
191 | test = MUSB_TEST_FORCE_HOST; |
192 | |
193 | else if (strstarts(str: buf, prefix: "fifo access" )) |
194 | test = MUSB_TEST_FIFO_ACCESS; |
195 | |
196 | else if (strstarts(str: buf, prefix: "force full-speed" )) |
197 | test = MUSB_TEST_FORCE_FS; |
198 | |
199 | else if (strstarts(str: buf, prefix: "force high-speed" )) |
200 | test = MUSB_TEST_FORCE_HS; |
201 | |
202 | else if (strstarts(str: buf, prefix: "test packet" )) { |
203 | test = MUSB_TEST_PACKET; |
204 | musb_load_testpacket(musb); |
205 | } |
206 | |
207 | else if (strstarts(str: buf, prefix: "test K" )) |
208 | test = MUSB_TEST_K; |
209 | |
210 | else if (strstarts(str: buf, prefix: "test J" )) |
211 | test = MUSB_TEST_J; |
212 | |
213 | else if (strstarts(str: buf, prefix: "test SE0 NAK" )) |
214 | test = MUSB_TEST_SE0_NAK; |
215 | |
216 | musb_writeb(musb->mregs, MUSB_TESTMODE, test); |
217 | |
218 | ret: |
219 | pm_runtime_mark_last_busy(dev: musb->controller); |
220 | pm_runtime_put_autosuspend(dev: musb->controller); |
221 | return count; |
222 | } |
223 | |
224 | static const struct file_operations musb_test_mode_fops = { |
225 | .open = musb_test_mode_open, |
226 | .write = musb_test_mode_write, |
227 | .read = seq_read, |
228 | .llseek = seq_lseek, |
229 | .release = single_release, |
230 | }; |
231 | |
232 | static int musb_softconnect_show(struct seq_file *s, void *unused) |
233 | { |
234 | struct musb *musb = s->private; |
235 | u8 reg; |
236 | int connect; |
237 | |
238 | switch (musb_get_state(musb)) { |
239 | case OTG_STATE_A_HOST: |
240 | case OTG_STATE_A_WAIT_BCON: |
241 | pm_runtime_get_sync(dev: musb->controller); |
242 | |
243 | reg = musb_readb(musb->mregs, MUSB_DEVCTL); |
244 | connect = reg & MUSB_DEVCTL_SESSION ? 1 : 0; |
245 | |
246 | pm_runtime_mark_last_busy(dev: musb->controller); |
247 | pm_runtime_put_autosuspend(dev: musb->controller); |
248 | break; |
249 | default: |
250 | connect = -1; |
251 | } |
252 | |
253 | seq_printf(m: s, fmt: "%d\n" , connect); |
254 | |
255 | return 0; |
256 | } |
257 | |
258 | static int musb_softconnect_open(struct inode *inode, struct file *file) |
259 | { |
260 | return single_open(file, musb_softconnect_show, inode->i_private); |
261 | } |
262 | |
263 | static ssize_t musb_softconnect_write(struct file *file, |
264 | const char __user *ubuf, size_t count, loff_t *ppos) |
265 | { |
266 | struct seq_file *s = file->private_data; |
267 | struct musb *musb = s->private; |
268 | char buf[2]; |
269 | u8 reg; |
270 | |
271 | memset(buf, 0x00, sizeof(buf)); |
272 | |
273 | if (copy_from_user(to: &buf, from: ubuf, min_t(size_t, sizeof(buf) - 1, count))) |
274 | return -EFAULT; |
275 | |
276 | pm_runtime_get_sync(dev: musb->controller); |
277 | if (!strncmp(buf, "0" , 1)) { |
278 | switch (musb_get_state(musb)) { |
279 | case OTG_STATE_A_HOST: |
280 | musb_root_disconnect(musb); |
281 | reg = musb_readb(musb->mregs, MUSB_DEVCTL); |
282 | reg &= ~MUSB_DEVCTL_SESSION; |
283 | musb_writeb(musb->mregs, MUSB_DEVCTL, reg); |
284 | break; |
285 | default: |
286 | break; |
287 | } |
288 | } else if (!strncmp(buf, "1" , 1)) { |
289 | switch (musb_get_state(musb)) { |
290 | case OTG_STATE_A_WAIT_BCON: |
291 | /* |
292 | * musb_save_context() called in musb_runtime_suspend() |
293 | * might cache devctl with SESSION bit cleared during |
294 | * soft-disconnect, so specifically set SESSION bit |
295 | * here to preserve it for musb_runtime_resume(). |
296 | */ |
297 | musb->context.devctl |= MUSB_DEVCTL_SESSION; |
298 | reg = musb_readb(musb->mregs, MUSB_DEVCTL); |
299 | reg |= MUSB_DEVCTL_SESSION; |
300 | musb_writeb(musb->mregs, MUSB_DEVCTL, reg); |
301 | break; |
302 | default: |
303 | break; |
304 | } |
305 | } |
306 | |
307 | pm_runtime_mark_last_busy(dev: musb->controller); |
308 | pm_runtime_put_autosuspend(dev: musb->controller); |
309 | return count; |
310 | } |
311 | |
312 | /* |
313 | * In host mode, connect/disconnect the bus without physically |
314 | * remove the devices. |
315 | */ |
316 | static const struct file_operations musb_softconnect_fops = { |
317 | .open = musb_softconnect_open, |
318 | .write = musb_softconnect_write, |
319 | .read = seq_read, |
320 | .llseek = seq_lseek, |
321 | .release = single_release, |
322 | }; |
323 | |
324 | void musb_init_debugfs(struct musb *musb) |
325 | { |
326 | struct dentry *root; |
327 | |
328 | root = debugfs_create_dir(name: dev_name(dev: musb->controller), parent: usb_debug_root); |
329 | musb->debugfs_root = root; |
330 | |
331 | debugfs_create_file(name: "regdump" , S_IRUGO, parent: root, data: musb, fops: &musb_regdump_fops); |
332 | debugfs_create_file(name: "testmode" , S_IRUGO | S_IWUSR, parent: root, data: musb, |
333 | fops: &musb_test_mode_fops); |
334 | debugfs_create_file(name: "softconnect" , S_IRUGO | S_IWUSR, parent: root, data: musb, |
335 | fops: &musb_softconnect_fops); |
336 | } |
337 | |
338 | void /* __init_or_exit */ musb_exit_debugfs(struct musb *musb) |
339 | { |
340 | debugfs_remove_recursive(dentry: musb->debugfs_root); |
341 | } |
342 | |