Commit 17f93402 authored by Amelie Delaunay's avatar Amelie Delaunay Committed by Felipe Balbi

usb: dwc2: override PHY input signals with usb role switch support

This patch adds support for usb role switch to dwc2, by using overriding
control of the PHY voltage valid and ID input signals.

iddig signal (ID) can be overridden:
- when setting GUSBCFG_FORCEHOSTMODE, iddig input pin is overridden with 1;
- when setting GUSBCFG_FORCEDEVMODE, iddig input pin is overridden with 0.

avalid/bvalid/vbusvalid signals can be overridden respectively with:
- GOTGCTL_AVALOEN + GOTGCTL_AVALOVAL
- GOTGCTL_BVALOEN + GOTGCTL_BVALOVAL
- GOTGCTL_VBVALEN + GOTGCTL_VBVALOVAL

It is possible to determine valid sessions thanks to usb role switch:
- if USB_ROLE_NONE then !avalid && !bvalid && !vbusvalid
- if USB_ROLE_DEVICE then !avalid && bvalid && vbusvalid
- if USB_ROLE_HOST then avalid && !bvalid && vbusvalid
Acked-by: default avatarMinas Harutyunyan <hminas@synopsys.com>
Acked-by: default avatarMartin Blumenstingl <martin.blumenstingl@googlemail.com>
Signed-off-by: default avatarAmelie Delaunay <amelie.delaunay@st.com>
Signed-off-by: default avatarFelipe Balbi <balbi@kernel.org>
parent 14793fae
...@@ -5,6 +5,7 @@ config USB_DWC2 ...@@ -5,6 +5,7 @@ config USB_DWC2
depends on HAS_DMA depends on HAS_DMA
depends on USB || USB_GADGET depends on USB || USB_GADGET
depends on HAS_IOMEM depends on HAS_IOMEM
select USB_ROLE_SWITCH
help help
Say Y here if your system has a Dual Role Hi-Speed USB Say Y here if your system has a Dual Role Hi-Speed USB
controller based on the DesignWare HSOTG IP Core. controller based on the DesignWare HSOTG IP Core.
......
...@@ -3,7 +3,7 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG ...@@ -3,7 +3,7 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG
ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG
obj-$(CONFIG_USB_DWC2) += dwc2.o obj-$(CONFIG_USB_DWC2) += dwc2.o
dwc2-y := core.o core_intr.o platform.o dwc2-y := core.o core_intr.o platform.o drd.o
dwc2-y += params.o dwc2-y += params.o
ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),) ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),)
......
...@@ -860,6 +860,7 @@ struct dwc2_hregs_backup { ...@@ -860,6 +860,7 @@ struct dwc2_hregs_backup {
* - USB_DR_MODE_PERIPHERAL * - USB_DR_MODE_PERIPHERAL
* - USB_DR_MODE_HOST * - USB_DR_MODE_HOST
* - USB_DR_MODE_OTG * - USB_DR_MODE_OTG
* @role_sw: usb_role_switch handle
* @hcd_enabled: Host mode sub-driver initialization indicator. * @hcd_enabled: Host mode sub-driver initialization indicator.
* @gadget_enabled: Peripheral mode sub-driver initialization indicator. * @gadget_enabled: Peripheral mode sub-driver initialization indicator.
* @ll_hw_enabled: Status of low-level hardware resources. * @ll_hw_enabled: Status of low-level hardware resources.
...@@ -1054,6 +1055,7 @@ struct dwc2_hsotg { ...@@ -1054,6 +1055,7 @@ struct dwc2_hsotg {
struct dwc2_core_params params; struct dwc2_core_params params;
enum usb_otg_state op_state; enum usb_otg_state op_state;
enum usb_dr_mode dr_mode; enum usb_dr_mode dr_mode;
struct usb_role_switch *role_sw;
unsigned int hcd_enabled:1; unsigned int hcd_enabled:1;
unsigned int gadget_enabled:1; unsigned int gadget_enabled:1;
unsigned int ll_hw_enabled:1; unsigned int ll_hw_enabled:1;
...@@ -1376,6 +1378,11 @@ static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) ...@@ -1376,6 +1378,11 @@ static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg)
return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0; return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0;
} }
int dwc2_drd_init(struct dwc2_hsotg *hsotg);
void dwc2_drd_suspend(struct dwc2_hsotg *hsotg);
void dwc2_drd_resume(struct dwc2_hsotg *hsotg);
void dwc2_drd_exit(struct dwc2_hsotg *hsotg);
/* /*
* Dump core registers and SPRAM * Dump core registers and SPRAM
*/ */
...@@ -1392,6 +1399,7 @@ int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2); ...@@ -1392,6 +1399,7 @@ int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2);
int dwc2_gadget_init(struct dwc2_hsotg *hsotg); int dwc2_gadget_init(struct dwc2_hsotg *hsotg);
void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2,
bool reset); bool reset);
void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg);
void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg); void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg);
void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2); void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2);
int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode); int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode);
...@@ -1417,6 +1425,7 @@ static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg) ...@@ -1417,6 +1425,7 @@ static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg)
{ return 0; } { return 0; }
static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2,
bool reset) {} bool reset) {}
static inline void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) {}
static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {}
static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {} static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {}
static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg,
......
// SPDX-License-Identifier: GPL-2.0
/*
* drd.c - DesignWare USB2 DRD Controller Dual-role support
*
* Copyright (C) 2020 STMicroelectronics
*
* Author(s): Amelie Delaunay <amelie.delaunay@st.com>
*/
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/usb/role.h>
#include "core.h"
static void dwc2_ovr_init(struct dwc2_hsotg *hsotg)
{
unsigned long flags;
u32 gotgctl;
spin_lock_irqsave(&hsotg->lock, flags);
gotgctl = dwc2_readl(hsotg, GOTGCTL);
gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN;
gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
dwc2_writel(hsotg, gotgctl, GOTGCTL);
dwc2_force_mode(hsotg, false);
spin_unlock_irqrestore(&hsotg->lock, flags);
}
static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid)
{
u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
/* Check if A-Session is already in the right state */
if ((valid && (gotgctl & GOTGCTL_ASESVLD)) ||
(!valid && !(gotgctl & GOTGCTL_ASESVLD)))
return -EALREADY;
if (valid)
gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL;
else
gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
dwc2_writel(hsotg, gotgctl, GOTGCTL);
return 0;
}
static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid)
{
u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
/* Check if B-Session is already in the right state */
if ((valid && (gotgctl & GOTGCTL_BSESVLD)) ||
(!valid && !(gotgctl & GOTGCTL_BSESVLD)))
return -EALREADY;
if (valid)
gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL;
else
gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL);
dwc2_writel(hsotg, gotgctl, GOTGCTL);
return 0;
}
static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
{
struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw);
unsigned long flags;
int already = 0;
/* Skip session not in line with dr_mode */
if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) ||
(role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL))
return -EINVAL;
#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
/* Skip session if core is in test mode */
if (role == USB_ROLE_NONE && hsotg->test_mode) {
dev_dbg(hsotg->dev, "Core is in test mode\n");
return -EBUSY;
}
#endif
spin_lock_irqsave(&hsotg->lock, flags);
if (role == USB_ROLE_HOST) {
already = dwc2_ovr_avalid(hsotg, true);
} else if (role == USB_ROLE_DEVICE) {
already = dwc2_ovr_bvalid(hsotg, true);
/* This clear DCTL.SFTDISCON bit */
dwc2_hsotg_core_connect(hsotg);
} else {
if (dwc2_is_device_mode(hsotg)) {
if (!dwc2_ovr_bvalid(hsotg, false))
/* This set DCTL.SFTDISCON bit */
dwc2_hsotg_core_disconnect(hsotg);
} else {
dwc2_ovr_avalid(hsotg, false);
}
}
spin_unlock_irqrestore(&hsotg->lock, flags);
if (!already && hsotg->dr_mode == USB_DR_MODE_OTG)
/* This will raise a Connector ID Status Change Interrupt */
dwc2_force_mode(hsotg, role == USB_ROLE_HOST);
dev_dbg(hsotg->dev, "%s-session valid\n",
role == USB_ROLE_NONE ? "No" :
role == USB_ROLE_HOST ? "A" : "B");
return 0;
}
int dwc2_drd_init(struct dwc2_hsotg *hsotg)
{
struct usb_role_switch_desc role_sw_desc = {0};
struct usb_role_switch *role_sw;
int ret;
if (!device_property_read_bool(hsotg->dev, "usb-role-switch"))
return 0;
role_sw_desc.driver_data = hsotg;
role_sw_desc.fwnode = dev_fwnode(hsotg->dev);
role_sw_desc.set = dwc2_drd_role_sw_set;
role_sw_desc.allow_userspace_control = true;
role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc);
if (IS_ERR(role_sw)) {
ret = PTR_ERR(role_sw);
dev_err(hsotg->dev,
"failed to register role switch: %d\n", ret);
return ret;
}
hsotg->role_sw = role_sw;
/* Enable override and initialize values */
dwc2_ovr_init(hsotg);
return 0;
}
void dwc2_drd_suspend(struct dwc2_hsotg *hsotg)
{
u32 gintsts, gintmsk;
if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
gintmsk = dwc2_readl(hsotg, GINTMSK);
gintmsk &= ~GINTSTS_CONIDSTSCHNG;
dwc2_writel(hsotg, gintmsk, GINTMSK);
gintsts = dwc2_readl(hsotg, GINTSTS);
dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
}
}
void dwc2_drd_resume(struct dwc2_hsotg *hsotg)
{
u32 gintsts, gintmsk;
if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
gintsts = dwc2_readl(hsotg, GINTSTS);
dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
gintmsk = dwc2_readl(hsotg, GINTMSK);
gintmsk |= GINTSTS_CONIDSTSCHNG;
dwc2_writel(hsotg, gintmsk, GINTMSK);
}
}
void dwc2_drd_exit(struct dwc2_hsotg *hsotg)
{
if (hsotg->role_sw)
usb_role_switch_unregister(hsotg->role_sw);
}
...@@ -3530,7 +3530,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, ...@@ -3530,7 +3530,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg,
dwc2_readl(hsotg, DOEPCTL0)); dwc2_readl(hsotg, DOEPCTL0));
} }
static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg)
{ {
/* set the soft-disconnect bit */ /* set the soft-disconnect bit */
dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON); dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON);
......
...@@ -323,6 +323,8 @@ static int dwc2_driver_remove(struct platform_device *dev) ...@@ -323,6 +323,8 @@ static int dwc2_driver_remove(struct platform_device *dev)
if (hsotg->gadget_enabled) if (hsotg->gadget_enabled)
dwc2_hsotg_remove(hsotg); dwc2_hsotg_remove(hsotg);
dwc2_drd_exit(hsotg);
if (hsotg->params.activate_stm_id_vb_detection) if (hsotg->params.activate_stm_id_vb_detection)
regulator_disable(hsotg->usb33d); regulator_disable(hsotg->usb33d);
...@@ -542,10 +544,17 @@ static int dwc2_driver_probe(struct platform_device *dev) ...@@ -542,10 +544,17 @@ static int dwc2_driver_probe(struct platform_device *dev)
dwc2_writel(hsotg, ggpio, GGPIO); dwc2_writel(hsotg, ggpio, GGPIO);
} }
retval = dwc2_drd_init(hsotg);
if (retval) {
if (retval != -EPROBE_DEFER)
dev_err(hsotg->dev, "failed to initialize dual-role\n");
goto error_init;
}
if (hsotg->dr_mode != USB_DR_MODE_HOST) { if (hsotg->dr_mode != USB_DR_MODE_HOST) {
retval = dwc2_gadget_init(hsotg); retval = dwc2_gadget_init(hsotg);
if (retval) if (retval)
goto error_init; goto error_drd;
hsotg->gadget_enabled = 1; hsotg->gadget_enabled = 1;
} }
...@@ -571,7 +580,7 @@ static int dwc2_driver_probe(struct platform_device *dev) ...@@ -571,7 +580,7 @@ static int dwc2_driver_probe(struct platform_device *dev)
if (retval) { if (retval) {
if (hsotg->gadget_enabled) if (hsotg->gadget_enabled)
dwc2_hsotg_remove(hsotg); dwc2_hsotg_remove(hsotg);
goto error_init; goto error_drd;
} }
hsotg->hcd_enabled = 1; hsotg->hcd_enabled = 1;
} }
...@@ -603,6 +612,9 @@ static int dwc2_driver_probe(struct platform_device *dev) ...@@ -603,6 +612,9 @@ static int dwc2_driver_probe(struct platform_device *dev)
dwc2_debugfs_exit(hsotg); dwc2_debugfs_exit(hsotg);
if (hsotg->hcd_enabled) if (hsotg->hcd_enabled)
dwc2_hcd_remove(hsotg); dwc2_hcd_remove(hsotg);
error_drd:
dwc2_drd_exit(hsotg);
error_init: error_init:
if (hsotg->params.activate_stm_id_vb_detection) if (hsotg->params.activate_stm_id_vb_detection)
regulator_disable(hsotg->usb33d); regulator_disable(hsotg->usb33d);
...@@ -621,6 +633,8 @@ static int __maybe_unused dwc2_suspend(struct device *dev) ...@@ -621,6 +633,8 @@ static int __maybe_unused dwc2_suspend(struct device *dev)
if (is_device_mode) if (is_device_mode)
dwc2_hsotg_suspend(dwc2); dwc2_hsotg_suspend(dwc2);
dwc2_drd_suspend(dwc2);
if (dwc2->params.activate_stm_id_vb_detection) { if (dwc2->params.activate_stm_id_vb_detection) {
unsigned long flags; unsigned long flags;
u32 ggpio, gotgctl; u32 ggpio, gotgctl;
...@@ -701,6 +715,8 @@ static int __maybe_unused dwc2_resume(struct device *dev) ...@@ -701,6 +715,8 @@ static int __maybe_unused dwc2_resume(struct device *dev)
/* Need to restore FORCEDEVMODE/FORCEHOSTMODE */ /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */
dwc2_force_dr_mode(dwc2); dwc2_force_dr_mode(dwc2);
dwc2_drd_resume(dwc2);
if (dwc2_is_device_mode(dwc2)) if (dwc2_is_device_mode(dwc2))
ret = dwc2_hsotg_resume(dwc2); ret = dwc2_hsotg_resume(dwc2);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment