.. | .. |
---|
7 | 7 | * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> |
---|
8 | 8 | */ |
---|
9 | 9 | |
---|
10 | | -#include <linux/debugfs.h> |
---|
11 | | -#include <linux/irq.h> |
---|
12 | | -#include <linux/kernel.h> |
---|
13 | | -#include <linux/of_device.h> |
---|
14 | | -#include <linux/pinctrl/consumer.h> |
---|
15 | | -#include <linux/seq_file.h> |
---|
16 | | -#include <linux/uaccess.h> |
---|
| 10 | +#include <linux/usb/role.h> |
---|
17 | 11 | |
---|
18 | 12 | #include "mtu3.h" |
---|
19 | 13 | #include "mtu3_dr.h" |
---|
| 14 | +#include "mtu3_debug.h" |
---|
20 | 15 | |
---|
21 | 16 | #define USB2_PORT 2 |
---|
22 | 17 | #define USB3_PORT 3 |
---|
.. | .. |
---|
28 | 23 | MTU3_VBUS_VALID, |
---|
29 | 24 | }; |
---|
30 | 25 | |
---|
| 26 | +static char *mailbox_state_string(enum mtu3_vbus_id_state state) |
---|
| 27 | +{ |
---|
| 28 | + switch (state) { |
---|
| 29 | + case MTU3_ID_FLOAT: |
---|
| 30 | + return "ID_FLOAT"; |
---|
| 31 | + case MTU3_ID_GROUND: |
---|
| 32 | + return "ID_GROUND"; |
---|
| 33 | + case MTU3_VBUS_OFF: |
---|
| 34 | + return "VBUS_OFF"; |
---|
| 35 | + case MTU3_VBUS_VALID: |
---|
| 36 | + return "VBUS_VALID"; |
---|
| 37 | + default: |
---|
| 38 | + return "UNKNOWN"; |
---|
| 39 | + } |
---|
| 40 | +} |
---|
| 41 | + |
---|
31 | 42 | static void toggle_opstate(struct ssusb_mtk *ssusb) |
---|
32 | 43 | { |
---|
33 | | - if (!ssusb->otg_switch.is_u3_drd) { |
---|
34 | | - mtu3_setbits(ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION); |
---|
35 | | - mtu3_setbits(ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN); |
---|
36 | | - } |
---|
| 44 | + mtu3_setbits(ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION); |
---|
| 45 | + mtu3_setbits(ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN); |
---|
37 | 46 | } |
---|
38 | 47 | |
---|
39 | 48 | /* only port0 supports dual-role mode */ |
---|
.. | .. |
---|
147 | 156 | container_of(otg_sx, struct ssusb_mtk, otg_switch); |
---|
148 | 157 | struct mtu3 *mtu = ssusb->u3d; |
---|
149 | 158 | |
---|
150 | | - dev_dbg(ssusb->dev, "mailbox state(%d)\n", status); |
---|
| 159 | + dev_dbg(ssusb->dev, "mailbox %s\n", mailbox_state_string(status)); |
---|
| 160 | + mtu3_dbg_trace(ssusb->dev, "mailbox %s", mailbox_state_string(status)); |
---|
151 | 161 | |
---|
152 | 162 | switch (status) { |
---|
153 | 163 | case MTU3_ID_GROUND: |
---|
.. | .. |
---|
238 | 248 | otg_sx->vbus_nb.notifier_call = ssusb_vbus_notifier; |
---|
239 | 249 | ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB, |
---|
240 | 250 | &otg_sx->vbus_nb); |
---|
241 | | - if (ret < 0) |
---|
| 251 | + if (ret < 0) { |
---|
242 | 252 | dev_err(ssusb->dev, "failed to register notifier for USB\n"); |
---|
| 253 | + return ret; |
---|
| 254 | + } |
---|
243 | 255 | |
---|
244 | 256 | otg_sx->id_nb.notifier_call = ssusb_id_notifier; |
---|
245 | 257 | ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB_HOST, |
---|
246 | 258 | &otg_sx->id_nb); |
---|
247 | | - if (ret < 0) |
---|
| 259 | + if (ret < 0) { |
---|
248 | 260 | dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n"); |
---|
| 261 | + return ret; |
---|
| 262 | + } |
---|
249 | 263 | |
---|
250 | 264 | dev_dbg(ssusb->dev, "EXTCON_USB: %d, EXTCON_USB_HOST: %d\n", |
---|
251 | 265 | extcon_get_state(edev, EXTCON_USB), |
---|
.. | .. |
---|
266 | 280 | * This is useful in special cases, such as uses TYPE-A receptacle but also |
---|
267 | 281 | * wants to support dual-role mode. |
---|
268 | 282 | */ |
---|
269 | | -static void ssusb_mode_manual_switch(struct ssusb_mtk *ssusb, int to_host) |
---|
| 283 | +void ssusb_mode_switch(struct ssusb_mtk *ssusb, int to_host) |
---|
270 | 284 | { |
---|
271 | 285 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
---|
272 | 286 | |
---|
.. | .. |
---|
279 | 293 | ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT); |
---|
280 | 294 | ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID); |
---|
281 | 295 | } |
---|
282 | | -} |
---|
283 | | - |
---|
284 | | -static int ssusb_mode_show(struct seq_file *sf, void *unused) |
---|
285 | | -{ |
---|
286 | | - struct ssusb_mtk *ssusb = sf->private; |
---|
287 | | - |
---|
288 | | - seq_printf(sf, "current mode: %s(%s drd)\n(echo device/host)\n", |
---|
289 | | - ssusb->is_host ? "host" : "device", |
---|
290 | | - ssusb->otg_switch.manual_drd_enabled ? "manual" : "auto"); |
---|
291 | | - |
---|
292 | | - return 0; |
---|
293 | | -} |
---|
294 | | - |
---|
295 | | -static int ssusb_mode_open(struct inode *inode, struct file *file) |
---|
296 | | -{ |
---|
297 | | - return single_open(file, ssusb_mode_show, inode->i_private); |
---|
298 | | -} |
---|
299 | | - |
---|
300 | | -static ssize_t ssusb_mode_write(struct file *file, |
---|
301 | | - const char __user *ubuf, size_t count, loff_t *ppos) |
---|
302 | | -{ |
---|
303 | | - struct seq_file *sf = file->private_data; |
---|
304 | | - struct ssusb_mtk *ssusb = sf->private; |
---|
305 | | - char buf[16]; |
---|
306 | | - |
---|
307 | | - if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) |
---|
308 | | - return -EFAULT; |
---|
309 | | - |
---|
310 | | - if (!strncmp(buf, "host", 4) && !ssusb->is_host) { |
---|
311 | | - ssusb_mode_manual_switch(ssusb, 1); |
---|
312 | | - } else if (!strncmp(buf, "device", 6) && ssusb->is_host) { |
---|
313 | | - ssusb_mode_manual_switch(ssusb, 0); |
---|
314 | | - } else { |
---|
315 | | - dev_err(ssusb->dev, "wrong or duplicated setting\n"); |
---|
316 | | - return -EINVAL; |
---|
317 | | - } |
---|
318 | | - |
---|
319 | | - return count; |
---|
320 | | -} |
---|
321 | | - |
---|
322 | | -static const struct file_operations ssusb_mode_fops = { |
---|
323 | | - .open = ssusb_mode_open, |
---|
324 | | - .write = ssusb_mode_write, |
---|
325 | | - .read = seq_read, |
---|
326 | | - .llseek = seq_lseek, |
---|
327 | | - .release = single_release, |
---|
328 | | -}; |
---|
329 | | - |
---|
330 | | -static int ssusb_vbus_show(struct seq_file *sf, void *unused) |
---|
331 | | -{ |
---|
332 | | - struct ssusb_mtk *ssusb = sf->private; |
---|
333 | | - struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
---|
334 | | - |
---|
335 | | - seq_printf(sf, "vbus state: %s\n(echo on/off)\n", |
---|
336 | | - regulator_is_enabled(otg_sx->vbus) ? "on" : "off"); |
---|
337 | | - |
---|
338 | | - return 0; |
---|
339 | | -} |
---|
340 | | - |
---|
341 | | -static int ssusb_vbus_open(struct inode *inode, struct file *file) |
---|
342 | | -{ |
---|
343 | | - return single_open(file, ssusb_vbus_show, inode->i_private); |
---|
344 | | -} |
---|
345 | | - |
---|
346 | | -static ssize_t ssusb_vbus_write(struct file *file, |
---|
347 | | - const char __user *ubuf, size_t count, loff_t *ppos) |
---|
348 | | -{ |
---|
349 | | - struct seq_file *sf = file->private_data; |
---|
350 | | - struct ssusb_mtk *ssusb = sf->private; |
---|
351 | | - struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
---|
352 | | - char buf[16]; |
---|
353 | | - bool enable; |
---|
354 | | - |
---|
355 | | - if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) |
---|
356 | | - return -EFAULT; |
---|
357 | | - |
---|
358 | | - if (kstrtobool(buf, &enable)) { |
---|
359 | | - dev_err(ssusb->dev, "wrong setting\n"); |
---|
360 | | - return -EINVAL; |
---|
361 | | - } |
---|
362 | | - |
---|
363 | | - ssusb_set_vbus(otg_sx, enable); |
---|
364 | | - |
---|
365 | | - return count; |
---|
366 | | -} |
---|
367 | | - |
---|
368 | | -static const struct file_operations ssusb_vbus_fops = { |
---|
369 | | - .open = ssusb_vbus_open, |
---|
370 | | - .write = ssusb_vbus_write, |
---|
371 | | - .read = seq_read, |
---|
372 | | - .llseek = seq_lseek, |
---|
373 | | - .release = single_release, |
---|
374 | | -}; |
---|
375 | | - |
---|
376 | | -static void ssusb_debugfs_init(struct ssusb_mtk *ssusb) |
---|
377 | | -{ |
---|
378 | | - struct dentry *root; |
---|
379 | | - |
---|
380 | | - root = debugfs_create_dir(dev_name(ssusb->dev), usb_debug_root); |
---|
381 | | - ssusb->dbgfs_root = root; |
---|
382 | | - |
---|
383 | | - debugfs_create_file("mode", 0644, root, ssusb, &ssusb_mode_fops); |
---|
384 | | - debugfs_create_file("vbus", 0644, root, ssusb, &ssusb_vbus_fops); |
---|
385 | | -} |
---|
386 | | - |
---|
387 | | -static void ssusb_debugfs_exit(struct ssusb_mtk *ssusb) |
---|
388 | | -{ |
---|
389 | | - debugfs_remove_recursive(ssusb->dbgfs_root); |
---|
390 | 296 | } |
---|
391 | 297 | |
---|
392 | 298 | void ssusb_set_force_mode(struct ssusb_mtk *ssusb, |
---|
.. | .. |
---|
412 | 318 | mtu3_writel(ssusb->ippc_base, SSUSB_U2_CTRL(0), value); |
---|
413 | 319 | } |
---|
414 | 320 | |
---|
| 321 | +static int ssusb_role_sw_set(struct usb_role_switch *sw, enum usb_role role) |
---|
| 322 | +{ |
---|
| 323 | + struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); |
---|
| 324 | + bool to_host = false; |
---|
| 325 | + |
---|
| 326 | + if (role == USB_ROLE_HOST) |
---|
| 327 | + to_host = true; |
---|
| 328 | + |
---|
| 329 | + if (to_host ^ ssusb->is_host) |
---|
| 330 | + ssusb_mode_switch(ssusb, to_host); |
---|
| 331 | + |
---|
| 332 | + return 0; |
---|
| 333 | +} |
---|
| 334 | + |
---|
| 335 | +static enum usb_role ssusb_role_sw_get(struct usb_role_switch *sw) |
---|
| 336 | +{ |
---|
| 337 | + struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); |
---|
| 338 | + enum usb_role role; |
---|
| 339 | + |
---|
| 340 | + role = ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; |
---|
| 341 | + |
---|
| 342 | + return role; |
---|
| 343 | +} |
---|
| 344 | + |
---|
| 345 | +static int ssusb_role_sw_register(struct otg_switch_mtk *otg_sx) |
---|
| 346 | +{ |
---|
| 347 | + struct usb_role_switch_desc role_sx_desc = { 0 }; |
---|
| 348 | + struct ssusb_mtk *ssusb = |
---|
| 349 | + container_of(otg_sx, struct ssusb_mtk, otg_switch); |
---|
| 350 | + |
---|
| 351 | + if (!otg_sx->role_sw_used) |
---|
| 352 | + return 0; |
---|
| 353 | + |
---|
| 354 | + role_sx_desc.set = ssusb_role_sw_set; |
---|
| 355 | + role_sx_desc.get = ssusb_role_sw_get; |
---|
| 356 | + role_sx_desc.fwnode = dev_fwnode(ssusb->dev); |
---|
| 357 | + role_sx_desc.driver_data = ssusb; |
---|
| 358 | + otg_sx->role_sw = usb_role_switch_register(ssusb->dev, &role_sx_desc); |
---|
| 359 | + |
---|
| 360 | + return PTR_ERR_OR_ZERO(otg_sx->role_sw); |
---|
| 361 | +} |
---|
| 362 | + |
---|
415 | 363 | int ssusb_otg_switch_init(struct ssusb_mtk *ssusb) |
---|
416 | 364 | { |
---|
417 | 365 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
---|
| 366 | + int ret = 0; |
---|
418 | 367 | |
---|
419 | 368 | INIT_WORK(&otg_sx->id_work, ssusb_id_work); |
---|
420 | 369 | INIT_WORK(&otg_sx->vbus_work, ssusb_vbus_work); |
---|
421 | 370 | |
---|
422 | 371 | if (otg_sx->manual_drd_enabled) |
---|
423 | | - ssusb_debugfs_init(ssusb); |
---|
| 372 | + ssusb_dr_debugfs_init(ssusb); |
---|
| 373 | + else if (otg_sx->role_sw_used) |
---|
| 374 | + ret = ssusb_role_sw_register(otg_sx); |
---|
424 | 375 | else |
---|
425 | | - ssusb_extcon_register(otg_sx); |
---|
| 376 | + ret = ssusb_extcon_register(otg_sx); |
---|
426 | 377 | |
---|
427 | | - return 0; |
---|
| 378 | + return ret; |
---|
428 | 379 | } |
---|
429 | 380 | |
---|
430 | 381 | void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb) |
---|
431 | 382 | { |
---|
432 | 383 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
---|
433 | 384 | |
---|
434 | | - if (otg_sx->manual_drd_enabled) |
---|
435 | | - ssusb_debugfs_exit(ssusb); |
---|
436 | | - |
---|
437 | 385 | cancel_work_sync(&otg_sx->id_work); |
---|
438 | 386 | cancel_work_sync(&otg_sx->vbus_work); |
---|
| 387 | + usb_role_switch_unregister(otg_sx->role_sw); |
---|
439 | 388 | } |
---|