1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * otg.c - ChipIdea USB IP core OTG driver |
4 | * |
5 | * Copyright (C) 2013 Freescale Semiconductor, Inc. |
6 | * |
7 | * Author: Peter Chen |
8 | */ |
9 | |
10 | /* |
11 | * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP |
12 | * are also included. |
13 | */ |
14 | |
15 | #include <linux/usb/otg.h> |
16 | #include <linux/usb/gadget.h> |
17 | #include <linux/usb/chipidea.h> |
18 | |
19 | #include "ci.h" |
20 | #include "bits.h" |
21 | #include "otg.h" |
22 | #include "otg_fsm.h" |
23 | |
24 | /** |
25 | * hw_read_otgsc - returns otgsc register bits value. |
26 | * @ci: the controller |
27 | * @mask: bitfield mask |
28 | */ |
29 | u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) |
30 | { |
31 | struct ci_hdrc_cable *cable; |
32 | u32 val = hw_read(ci, reg: OP_OTGSC, mask); |
33 | |
34 | /* |
35 | * If using extcon framework for VBUS and/or ID signal |
36 | * detection overwrite OTGSC register value |
37 | */ |
38 | cable = &ci->platdata->vbus_extcon; |
39 | if (!IS_ERR(ptr: cable->edev) || ci->role_switch) { |
40 | if (cable->changed) |
41 | val |= OTGSC_BSVIS; |
42 | else |
43 | val &= ~OTGSC_BSVIS; |
44 | |
45 | if (cable->connected) |
46 | val |= OTGSC_BSV; |
47 | else |
48 | val &= ~OTGSC_BSV; |
49 | |
50 | if (cable->enabled) |
51 | val |= OTGSC_BSVIE; |
52 | else |
53 | val &= ~OTGSC_BSVIE; |
54 | } |
55 | |
56 | cable = &ci->platdata->id_extcon; |
57 | if (!IS_ERR(ptr: cable->edev) || ci->role_switch) { |
58 | if (cable->changed) |
59 | val |= OTGSC_IDIS; |
60 | else |
61 | val &= ~OTGSC_IDIS; |
62 | |
63 | if (cable->connected) |
64 | val &= ~OTGSC_ID; /* host */ |
65 | else |
66 | val |= OTGSC_ID; /* device */ |
67 | |
68 | if (cable->enabled) |
69 | val |= OTGSC_IDIE; |
70 | else |
71 | val &= ~OTGSC_IDIE; |
72 | } |
73 | |
74 | return val & mask; |
75 | } |
76 | |
77 | /** |
78 | * hw_write_otgsc - updates target bits of OTGSC register. |
79 | * @ci: the controller |
80 | * @mask: bitfield mask |
81 | * @data: to be written |
82 | */ |
83 | void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) |
84 | { |
85 | struct ci_hdrc_cable *cable; |
86 | |
87 | cable = &ci->platdata->vbus_extcon; |
88 | if (!IS_ERR(ptr: cable->edev) || ci->role_switch) { |
89 | if (data & mask & OTGSC_BSVIS) |
90 | cable->changed = false; |
91 | |
92 | /* Don't enable vbus interrupt if using external notifier */ |
93 | if (data & mask & OTGSC_BSVIE) { |
94 | cable->enabled = true; |
95 | data &= ~OTGSC_BSVIE; |
96 | } else if (mask & OTGSC_BSVIE) { |
97 | cable->enabled = false; |
98 | } |
99 | } |
100 | |
101 | cable = &ci->platdata->id_extcon; |
102 | if (!IS_ERR(ptr: cable->edev) || ci->role_switch) { |
103 | if (data & mask & OTGSC_IDIS) |
104 | cable->changed = false; |
105 | |
106 | /* Don't enable id interrupt if using external notifier */ |
107 | if (data & mask & OTGSC_IDIE) { |
108 | cable->enabled = true; |
109 | data &= ~OTGSC_IDIE; |
110 | } else if (mask & OTGSC_IDIE) { |
111 | cable->enabled = false; |
112 | } |
113 | } |
114 | |
115 | hw_write(ci, reg: OP_OTGSC, mask: mask | OTGSC_INT_STATUS_BITS, data); |
116 | } |
117 | |
118 | /** |
119 | * ci_otg_role - pick role based on ID pin state |
120 | * @ci: the controller |
121 | */ |
122 | enum ci_role ci_otg_role(struct ci_hdrc *ci) |
123 | { |
124 | enum ci_role role = hw_read_otgsc(ci, OTGSC_ID) |
125 | ? CI_ROLE_GADGET |
126 | : CI_ROLE_HOST; |
127 | |
128 | return role; |
129 | } |
130 | |
131 | void ci_handle_vbus_change(struct ci_hdrc *ci) |
132 | { |
133 | if (!ci->is_otg) { |
134 | if (ci->platdata->flags & CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS) |
135 | usb_gadget_vbus_connect(gadget: &ci->gadget); |
136 | return; |
137 | } |
138 | |
139 | if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active) |
140 | usb_gadget_vbus_connect(gadget: &ci->gadget); |
141 | else if (!hw_read_otgsc(ci, OTGSC_BSV) && ci->vbus_active) |
142 | usb_gadget_vbus_disconnect(gadget: &ci->gadget); |
143 | } |
144 | |
145 | /** |
146 | * hw_wait_vbus_lower_bsv - When we switch to device mode, the vbus value |
147 | * should be lower than OTGSC_BSV before connecting |
148 | * to host. |
149 | * |
150 | * @ci: the controller |
151 | * |
152 | * This function returns an error code if timeout |
153 | */ |
154 | static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci) |
155 | { |
156 | unsigned long elapse = jiffies + msecs_to_jiffies(m: 5000); |
157 | u32 mask = OTGSC_BSV; |
158 | |
159 | while (hw_read_otgsc(ci, mask)) { |
160 | if (time_after(jiffies, elapse)) { |
161 | dev_err(ci->dev, "timeout waiting for %08x in OTGSC\n" , |
162 | mask); |
163 | return -ETIMEDOUT; |
164 | } |
165 | msleep(msecs: 20); |
166 | } |
167 | |
168 | return 0; |
169 | } |
170 | |
171 | void ci_handle_id_switch(struct ci_hdrc *ci) |
172 | { |
173 | enum ci_role role; |
174 | |
175 | mutex_lock(&ci->mutex); |
176 | role = ci_otg_role(ci); |
177 | if (role != ci->role) { |
178 | dev_dbg(ci->dev, "switching from %s to %s\n" , |
179 | ci_role(ci)->name, ci->roles[role]->name); |
180 | |
181 | if (ci->vbus_active && ci->role == CI_ROLE_GADGET) |
182 | /* |
183 | * vbus disconnect event is lost due to role |
184 | * switch occurs during system suspend. |
185 | */ |
186 | usb_gadget_vbus_disconnect(gadget: &ci->gadget); |
187 | |
188 | ci_role_stop(ci); |
189 | |
190 | if (role == CI_ROLE_GADGET && |
191 | IS_ERR(ptr: ci->platdata->vbus_extcon.edev)) |
192 | /* |
193 | * Wait vbus lower than OTGSC_BSV before connecting |
194 | * to host. If connecting status is from an external |
195 | * connector instead of register, we don't need to |
196 | * care vbus on the board, since it will not affect |
197 | * external connector status. |
198 | */ |
199 | hw_wait_vbus_lower_bsv(ci); |
200 | |
201 | ci_role_start(ci, role); |
202 | /* vbus change may have already occurred */ |
203 | if (role == CI_ROLE_GADGET) |
204 | ci_handle_vbus_change(ci); |
205 | } |
206 | mutex_unlock(lock: &ci->mutex); |
207 | } |
208 | /** |
209 | * ci_otg_work - perform otg (vbus/id) event handle |
210 | * @work: work struct |
211 | */ |
212 | static void ci_otg_work(struct work_struct *work) |
213 | { |
214 | struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); |
215 | |
216 | if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) { |
217 | enable_irq(irq: ci->irq); |
218 | return; |
219 | } |
220 | |
221 | pm_runtime_get_sync(dev: ci->dev); |
222 | |
223 | if (ci->id_event) { |
224 | ci->id_event = false; |
225 | ci_handle_id_switch(ci); |
226 | } |
227 | |
228 | if (ci->b_sess_valid_event) { |
229 | ci->b_sess_valid_event = false; |
230 | ci_handle_vbus_change(ci); |
231 | } |
232 | |
233 | pm_runtime_put_sync(dev: ci->dev); |
234 | |
235 | enable_irq(irq: ci->irq); |
236 | } |
237 | |
238 | |
239 | /** |
240 | * ci_hdrc_otg_init - initialize otg struct |
241 | * @ci: the controller |
242 | */ |
243 | int ci_hdrc_otg_init(struct ci_hdrc *ci) |
244 | { |
245 | INIT_WORK(&ci->work, ci_otg_work); |
246 | ci->wq = create_freezable_workqueue("ci_otg" ); |
247 | if (!ci->wq) { |
248 | dev_err(ci->dev, "can't create workqueue\n" ); |
249 | return -ENODEV; |
250 | } |
251 | |
252 | if (ci_otg_is_fsm_mode(ci)) |
253 | return ci_hdrc_otg_fsm_init(ci); |
254 | |
255 | return 0; |
256 | } |
257 | |
258 | /** |
259 | * ci_hdrc_otg_destroy - destroy otg struct |
260 | * @ci: the controller |
261 | */ |
262 | void ci_hdrc_otg_destroy(struct ci_hdrc *ci) |
263 | { |
264 | if (ci->wq) |
265 | destroy_workqueue(wq: ci->wq); |
266 | |
267 | /* Disable all OTG irq and clear status */ |
268 | hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, |
269 | OTGSC_INT_STATUS_BITS); |
270 | if (ci_otg_is_fsm_mode(ci)) |
271 | ci_hdrc_otg_fsm_remove(ci); |
272 | } |
273 | |