diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index c5aa235863e8..aee55114d269 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -41,6 +41,7 @@ config USB_DWC3_GADGET config USB_DWC3_DUAL_ROLE bool "Dual Role mode" depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3)) + depends on (EXTCON=y || EXTCON=USB_DWC3) help This is the default mode of working of DWC3 controller where both host and gadget features are enabled. diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index ffca34029b21..f15fabbd1e59 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -17,6 +17,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),) dwc3-y += gadget.o ep0.o endif +ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),) + dwc3-y += drd.o +endif + ifneq ($(CONFIG_USB_DWC3_ULPI),) dwc3-y += ulpi.o endif diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 458e7c6cc002..455d89a1cd6d 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -921,7 +921,12 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) break; case USB_DR_MODE_OTG: INIT_WORK(&dwc->drd_work, __dwc3_set_mode); - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + ret = dwc3_drd_init(dwc); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to initialize dual-role\n"); + return ret; + } break; default: dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode); @@ -941,9 +946,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) dwc3_host_exit(dwc); break; case USB_DR_MODE_OTG: - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); - dwc3_gadget_exit(dwc); - flush_work(&dwc->drd_work); + dwc3_drd_exit(dwc); break; default: /* do nothing */ diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 1fe23e36485f..981c77f5628e 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -785,6 +785,8 @@ struct dwc3_scratchpad_array { * @dr_mode: requested mode of operation * @current_dr_role: current role of operation when in dual-role mode * @desired_dr_role: desired role of operation when in dual-role mode + * @edev: extcon handle + * @edev_nb: extcon notifier * @hsphy_mode: UTMI phy mode, one of following: * - USBPHY_INTERFACE_MODE_UTMI * - USBPHY_INTERFACE_MODE_UTMIW @@ -898,6 +900,8 @@ struct dwc3 { enum usb_dr_mode dr_mode; u32 current_dr_role; u32 desired_dr_role; + struct extcon_dev *edev; + struct notifier_block edev_nb; enum usb_phy_interface hsphy_mode; u32 fladj; @@ -1218,6 +1222,16 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, { return 0; } #endif +#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_drd_init(struct dwc3 *dwc); +void dwc3_drd_exit(struct dwc3 *dwc); +#else +static inline int dwc3_drd_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_drd_exit(struct dwc3 *dwc) +{ } +#endif + /* power management interface */ #if !IS_ENABLED(CONFIG_USB_DWC3_HOST) int dwc3_gadget_suspend(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c new file mode 100644 index 000000000000..2765c51c7ef5 --- /dev/null +++ b/drivers/usb/dwc3/drd.c @@ -0,0 +1,85 @@ +/** + * drd.c - DesignWare USB3 DRD Controller Dual-role support + * + * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Roger Quadros + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "debug.h" +#include "core.h" +#include "gadget.h" + +static void dwc3_drd_update(struct dwc3 *dwc) +{ + int id; + + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + if (id < 0) + id = 0; + + dwc3_set_mode(dwc, id ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); +} + +static int dwc3_drd_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb); + + dwc3_set_mode(dwc, event ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); + + return NOTIFY_DONE; +} + +int dwc3_drd_init(struct dwc3 *dwc) +{ + int ret; + + if (dwc->dev->of_node) { + if (of_property_read_bool(dwc->dev->of_node, "extcon")) + dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0); + + if (IS_ERR(dwc->edev)) + return PTR_ERR(dwc->edev); + + dwc->edev_nb.notifier_call = dwc3_drd_notifier; + ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + if (ret < 0) { + dev_err(dwc->dev, "couldn't register cable notifier\n"); + return ret; + } + } + + dwc3_drd_update(dwc); + + return 0; +} + +void dwc3_drd_exit(struct dwc3 *dwc) +{ + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + flush_work(&dwc->drd_work); + dwc3_gadget_exit(dwc); +}