1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/kernel.h> |
3 | #include <linux/device.h> |
4 | #include <linux/types.h> |
5 | #include <linux/spinlock.h> |
6 | #include <linux/debugfs.h> |
7 | #include <linux/seq_file.h> |
8 | #include <linux/uaccess.h> |
9 | #include <linux/usb/ch9.h> |
10 | #include <linux/usb/gadget.h> |
11 | #include <linux/usb/phy.h> |
12 | #include <linux/usb/otg.h> |
13 | #include <linux/usb/otg-fsm.h> |
14 | #include <linux/usb/chipidea.h> |
15 | |
16 | #include "ci.h" |
17 | #include "udc.h" |
18 | #include "bits.h" |
19 | #include "otg.h" |
20 | |
21 | /* |
22 | * ci_device_show: prints information about device capabilities and status |
23 | */ |
24 | static int ci_device_show(struct seq_file *s, void *data) |
25 | { |
26 | struct ci_hdrc *ci = s->private; |
27 | struct usb_gadget *gadget = &ci->gadget; |
28 | |
29 | seq_printf(m: s, fmt: "speed = %d\n" , gadget->speed); |
30 | seq_printf(m: s, fmt: "max_speed = %d\n" , gadget->max_speed); |
31 | seq_printf(m: s, fmt: "is_otg = %d\n" , gadget->is_otg); |
32 | seq_printf(m: s, fmt: "is_a_peripheral = %d\n" , gadget->is_a_peripheral); |
33 | seq_printf(m: s, fmt: "b_hnp_enable = %d\n" , gadget->b_hnp_enable); |
34 | seq_printf(m: s, fmt: "a_hnp_support = %d\n" , gadget->a_hnp_support); |
35 | seq_printf(m: s, fmt: "a_alt_hnp_support = %d\n" , gadget->a_alt_hnp_support); |
36 | seq_printf(m: s, fmt: "name = %s\n" , |
37 | (gadget->name ? gadget->name : "" )); |
38 | |
39 | if (!ci->driver) |
40 | return 0; |
41 | |
42 | seq_printf(m: s, fmt: "gadget function = %s\n" , |
43 | (ci->driver->function ? ci->driver->function : "" )); |
44 | seq_printf(m: s, fmt: "gadget max speed = %d\n" , ci->driver->max_speed); |
45 | |
46 | return 0; |
47 | } |
48 | DEFINE_SHOW_ATTRIBUTE(ci_device); |
49 | |
50 | /* |
51 | * ci_port_test_show: reads port test mode |
52 | */ |
53 | static int ci_port_test_show(struct seq_file *s, void *data) |
54 | { |
55 | struct ci_hdrc *ci = s->private; |
56 | unsigned long flags; |
57 | unsigned mode; |
58 | |
59 | pm_runtime_get_sync(dev: ci->dev); |
60 | spin_lock_irqsave(&ci->lock, flags); |
61 | mode = hw_port_test_get(ci); |
62 | spin_unlock_irqrestore(lock: &ci->lock, flags); |
63 | pm_runtime_put_sync(dev: ci->dev); |
64 | |
65 | seq_printf(m: s, fmt: "mode = %u\n" , mode); |
66 | |
67 | return 0; |
68 | } |
69 | |
70 | /* |
71 | * ci_port_test_write: writes port test mode |
72 | */ |
73 | static ssize_t ci_port_test_write(struct file *file, const char __user *ubuf, |
74 | size_t count, loff_t *ppos) |
75 | { |
76 | struct seq_file *s = file->private_data; |
77 | struct ci_hdrc *ci = s->private; |
78 | unsigned long flags; |
79 | unsigned mode; |
80 | char buf[32]; |
81 | int ret; |
82 | |
83 | count = min_t(size_t, sizeof(buf) - 1, count); |
84 | if (copy_from_user(to: buf, from: ubuf, n: count)) |
85 | return -EFAULT; |
86 | |
87 | /* sscanf requires a zero terminated string */ |
88 | buf[count] = '\0'; |
89 | |
90 | if (sscanf(buf, "%u" , &mode) != 1) |
91 | return -EINVAL; |
92 | |
93 | if (mode > 255) |
94 | return -EBADRQC; |
95 | |
96 | pm_runtime_get_sync(dev: ci->dev); |
97 | spin_lock_irqsave(&ci->lock, flags); |
98 | ret = hw_port_test_set(ci, mode); |
99 | spin_unlock_irqrestore(lock: &ci->lock, flags); |
100 | pm_runtime_put_sync(dev: ci->dev); |
101 | |
102 | return ret ? ret : count; |
103 | } |
104 | |
105 | static int ci_port_test_open(struct inode *inode, struct file *file) |
106 | { |
107 | return single_open(file, ci_port_test_show, inode->i_private); |
108 | } |
109 | |
110 | static const struct file_operations ci_port_test_fops = { |
111 | .open = ci_port_test_open, |
112 | .write = ci_port_test_write, |
113 | .read = seq_read, |
114 | .llseek = seq_lseek, |
115 | .release = single_release, |
116 | }; |
117 | |
118 | /* |
119 | * ci_qheads_show: DMA contents of all queue heads |
120 | */ |
121 | static int ci_qheads_show(struct seq_file *s, void *data) |
122 | { |
123 | struct ci_hdrc *ci = s->private; |
124 | unsigned long flags; |
125 | unsigned i, j; |
126 | |
127 | if (ci->role != CI_ROLE_GADGET) { |
128 | seq_printf(m: s, fmt: "not in gadget mode\n" ); |
129 | return 0; |
130 | } |
131 | |
132 | spin_lock_irqsave(&ci->lock, flags); |
133 | for (i = 0; i < ci->hw_ep_max/2; i++) { |
134 | struct ci_hw_ep *hweprx = &ci->ci_hw_ep[i]; |
135 | struct ci_hw_ep *hweptx = |
136 | &ci->ci_hw_ep[i + ci->hw_ep_max/2]; |
137 | seq_printf(m: s, fmt: "EP=%02i: RX=%08X TX=%08X\n" , |
138 | i, (u32)hweprx->qh.dma, (u32)hweptx->qh.dma); |
139 | for (j = 0; j < (sizeof(struct ci_hw_qh)/sizeof(u32)); j++) |
140 | seq_printf(m: s, fmt: " %04X: %08X %08X\n" , j, |
141 | *((u32 *)hweprx->qh.ptr + j), |
142 | *((u32 *)hweptx->qh.ptr + j)); |
143 | } |
144 | spin_unlock_irqrestore(lock: &ci->lock, flags); |
145 | |
146 | return 0; |
147 | } |
148 | DEFINE_SHOW_ATTRIBUTE(ci_qheads); |
149 | |
150 | /* |
151 | * ci_requests_show: DMA contents of all requests currently queued (all endpts) |
152 | */ |
153 | static int ci_requests_show(struct seq_file *s, void *data) |
154 | { |
155 | struct ci_hdrc *ci = s->private; |
156 | unsigned long flags; |
157 | struct ci_hw_req *req = NULL; |
158 | struct td_node *node, *tmpnode; |
159 | unsigned i, j, qsize = sizeof(struct ci_hw_td)/sizeof(u32); |
160 | |
161 | if (ci->role != CI_ROLE_GADGET) { |
162 | seq_printf(m: s, fmt: "not in gadget mode\n" ); |
163 | return 0; |
164 | } |
165 | |
166 | spin_lock_irqsave(&ci->lock, flags); |
167 | for (i = 0; i < ci->hw_ep_max; i++) |
168 | list_for_each_entry(req, &ci->ci_hw_ep[i].qh.queue, queue) { |
169 | list_for_each_entry_safe(node, tmpnode, &req->tds, td) { |
170 | seq_printf(m: s, fmt: "EP=%02i: TD=%08X %s\n" , |
171 | i % (ci->hw_ep_max / 2), |
172 | (u32)node->dma, |
173 | ((i < ci->hw_ep_max/2) ? |
174 | "RX" : "TX" )); |
175 | |
176 | for (j = 0; j < qsize; j++) |
177 | seq_printf(m: s, fmt: " %04X: %08X\n" , j, |
178 | *((u32 *)node->ptr + j)); |
179 | } |
180 | } |
181 | spin_unlock_irqrestore(lock: &ci->lock, flags); |
182 | |
183 | return 0; |
184 | } |
185 | DEFINE_SHOW_ATTRIBUTE(ci_requests); |
186 | |
187 | static int ci_otg_show(struct seq_file *s, void *unused) |
188 | { |
189 | struct ci_hdrc *ci = s->private; |
190 | struct otg_fsm *fsm; |
191 | |
192 | if (!ci || !ci_otg_is_fsm_mode(ci)) |
193 | return 0; |
194 | |
195 | fsm = &ci->fsm; |
196 | |
197 | /* ------ State ----- */ |
198 | seq_printf(m: s, fmt: "OTG state: %s\n\n" , |
199 | usb_otg_state_string(state: ci->otg.state)); |
200 | |
201 | /* ------ State Machine Variables ----- */ |
202 | seq_printf(m: s, fmt: "a_bus_drop: %d\n" , fsm->a_bus_drop); |
203 | |
204 | seq_printf(m: s, fmt: "a_bus_req: %d\n" , fsm->a_bus_req); |
205 | |
206 | seq_printf(m: s, fmt: "a_srp_det: %d\n" , fsm->a_srp_det); |
207 | |
208 | seq_printf(m: s, fmt: "a_vbus_vld: %d\n" , fsm->a_vbus_vld); |
209 | |
210 | seq_printf(m: s, fmt: "b_conn: %d\n" , fsm->b_conn); |
211 | |
212 | seq_printf(m: s, fmt: "adp_change: %d\n" , fsm->adp_change); |
213 | |
214 | seq_printf(m: s, fmt: "power_up: %d\n" , fsm->power_up); |
215 | |
216 | seq_printf(m: s, fmt: "a_bus_resume: %d\n" , fsm->a_bus_resume); |
217 | |
218 | seq_printf(m: s, fmt: "a_bus_suspend: %d\n" , fsm->a_bus_suspend); |
219 | |
220 | seq_printf(m: s, fmt: "a_conn: %d\n" , fsm->a_conn); |
221 | |
222 | seq_printf(m: s, fmt: "b_bus_req: %d\n" , fsm->b_bus_req); |
223 | |
224 | seq_printf(m: s, fmt: "b_bus_suspend: %d\n" , fsm->b_bus_suspend); |
225 | |
226 | seq_printf(m: s, fmt: "b_se0_srp: %d\n" , fsm->b_se0_srp); |
227 | |
228 | seq_printf(m: s, fmt: "b_ssend_srp: %d\n" , fsm->b_ssend_srp); |
229 | |
230 | seq_printf(m: s, fmt: "b_sess_vld: %d\n" , fsm->b_sess_vld); |
231 | |
232 | seq_printf(m: s, fmt: "b_srp_done: %d\n" , fsm->b_srp_done); |
233 | |
234 | seq_printf(m: s, fmt: "drv_vbus: %d\n" , fsm->drv_vbus); |
235 | |
236 | seq_printf(m: s, fmt: "loc_conn: %d\n" , fsm->loc_conn); |
237 | |
238 | seq_printf(m: s, fmt: "loc_sof: %d\n" , fsm->loc_sof); |
239 | |
240 | seq_printf(m: s, fmt: "adp_prb: %d\n" , fsm->adp_prb); |
241 | |
242 | seq_printf(m: s, fmt: "id: %d\n" , fsm->id); |
243 | |
244 | seq_printf(m: s, fmt: "protocol: %d\n" , fsm->protocol); |
245 | |
246 | return 0; |
247 | } |
248 | DEFINE_SHOW_ATTRIBUTE(ci_otg); |
249 | |
250 | static int ci_registers_show(struct seq_file *s, void *unused) |
251 | { |
252 | struct ci_hdrc *ci = s->private; |
253 | u32 tmp_reg; |
254 | |
255 | if (!ci || ci->in_lpm) |
256 | return -EPERM; |
257 | |
258 | /* ------ Registers ----- */ |
259 | tmp_reg = hw_read_intr_enable(ci); |
260 | seq_printf(m: s, fmt: "USBINTR reg: %08x\n" , tmp_reg); |
261 | |
262 | tmp_reg = hw_read_intr_status(ci); |
263 | seq_printf(m: s, fmt: "USBSTS reg: %08x\n" , tmp_reg); |
264 | |
265 | tmp_reg = hw_read(ci, reg: OP_USBMODE, mask: ~0); |
266 | seq_printf(m: s, fmt: "USBMODE reg: %08x\n" , tmp_reg); |
267 | |
268 | tmp_reg = hw_read(ci, reg: OP_USBCMD, mask: ~0); |
269 | seq_printf(m: s, fmt: "USBCMD reg: %08x\n" , tmp_reg); |
270 | |
271 | tmp_reg = hw_read(ci, reg: OP_PORTSC, mask: ~0); |
272 | seq_printf(m: s, fmt: "PORTSC reg: %08x\n" , tmp_reg); |
273 | |
274 | if (ci->is_otg) { |
275 | tmp_reg = hw_read_otgsc(ci, mask: ~0); |
276 | seq_printf(m: s, fmt: "OTGSC reg: %08x\n" , tmp_reg); |
277 | } |
278 | |
279 | return 0; |
280 | } |
281 | DEFINE_SHOW_ATTRIBUTE(ci_registers); |
282 | |
283 | /** |
284 | * dbg_create_files: initializes the attribute interface |
285 | * @ci: device |
286 | * |
287 | * This function returns an error code |
288 | */ |
289 | void dbg_create_files(struct ci_hdrc *ci) |
290 | { |
291 | struct dentry *dir; |
292 | |
293 | dir = debugfs_create_dir(name: dev_name(dev: ci->dev), parent: usb_debug_root); |
294 | |
295 | debugfs_create_file(name: "device" , S_IRUGO, parent: dir, data: ci, fops: &ci_device_fops); |
296 | debugfs_create_file(name: "port_test" , S_IRUGO | S_IWUSR, parent: dir, data: ci, fops: &ci_port_test_fops); |
297 | debugfs_create_file(name: "qheads" , S_IRUGO, parent: dir, data: ci, fops: &ci_qheads_fops); |
298 | debugfs_create_file(name: "requests" , S_IRUGO, parent: dir, data: ci, fops: &ci_requests_fops); |
299 | |
300 | if (ci_otg_is_fsm_mode(ci)) |
301 | debugfs_create_file(name: "otg" , S_IRUGO, parent: dir, data: ci, fops: &ci_otg_fops); |
302 | |
303 | debugfs_create_file(name: "registers" , S_IRUGO, parent: dir, data: ci, fops: &ci_registers_fops); |
304 | } |
305 | |
306 | /** |
307 | * dbg_remove_files: destroys the attribute interface |
308 | * @ci: device |
309 | */ |
310 | void dbg_remove_files(struct ci_hdrc *ci) |
311 | { |
312 | debugfs_lookup_and_remove(name: dev_name(dev: ci->dev), parent: usb_debug_root); |
313 | } |
314 | |