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 | ||
d0ed062a CY |
10 | #include "mtu3.h" |
11 | #include "mtu3_dr.h" | |
4aab6ad2 | 12 | #include "mtu3_debug.h" |
d0ed062a CY |
13 | |
14 | #define USB2_PORT 2 | |
15 | #define USB3_PORT 3 | |
16 | ||
6c7b9497 CY |
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 | ||
d0ed062a CY |
22 | static void toggle_opstate(struct ssusb_mtk *ssusb) |
23 | { | |
456244ae ML |
24 | mtu3_setbits(ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION); |
25 | mtu3_setbits(ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN); | |
d0ed062a CY |
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(ibase, SSUSB_U2_CTRL(0)); | |
41 | value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS; | |
42 | mtu3_writel(ibase, SSUSB_U2_CTRL(0), value); | |
43 | ||
44 | /* 2. power on, enable u2 port0 and select its mode */ | |
45 | value = mtu3_readl(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(ibase, SSUSB_U2_CTRL(0), value); | |
50 | } else { | |
51 | /* 1. power off and disable u3 port0 */ | |
52 | value = mtu3_readl(ibase, SSUSB_U3_CTRL(0)); | |
53 | value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS; | |
54 | mtu3_writel(ibase, SSUSB_U3_CTRL(0), value); | |
55 | ||
56 | /* 2. power on, enable u3 port0 and select its mode */ | |
57 | value = mtu3_readl(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(ibase, SSUSB_U3_CTRL(0), 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, true); | |
74 | ||
75 | if (ssusb->otg_switch.is_u3_drd) { | |
76 | ssusb_port0_switch(ssusb, USB3_PORT, true); | |
77 | check_clk = SSUSB_U3_MAC_RST_B_STS; | |
78 | } | |
79 | ||
80 | ssusb_check_clocks(ssusb, 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, false); | |
93 | ||
94 | if (ssusb->otg_switch.is_u3_drd) { | |
95 | ssusb_port0_switch(ssusb, USB3_PORT, false); | |
96 | check_clk = SSUSB_U3_MAC_RST_B_STS; | |
97 | } | |
98 | ||
99 | ssusb_check_clocks(ssusb, check_clk); | |
100 | } | |
101 | ||
102 | int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on) | |
103 | { | |
6c7b9497 | 104 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
d0ed062a CY |
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(vbus); | |
116 | if (ret) { | |
117 | dev_err(ssusb->dev, "vbus regulator enable failed\n"); | |
118 | return ret; | |
119 | } | |
120 | } else { | |
121 | regulator_disable(vbus); | |
122 | } | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
18cfd7b8 | 127 | static void ssusb_mode_sw_work(struct work_struct *work) |
d0ed062a | 128 | { |
18cfd7b8 CY |
129 | struct otg_switch_mtk *otg_sx = |
130 | container_of(work, struct otg_switch_mtk, dr_work); | |
6c7b9497 | 131 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
d0ed062a | 132 | struct mtu3 *mtu = ssusb->u3d; |
18cfd7b8 CY |
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 | ||
88c6b901 CY |
138 | if (desired_role == USB_ROLE_NONE) { |
139 | /* the default mode is host as probe does */ | |
18cfd7b8 | 140 | desired_role = USB_ROLE_HOST; |
88c6b901 CY |
141 | if (otg_sx->default_role == USB_ROLE_DEVICE) |
142 | desired_role = USB_ROLE_DEVICE; | |
143 | } | |
d0ed062a | 144 | |
18cfd7b8 CY |
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(ssusb->dev, "set role : %s", usb_role_string(desired_role)); | |
6b587394 | 150 | pm_runtime_get_sync(ssusb->dev); |
d0ed062a | 151 | |
18cfd7b8 | 152 | switch (desired_role) { |
a04c9f2d | 153 | case USB_ROLE_HOST: |
13862176 | 154 | ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_HOST); |
ae634f93 | 155 | mtu3_stop(mtu); |
d0ed062a CY |
156 | switch_port_to_host(ssusb); |
157 | ssusb_set_vbus(otg_sx, 1); | |
158 | ssusb->is_host = true; | |
159 | break; | |
a04c9f2d | 160 | case USB_ROLE_DEVICE: |
13862176 | 161 | ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_DEVICE); |
d0ed062a CY |
162 | ssusb->is_host = false; |
163 | ssusb_set_vbus(otg_sx, 0); | |
164 | switch_port_to_device(ssusb); | |
d0ed062a CY |
165 | mtu3_start(mtu); |
166 | break; | |
a04c9f2d | 167 | case USB_ROLE_NONE: |
d0ed062a | 168 | default: |
a04c9f2d | 169 | dev_err(ssusb->dev, "invalid role\n"); |
d0ed062a | 170 | } |
6b587394 | 171 | pm_runtime_put(ssusb->dev); |
d0ed062a CY |
172 | } |
173 | ||
18cfd7b8 | 174 | static void ssusb_set_mode(struct otg_switch_mtk *otg_sx, enum usb_role role) |
d0ed062a | 175 | { |
6c7b9497 | 176 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
d0ed062a | 177 | |
18cfd7b8 CY |
178 | if (ssusb->dr_mode != USB_DR_MODE_OTG) |
179 | return; | |
180 | ||
181 | otg_sx->desired_role = role; | |
182 | queue_work(system_freezable_wq, &otg_sx->dr_work); | |
681e9485 CY |
183 | } |
184 | ||
681e9485 CY |
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 | ||
18cfd7b8 | 191 | ssusb_set_mode(otg_sx, event ? USB_ROLE_HOST : USB_ROLE_DEVICE); |
d0ed062a CY |
192 | |
193 | return NOTIFY_DONE; | |
194 | } | |
195 | ||
d0ed062a CY |
196 | static int ssusb_extcon_register(struct otg_switch_mtk *otg_sx) |
197 | { | |
6c7b9497 | 198 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
d0ed062a CY |
199 | struct extcon_dev *edev = otg_sx->edev; |
200 | int ret; | |
201 | ||
202 | /* extcon is optional */ | |
203 | if (!edev) | |
204 | return 0; | |
205 | ||
d0ed062a | 206 | otg_sx->id_nb.notifier_call = ssusb_id_notifier; |
a2cfed43 | 207 | ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB_HOST, |
d0ed062a | 208 | &otg_sx->id_nb); |
03d8bfc1 | 209 | if (ret < 0) { |
d0ed062a | 210 | dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n"); |
03d8bfc1 CY |
211 | return ret; |
212 | } | |
d0ed062a | 213 | |
18cfd7b8 CY |
214 | ret = extcon_get_state(edev, EXTCON_USB_HOST); |
215 | dev_dbg(ssusb->dev, "EXTCON_USB_HOST: %d\n", ret); | |
d0ed062a CY |
216 | |
217 | /* default as host, switch to device mode if needed */ | |
18cfd7b8 CY |
218 | if (!ret) |
219 | ssusb_set_mode(otg_sx, USB_ROLE_DEVICE); | |
d0ed062a CY |
220 | |
221 | return 0; | |
222 | } | |
223 | ||
d0ed062a CY |
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. | |
d0ed062a | 229 | */ |
1ac91ac5 | 230 | void ssusb_mode_switch(struct ssusb_mtk *ssusb, int to_host) |
d0ed062a CY |
231 | { |
232 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; | |
233 | ||
13862176 | 234 | ssusb_set_mode(otg_sx, to_host ? USB_ROLE_HOST : USB_ROLE_DEVICE); |
d0ed062a CY |
235 | } |
236 | ||
c776f2c3 CY |
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(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(ssusb->ippc_base, SSUSB_U2_CTRL(0), value); | |
258 | } | |
259 | ||
bce3052f | 260 | static int ssusb_role_sw_set(struct usb_role_switch *sw, enum usb_role role) |
1ac91ac5 | 261 | { |
bce3052f | 262 | struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); |
bfce43c4 | 263 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
1ac91ac5 | 264 | |
bfce43c4 | 265 | ssusb_set_mode(otg_sx, role); |
1ac91ac5 CY |
266 | |
267 | return 0; | |
268 | } | |
269 | ||
bce3052f | 270 | static enum usb_role ssusb_role_sw_get(struct usb_role_switch *sw) |
1ac91ac5 | 271 | { |
bce3052f | 272 | struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); |
1ac91ac5 | 273 | |
bfce43c4 | 274 | return ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; |
1ac91ac5 CY |
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 }; | |
6c7b9497 | 280 | struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); |
88c6b901 CY |
281 | struct device *dev = ssusb->dev; |
282 | enum usb_dr_mode mode; | |
1ac91ac5 CY |
283 | |
284 | if (!otg_sx->role_sw_used) | |
285 | return 0; | |
286 | ||
88c6b901 CY |
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 | ||
1ac91ac5 CY |
293 | role_sx_desc.set = ssusb_role_sw_set; |
294 | role_sx_desc.get = ssusb_role_sw_get; | |
88c6b901 | 295 | role_sx_desc.fwnode = dev_fwnode(dev); |
bce3052f | 296 | role_sx_desc.driver_data = ssusb; |
976a5c25 | 297 | role_sx_desc.allow_userspace_control = true; |
88c6b901 CY |
298 | otg_sx->role_sw = usb_role_switch_register(dev, &role_sx_desc); |
299 | if (IS_ERR(otg_sx->role_sw)) | |
300 | return PTR_ERR(otg_sx->role_sw); | |
1ac91ac5 | 301 | |
88c6b901 CY |
302 | ssusb_set_mode(otg_sx, otg_sx->default_role); |
303 | ||
304 | return 0; | |
1ac91ac5 CY |
305 | } |
306 | ||
d0ed062a CY |
307 | int ssusb_otg_switch_init(struct ssusb_mtk *ssusb) |
308 | { | |
309 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; | |
03d8bfc1 | 310 | int ret = 0; |
d0ed062a | 311 | |
18cfd7b8 | 312 | INIT_WORK(&otg_sx->dr_work, ssusb_mode_sw_work); |
681e9485 | 313 | |
11254eb2 | 314 | if (otg_sx->manual_drd_enabled) |
4aab6ad2 | 315 | ssusb_dr_debugfs_init(ssusb); |
1ac91ac5 CY |
316 | else if (otg_sx->role_sw_used) |
317 | ret = ssusb_role_sw_register(otg_sx); | |
11254eb2 | 318 | else |
03d8bfc1 | 319 | ret = ssusb_extcon_register(otg_sx); |
d0ed062a | 320 | |
03d8bfc1 | 321 | return ret; |
d0ed062a CY |
322 | } |
323 | ||
324 | void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb) | |
325 | { | |
326 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; | |
327 | ||
18cfd7b8 | 328 | cancel_work_sync(&otg_sx->dr_work); |
1ac91ac5 | 329 | usb_role_switch_unregister(otg_sx->role_sw); |
d0ed062a | 330 | } |