This feature allows to use OTG feature with devices which doesn't support hardware DWC3 OTG. In such situation we can supply own mechanism of cable detection. This code is inspired by DWC3 driver from Hardkernel Linux sources [1]. [1] https://github.com/hardkernel/linux. Signed-off-by: Robert Baldyga <r.baldyga@xxxxxxxxxxx> --- drivers/usb/dwc3/otg.c | 89 ++++++++++++++++++++++++++++++++++++++++---------- drivers/usb/dwc3/otg.h | 48 +++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 17 deletions(-) diff --git a/drivers/usb/dwc3/otg.c b/drivers/usb/dwc3/otg.c index 708ab22..99dfd2c 100644 --- a/drivers/usb/dwc3/otg.c +++ b/drivers/usb/dwc3/otg.c @@ -29,6 +29,11 @@ #include "otg.h" #include "io.h" +static struct dwc3_ext_otg_ops *dwc3_otg_rsw_probe(struct dwc3 *dwc) +{ + return NULL; +} + static void dwc3_otg_set_host_mode(struct dwc3_otg *dotg) { struct dwc3 *dwc = dotg->dwc; @@ -388,25 +393,33 @@ int dwc3_otg_start(struct dwc3 *dwc) if (!dotg) return -ENODEV; - dotg->regs = dwc->regs; + if (dotg->ext_otg_ops) { + ret = dwc3_ext_otg_start(dotg); + if (ret) { + dev_err(dwc->dev, "failed to start external OTG\n"); + return ret; + } + } else { + dotg->regs = dwc->regs; - dwc3_otg_reset(dotg); + dwc3_otg_reset(dotg); - dotg->fsm.id = dwc3_otg_get_id_state(dotg); - dotg->fsm.b_sess_vld = dwc3_otg_get_b_sess_state(dotg); + dotg->fsm.id = dwc3_otg_get_id_state(dotg); + dotg->fsm.b_sess_vld = dwc3_otg_get_b_sess_state(dotg); - dotg->irq = platform_get_irq(to_platform_device(dwc->dev), 0); - ret = devm_request_threaded_irq(dwc->dev, dotg->irq, - dwc3_otg_interrupt, - dwc3_otg_thread_interrupt, - IRQF_SHARED, "dwc3-otg", dotg); - if (ret) { - dev_err(dwc->dev, "failed to request irq #%d --> %d\n", - dotg->irq, ret); - return ret; - } + dotg->irq = platform_get_irq(to_platform_device(dwc->dev), 0); + ret = devm_request_threaded_irq(dwc->dev, dotg->irq, + dwc3_otg_interrupt, + dwc3_otg_thread_interrupt, + IRQF_SHARED, "dwc3-otg", dotg); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + dotg->irq, ret); + return ret; + } - dwc3_otg_enable_irq(dotg); + dwc3_otg_enable_irq(dotg); + } dwc3_otg_run_sm(fsm); @@ -424,8 +437,12 @@ void dwc3_otg_stop(struct dwc3 *dwc) if (!dotg) return; - dwc3_otg_disable_irq(dotg); - free_irq(dotg->irq, dotg); + if (dotg->ext_otg_ops) { + dwc3_ext_otg_stop(dotg); + } else { + dwc3_otg_disable_irq(dotg); + free_irq(dotg->irq, dotg); + } } /** @@ -437,6 +454,7 @@ void dwc3_otg_stop(struct dwc3 *dwc) int dwc3_otg_init(struct dwc3 *dwc) { struct dwc3_otg *dotg; + struct dwc3_ext_otg_ops *ops = NULL; u32 reg; int ret = 0; @@ -447,9 +465,25 @@ int dwc3_otg_init(struct dwc3 *dwc) reg = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6); if (!(reg & DWC3_GHWPARAMS6_SRP_SUPPORT)) { dev_err(dwc->dev, "dwc3_otg address space is not supported\n"); + + /* + * Some SoCs (e.g. Exynos5) don't have HW OTG, however, some + * boards use simplified role switch (rsw) function based on + * ID/BSes gpio interrupts. As a fall-back try to bind to rsw. + */ + ops = dwc3_otg_rsw_probe(dwc); + if (ops) + goto has_ext_otg; + + /* + * No HW OTG support in the core. + * We return 0 to indicate no error, since this is acceptable + * situation, just continue probe the dwc3 driver without otg. + */ return 0; } +has_ext_otg: /* Allocate and init otg instance */ dotg = devm_kzalloc(dwc->dev, sizeof(struct dwc3_otg), GFP_KERNEL); if (!dotg) @@ -459,6 +493,8 @@ int dwc3_otg_init(struct dwc3 *dwc) dwc->dotg = dotg; dotg->dwc = dwc; + dotg->ext_otg_ops = ops; + dotg->otg.set_peripheral = dwc3_otg_set_peripheral; dotg->otg.set_host = NULL; @@ -472,6 +508,14 @@ int dwc3_otg_init(struct dwc3 *dwc) if (IS_ERR(dotg->vbus_reg)) dev_info(dwc->dev, "vbus regulator is not available\n"); + if (dotg->ext_otg_ops) { + ret = dwc3_ext_otg_setup(dotg); + if (ret) { + dev_err(dwc->dev, "failed to setup external OTG\n"); + return ret; + } + } + ret = sysfs_create_group(&dwc->dev->kobj, &dwc3_otg_attr_group); if (ret) dev_err(dwc->dev, "failed to create dwc3 otg attributes\n"); @@ -491,6 +535,17 @@ void dwc3_otg_exit(struct dwc3 *dwc) if (!dotg) return; + reg = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6); + if (!(reg & DWC3_GHWPARAMS6_SRP_SUPPORT)) { + if (dotg->ext_otg_ops) { + dwc3_ext_otg_exit(dotg); + goto has_ext_otg; + } + + return; + } + +has_ext_otg: sysfs_remove_group(&dwc->dev->kobj, &dwc3_otg_attr_group); kfree(dotg); dwc->dotg = NULL; diff --git a/drivers/usb/dwc3/otg.h b/drivers/usb/dwc3/otg.h index ffb8f0e..4be7165 100644 --- a/drivers/usb/dwc3/otg.h +++ b/drivers/usb/dwc3/otg.h @@ -26,6 +26,13 @@ #include "core.h" +struct dwc3_ext_otg_ops { + int (*setup)(struct device *dev, struct otg_fsm *fsm); + void (*exit)(struct device *dev); + int (*start)(struct device *dev); + void (*stop)(struct device *dev); +}; + /** * struct dwc3_otg: OTG driver data. Shared by HCD and DCD. * @otg: USB OTG Transceiver structure. @@ -34,6 +41,7 @@ * @irq: IRQ number assigned for HSUSB controller. * @regs: ioremapped register base address. * @vbus_reg: Vbus regulator. + * @ext_otg_ops: external OTG engine ops. */ struct dwc3_otg { struct usb_otg otg; @@ -43,8 +51,48 @@ struct dwc3_otg { void __iomem *regs; struct regulator *vbus_reg; + + struct dwc3_ext_otg_ops *ext_otg_ops; }; +static inline int dwc3_ext_otg_setup(struct dwc3_otg *dotg) +{ + struct device *dev = dotg->dwc->dev->parent; + + if (!dotg->ext_otg_ops->setup) + return -EOPNOTSUPP; + return dotg->ext_otg_ops->setup(dev, &dotg->fsm); +} + +static inline int dwc3_ext_otg_exit(struct dwc3_otg *dotg) +{ + struct device *dev = dotg->dwc->dev->parent; + + if (!dotg->ext_otg_ops->exit) + return -EOPNOTSUPP; + dotg->ext_otg_ops->exit(dev); + return 0; +} + +static inline int dwc3_ext_otg_start(struct dwc3_otg *dotg) +{ + struct device *dev = dotg->dwc->dev->parent; + + if (!dotg->ext_otg_ops->start) + return -EOPNOTSUPP; + return dotg->ext_otg_ops->start(dev); +} + +static inline int dwc3_ext_otg_stop(struct dwc3_otg *dotg) +{ + struct device *dev = dotg->dwc->dev->parent; + + if (!dotg->ext_otg_ops->stop) + return -EOPNOTSUPP; + dotg->ext_otg_ops->stop(dev); + return 0; +} + int dwc3_otg_start(struct dwc3 *dwc); void dwc3_otg_stop(struct dwc3 *dwc); int dwc3_otg_init(struct dwc3 *dwc); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html