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