1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * mtu3_dr.c - dual role switch and host glue layer |
4 | * |
5 | * Copyright (C) 2016 MediaTek Inc. |
6 | * |
7 | * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> |
8 | */ |
9 | |
10 | #include "mtu3.h" |
11 | #include "mtu3_dr.h" |
12 | #include "mtu3_debug.h" |
13 | |
14 | #define USB2_PORT 2 |
15 | #define USB3_PORT 3 |
16 | |
17 | static inline struct ssusb_mtk *otg_sx_to_ssusb(struct otg_switch_mtk *otg_sx) |
18 | { |
19 | return container_of(otg_sx, struct ssusb_mtk, otg_switch); |
20 | } |
21 | |
22 | static void toggle_opstate(struct ssusb_mtk *ssusb) |
23 | { |
24 | mtu3_setbits(base: ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION); |
25 | mtu3_setbits(base: ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN); |
26 | } |
27 | |
28 | /* only port0 supports dual-role mode */ |
29 | static int ssusb_port0_switch(struct ssusb_mtk *ssusb, |
30 | int version, bool tohost) |
31 | { |
32 | void __iomem *ibase = ssusb->ippc_base; |
33 | u32 value; |
34 | |
35 | dev_dbg(ssusb->dev, "%s (switch u%d port0 to %s)\n" , __func__, |
36 | version, tohost ? "host" : "device" ); |
37 | |
38 | if (version == USB2_PORT) { |
39 | /* 1. power off and disable u2 port0 */ |
40 | value = mtu3_readl(base: ibase, SSUSB_U2_CTRL(0)); |
41 | value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS; |
42 | mtu3_writel(base: ibase, SSUSB_U2_CTRL(0), data: value); |
43 | |
44 | /* 2. power on, enable u2 port0 and select its mode */ |
45 | value = mtu3_readl(base: ibase, SSUSB_U2_CTRL(0)); |
46 | value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS); |
47 | value = tohost ? (value | SSUSB_U2_PORT_HOST_SEL) : |
48 | (value & (~SSUSB_U2_PORT_HOST_SEL)); |
49 | mtu3_writel(base: ibase, SSUSB_U2_CTRL(0), data: value); |
50 | } else { |
51 | /* 1. power off and disable u3 port0 */ |
52 | value = mtu3_readl(base: ibase, SSUSB_U3_CTRL(0)); |
53 | value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS; |
54 | mtu3_writel(base: ibase, SSUSB_U3_CTRL(0), data: value); |
55 | |
56 | /* 2. power on, enable u3 port0 and select its mode */ |
57 | value = mtu3_readl(base: ibase, SSUSB_U3_CTRL(0)); |
58 | value &= ~(SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS); |
59 | value = tohost ? (value | SSUSB_U3_PORT_HOST_SEL) : |
60 | (value & (~SSUSB_U3_PORT_HOST_SEL)); |
61 | mtu3_writel(base: ibase, SSUSB_U3_CTRL(0), data: value); |
62 | } |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static void switch_port_to_host(struct ssusb_mtk *ssusb) |
68 | { |
69 | u32 check_clk = 0; |
70 | |
71 | dev_dbg(ssusb->dev, "%s\n" , __func__); |
72 | |
73 | ssusb_port0_switch(ssusb, USB2_PORT, tohost: true); |
74 | |
75 | if (ssusb->otg_switch.is_u3_drd) { |
76 | ssusb_port0_switch(ssusb, USB3_PORT, tohost: true); |
77 | check_clk = SSUSB_U3_MAC_RST_B_STS; |
78 | } |
79 | |
80 | ssusb_check_clocks(ssusb, ex_clks: check_clk); |
81 | |
82 | /* after all clocks are stable */ |
83 | toggle_opstate(ssusb); |
84 | } |
85 | |
86 | static void switch_port_to_device(struct ssusb_mtk *ssusb) |
87 | { |
88 | u32 check_clk = 0; |
89 | |
90 | dev_dbg(ssusb->dev, "%s\n" , __func__); |
91 | |
92 | ssusb_port0_switch(ssusb, USB2_PORT, tohost: false); |
93 | |
94 | if (ssusb->otg_switch.is_u3_drd) { |
95 | ssusb_port0_switch(ssusb, USB3_PORT, tohost: false); |
96 | check_clk = SSUSB_U3_MAC_RST_B_STS; |
97 | } |
98 | |
99 | ssusb_check_clocks(ssusb, ex_clks: check_clk); |
100 | } |
101 | |
102 | int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on) |
103 | { |
104 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
105 | struct regulator *vbus = otg_sx->vbus; |
106 | int ret; |
107 | |
108 | /* vbus is optional */ |
109 | if (!vbus) |
110 | return 0; |
111 | |
112 | dev_dbg(ssusb->dev, "%s: turn %s\n" , __func__, is_on ? "on" : "off" ); |
113 | |
114 | if (is_on) { |
115 | ret = regulator_enable(regulator: vbus); |
116 | if (ret) { |
117 | dev_err(ssusb->dev, "vbus regulator enable failed\n" ); |
118 | return ret; |
119 | } |
120 | } else { |
121 | regulator_disable(regulator: vbus); |
122 | } |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | static void ssusb_mode_sw_work(struct work_struct *work) |
128 | { |
129 | struct otg_switch_mtk *otg_sx = |
130 | container_of(work, struct otg_switch_mtk, dr_work); |
131 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
132 | struct mtu3 *mtu = ssusb->u3d; |
133 | enum usb_role desired_role = otg_sx->desired_role; |
134 | enum usb_role current_role; |
135 | |
136 | current_role = ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; |
137 | |
138 | if (desired_role == USB_ROLE_NONE) { |
139 | /* the default mode is host as probe does */ |
140 | desired_role = USB_ROLE_HOST; |
141 | if (otg_sx->default_role == USB_ROLE_DEVICE) |
142 | desired_role = USB_ROLE_DEVICE; |
143 | } |
144 | |
145 | if (current_role == desired_role) |
146 | return; |
147 | |
148 | dev_dbg(ssusb->dev, "set role : %s\n" , usb_role_string(desired_role)); |
149 | mtu3_dbg_trace(dev: ssusb->dev, fmt: "set role : %s" , usb_role_string(role: desired_role)); |
150 | pm_runtime_get_sync(dev: ssusb->dev); |
151 | |
152 | switch (desired_role) { |
153 | case USB_ROLE_HOST: |
154 | ssusb_set_force_mode(ssusb, mode: MTU3_DR_FORCE_HOST); |
155 | mtu3_stop(mtu); |
156 | switch_port_to_host(ssusb); |
157 | ssusb_set_vbus(otg_sx, is_on: 1); |
158 | ssusb->is_host = true; |
159 | break; |
160 | case USB_ROLE_DEVICE: |
161 | ssusb_set_force_mode(ssusb, mode: MTU3_DR_FORCE_DEVICE); |
162 | ssusb->is_host = false; |
163 | ssusb_set_vbus(otg_sx, is_on: 0); |
164 | switch_port_to_device(ssusb); |
165 | mtu3_start(mtu); |
166 | break; |
167 | case USB_ROLE_NONE: |
168 | default: |
169 | dev_err(ssusb->dev, "invalid role\n" ); |
170 | } |
171 | pm_runtime_put(dev: ssusb->dev); |
172 | } |
173 | |
174 | static void ssusb_set_mode(struct otg_switch_mtk *otg_sx, enum usb_role role) |
175 | { |
176 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
177 | |
178 | if (ssusb->dr_mode != USB_DR_MODE_OTG) |
179 | return; |
180 | |
181 | otg_sx->desired_role = role; |
182 | queue_work(wq: system_freezable_wq, work: &otg_sx->dr_work); |
183 | } |
184 | |
185 | static int ssusb_id_notifier(struct notifier_block *nb, |
186 | unsigned long event, void *ptr) |
187 | { |
188 | struct otg_switch_mtk *otg_sx = |
189 | container_of(nb, struct otg_switch_mtk, id_nb); |
190 | |
191 | ssusb_set_mode(otg_sx, role: event ? USB_ROLE_HOST : USB_ROLE_DEVICE); |
192 | |
193 | return NOTIFY_DONE; |
194 | } |
195 | |
196 | static int ssusb_extcon_register(struct otg_switch_mtk *otg_sx) |
197 | { |
198 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
199 | struct extcon_dev *edev = otg_sx->edev; |
200 | int ret; |
201 | |
202 | /* extcon is optional */ |
203 | if (!edev) |
204 | return 0; |
205 | |
206 | otg_sx->id_nb.notifier_call = ssusb_id_notifier; |
207 | ret = devm_extcon_register_notifier(dev: ssusb->dev, edev, EXTCON_USB_HOST, |
208 | nb: &otg_sx->id_nb); |
209 | if (ret < 0) { |
210 | dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n" ); |
211 | return ret; |
212 | } |
213 | |
214 | ret = extcon_get_state(edev, EXTCON_USB_HOST); |
215 | dev_dbg(ssusb->dev, "EXTCON_USB_HOST: %d\n" , ret); |
216 | |
217 | /* default as host, switch to device mode if needed */ |
218 | if (!ret) |
219 | ssusb_set_mode(otg_sx, role: USB_ROLE_DEVICE); |
220 | |
221 | return 0; |
222 | } |
223 | |
224 | /* |
225 | * We provide an interface via debugfs to switch between host and device modes |
226 | * depending on user input. |
227 | * This is useful in special cases, such as uses TYPE-A receptacle but also |
228 | * wants to support dual-role mode. |
229 | */ |
230 | void ssusb_mode_switch(struct ssusb_mtk *ssusb, int to_host) |
231 | { |
232 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
233 | |
234 | ssusb_set_mode(otg_sx, role: to_host ? USB_ROLE_HOST : USB_ROLE_DEVICE); |
235 | } |
236 | |
237 | void ssusb_set_force_mode(struct ssusb_mtk *ssusb, |
238 | enum mtu3_dr_force_mode mode) |
239 | { |
240 | u32 value; |
241 | |
242 | value = mtu3_readl(base: ssusb->ippc_base, SSUSB_U2_CTRL(0)); |
243 | switch (mode) { |
244 | case MTU3_DR_FORCE_DEVICE: |
245 | value |= SSUSB_U2_PORT_FORCE_IDDIG | SSUSB_U2_PORT_RG_IDDIG; |
246 | break; |
247 | case MTU3_DR_FORCE_HOST: |
248 | value |= SSUSB_U2_PORT_FORCE_IDDIG; |
249 | value &= ~SSUSB_U2_PORT_RG_IDDIG; |
250 | break; |
251 | case MTU3_DR_FORCE_NONE: |
252 | value &= ~(SSUSB_U2_PORT_FORCE_IDDIG | SSUSB_U2_PORT_RG_IDDIG); |
253 | break; |
254 | default: |
255 | return; |
256 | } |
257 | mtu3_writel(base: ssusb->ippc_base, SSUSB_U2_CTRL(0), data: value); |
258 | } |
259 | |
260 | static int ssusb_role_sw_set(struct usb_role_switch *sw, enum usb_role role) |
261 | { |
262 | struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); |
263 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
264 | |
265 | ssusb_set_mode(otg_sx, role); |
266 | |
267 | return 0; |
268 | } |
269 | |
270 | static enum usb_role ssusb_role_sw_get(struct usb_role_switch *sw) |
271 | { |
272 | struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); |
273 | |
274 | return ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; |
275 | } |
276 | |
277 | static int ssusb_role_sw_register(struct otg_switch_mtk *otg_sx) |
278 | { |
279 | struct usb_role_switch_desc role_sx_desc = { 0 }; |
280 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
281 | struct device *dev = ssusb->dev; |
282 | enum usb_dr_mode mode; |
283 | |
284 | if (!otg_sx->role_sw_used) |
285 | return 0; |
286 | |
287 | mode = usb_get_role_switch_default_mode(dev); |
288 | if (mode == USB_DR_MODE_PERIPHERAL) |
289 | otg_sx->default_role = USB_ROLE_DEVICE; |
290 | else |
291 | otg_sx->default_role = USB_ROLE_HOST; |
292 | |
293 | role_sx_desc.set = ssusb_role_sw_set; |
294 | role_sx_desc.get = ssusb_role_sw_get; |
295 | role_sx_desc.fwnode = dev_fwnode(dev); |
296 | role_sx_desc.driver_data = ssusb; |
297 | role_sx_desc.allow_userspace_control = true; |
298 | otg_sx->role_sw = usb_role_switch_register(parent: dev, desc: &role_sx_desc); |
299 | if (IS_ERR(ptr: otg_sx->role_sw)) |
300 | return PTR_ERR(ptr: otg_sx->role_sw); |
301 | |
302 | ssusb_set_mode(otg_sx, role: otg_sx->default_role); |
303 | |
304 | return 0; |
305 | } |
306 | |
307 | int ssusb_otg_switch_init(struct ssusb_mtk *ssusb) |
308 | { |
309 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
310 | int ret = 0; |
311 | |
312 | INIT_WORK(&otg_sx->dr_work, ssusb_mode_sw_work); |
313 | |
314 | if (otg_sx->manual_drd_enabled) |
315 | ssusb_dr_debugfs_init(ssusb); |
316 | else if (otg_sx->role_sw_used) |
317 | ret = ssusb_role_sw_register(otg_sx); |
318 | else |
319 | ret = ssusb_extcon_register(otg_sx); |
320 | |
321 | return ret; |
322 | } |
323 | |
324 | void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb) |
325 | { |
326 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
327 | |
328 | cancel_work_sync(work: &otg_sx->dr_work); |
329 | usb_role_switch_unregister(sw: otg_sx->role_sw); |
330 | } |
331 | |