Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Roger Quadros | 1942 | 94.46% | 2 | 33.33% |
Andrzej Hajda | 111 | 5.40% | 1 | 16.67% |
Greg Kroah-Hartman | 2 | 0.10% | 2 | 33.33% |
Fengguang Wu | 1 | 0.05% | 1 | 16.67% |
Total | 2056 | 6 |
// SPDX-License-Identifier: GPL-2.0 /** * drd.c - DesignWare USB3 DRD Controller Dual-role support * * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com * * Authors: Roger Quadros <rogerq@ti.com> */ #include <linux/extcon.h> #include <linux/of_graph.h> #include <linux/platform_device.h> #include "debug.h" #include "core.h" #include "gadget.h" static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask) { u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN); reg &= ~(disable_mask); dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); } static void dwc3_otg_enable_events(struct dwc3 *dwc, u32 enable_mask) { u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN); reg |= (enable_mask); dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); } static void dwc3_otg_clear_events(struct dwc3 *dwc) { u32 reg = dwc3_readl(dwc->regs, DWC3_OEVT); dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); } #define DWC3_OTG_ALL_EVENTS (DWC3_OEVTEN_XHCIRUNSTPSETEN | \ DWC3_OEVTEN_DEVRUNSTPSETEN | DWC3_OEVTEN_HIBENTRYEN | \ DWC3_OEVTEN_CONIDSTSCHNGEN | DWC3_OEVTEN_HRRCONFNOTIFEN | \ DWC3_OEVTEN_HRRINITNOTIFEN | DWC3_OEVTEN_ADEVIDLEEN | \ DWC3_OEVTEN_ADEVBHOSTENDEN | DWC3_OEVTEN_ADEVHOSTEN | \ DWC3_OEVTEN_ADEVHNPCHNGEN | DWC3_OEVTEN_ADEVSRPDETEN | \ DWC3_OEVTEN_ADEVSESSENDDETEN | DWC3_OEVTEN_BDEVBHOSTENDEN | \ DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVSESSVLDDETEN | \ DWC3_OEVTEN_BDEVVBUSCHNGEN) static irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc) { struct dwc3 *dwc = _dwc; spin_lock(&dwc->lock); if (dwc->otg_restart_host) { dwc3_otg_host_init(dwc); dwc->otg_restart_host = 0; } spin_unlock(&dwc->lock); dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); return IRQ_HANDLED; } static irqreturn_t dwc3_otg_irq(int irq, void *_dwc) { u32 reg; struct dwc3 *dwc = _dwc; irqreturn_t ret = IRQ_NONE; reg = dwc3_readl(dwc->regs, DWC3_OEVT); if (reg) { /* ignore non OTG events, we can't disable them in OEVTEN */ if (!(reg & DWC3_OTG_ALL_EVENTS)) { dwc3_writel(dwc->regs, DWC3_OEVT, reg); return IRQ_NONE; } if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST && !(reg & DWC3_OEVT_DEVICEMODE)) dwc->otg_restart_host = 1; dwc3_writel(dwc->regs, DWC3_OEVT, reg); ret = IRQ_WAKE_THREAD; } return ret; } static void dwc3_otgregs_init(struct dwc3 *dwc) { u32 reg; /* * Prevent host/device reset from resetting OTG core. * If we don't do this then xhci_reset (USBCMD.HCRST) will reset * the signal outputs sent to the PHY, the OTG FSM logic of the * core and also the resets to the VBUS filters inside the core. */ reg = dwc3_readl(dwc->regs, DWC3_OCFG); reg |= DWC3_OCFG_SFTRSTMASK; dwc3_writel(dwc->regs, DWC3_OCFG, reg); /* Disable hibernation for simplicity */ reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg &= ~DWC3_GCTL_GBLHIBERNATIONEN; dwc3_writel(dwc->regs, DWC3_GCTL, reg); /* * Initialize OTG registers as per * Figure 11-4 OTG Driver Overall Programming Flow */ /* OCFG.SRPCap = 0, OCFG.HNPCap = 0 */ reg = dwc3_readl(dwc->regs, DWC3_OCFG); reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP); dwc3_writel(dwc->regs, DWC3_OCFG, reg); /* OEVT = FFFF */ dwc3_otg_clear_events(dwc); /* OEVTEN = 0 */ dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); /* OEVTEN.ConIDStsChngEn = 1. Instead we enable all events */ dwc3_otg_enable_events(dwc, DWC3_OTG_ALL_EVENTS); /* * OCTL.PeriMode = 1, OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0, * OCTL.HNPReq = 0 */ reg = dwc3_readl(dwc->regs, DWC3_OCTL); reg |= DWC3_OCTL_PERIMODE; reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN | DWC3_OCTL_HNPREQ); dwc3_writel(dwc->regs, DWC3_OCTL, reg); } static int dwc3_otg_get_irq(struct dwc3 *dwc) { struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); int irq; irq = platform_get_irq_byname(dwc3_pdev, "otg"); if (irq > 0) goto out; if (irq == -EPROBE_DEFER) goto out; irq = platform_get_irq_byname(dwc3_pdev, "dwc_usb3"); if (irq > 0) goto out; if (irq == -EPROBE_DEFER) goto out; irq = platform_get_irq(dwc3_pdev, 0); if (irq > 0) goto out; if (irq != -EPROBE_DEFER) dev_err(dwc->dev, "missing OTG IRQ\n"); if (!irq) irq = -EINVAL; out: return irq; } void dwc3_otg_init(struct dwc3 *dwc) { u32 reg; /* * As per Figure 11-4 OTG Driver Overall Programming Flow, * block "Initialize GCTL for OTG operation". */ /* GCTL.PrtCapDir=2'b11 */ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG); /* GUSB2PHYCFG0.SusPHY=0 */ reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); /* Initialize OTG registers */ dwc3_otgregs_init(dwc); } void dwc3_otg_exit(struct dwc3 *dwc) { /* disable all OTG IRQs */ dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); /* clear all events */ dwc3_otg_clear_events(dwc); } /* should be called before Host controller driver is started */ void dwc3_otg_host_init(struct dwc3 *dwc) { u32 reg; /* As per Figure 11-10 A-Device Flow Diagram */ /* OCFG.HNPCap = 0, OCFG.SRPCap = 0. Already 0 */ /* * OCTL.PeriMode=0, OCTL.TermSelDLPulse = 0, * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0 */ reg = dwc3_readl(dwc->regs, DWC3_OCTL); reg &= ~(DWC3_OCTL_PERIMODE | DWC3_OCTL_TERMSELIDPULSE | DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN); dwc3_writel(dwc->regs, DWC3_OCTL, reg); /* * OCFG.DisPrtPwrCutoff = 0/1 */ reg = dwc3_readl(dwc->regs, DWC3_OCFG); reg &= ~DWC3_OCFG_DISPWRCUTTOFF; dwc3_writel(dwc->regs, DWC3_OCFG, reg); /* * OCFG.SRPCap = 1, OCFG.HNPCap = GHWPARAMS6.HNP_CAP * We don't want SRP/HNP for simple dual-role so leave * these disabled. */ /* * OEVTEN.OTGADevHostEvntEn = 1 * OEVTEN.OTGADevSessEndDetEvntEn = 1 * We don't want HNP/role-swap so leave these disabled. */ /* GUSB2PHYCFG.ULPIAutoRes = 1/0, GUSB2PHYCFG.SusPHY = 1 */ if (!dwc->dis_u2_susphy_quirk) { reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); reg |= DWC3_GUSB2PHYCFG_SUSPHY; dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); } /* Set Port Power to enable VBUS: OCTL.PrtPwrCtl = 1 */ reg = dwc3_readl(dwc->regs, DWC3_OCTL); reg |= DWC3_OCTL_PRTPWRCTL; dwc3_writel(dwc->regs, DWC3_OCTL, reg); } /* should be called after Host controller driver is stopped */ static void dwc3_otg_host_exit(struct dwc3 *dwc) { u32 reg; /* * Exit from A-device flow as per * Figure 11-4 OTG Driver Overall Programming Flow */ /* * OEVTEN.OTGADevBHostEndEvntEn=0, OEVTEN.OTGADevHNPChngEvntEn=0 * OEVTEN.OTGADevSessEndDetEvntEn=0, * OEVTEN.OTGADevHostEvntEn = 0 * But we don't disable any OTG events */ /* OCTL.HstSetHNPEn = 0, OCTL.PrtPwrCtl=0 */ reg = dwc3_readl(dwc->regs, DWC3_OCTL); reg &= ~(DWC3_OCTL_HSTSETHNPEN | DWC3_OCTL_PRTPWRCTL); dwc3_writel(dwc->regs, DWC3_OCTL, reg); } /* should be called before the gadget controller driver is started */ static void dwc3_otg_device_init(struct dwc3 *dwc) { u32 reg; /* As per Figure 11-20 B-Device Flow Diagram */ /* * OCFG.HNPCap = GHWPARAMS6.HNP_CAP, OCFG.SRPCap = 1 * but we keep them 0 for simple dual-role operation. */ reg = dwc3_readl(dwc->regs, DWC3_OCFG); /* OCFG.OTGSftRstMsk = 0/1 */ reg |= DWC3_OCFG_SFTRSTMASK; dwc3_writel(dwc->regs, DWC3_OCFG, reg); /* * OCTL.PeriMode = 1 * OCTL.TermSelDLPulse = 0/1, OCTL.HNPReq = 0 * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0 */ reg = dwc3_readl(dwc->regs, DWC3_OCTL); reg |= DWC3_OCTL_PERIMODE; reg &= ~(DWC3_OCTL_TERMSELIDPULSE | DWC3_OCTL_HNPREQ | DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN); dwc3_writel(dwc->regs, DWC3_OCTL, reg); /* OEVTEN.OTGBDevSesVldDetEvntEn = 1 */ dwc3_otg_enable_events(dwc, DWC3_OEVTEN_BDEVSESSVLDDETEN); /* GUSB2PHYCFG.ULPIAutoRes = 0, GUSB2PHYCFG0.SusPHY = 1 */ if (!dwc->dis_u2_susphy_quirk) { reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); reg |= DWC3_GUSB2PHYCFG_SUSPHY; dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); } /* GCTL.GblHibernationEn = 0. Already 0. */ } /* should be called after the gadget controller driver is stopped */ static void dwc3_otg_device_exit(struct dwc3 *dwc) { u32 reg; /* * Exit from B-device flow as per * Figure 11-4 OTG Driver Overall Programming Flow */ /* * OEVTEN.OTGBDevHNPChngEvntEn = 0 * OEVTEN.OTGBDevVBusChngEvntEn = 0 * OEVTEN.OTGBDevBHostEndEvntEn = 0 */ dwc3_otg_disable_events(dwc, DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVVBUSCHNGEN | DWC3_OEVTEN_BDEVBHOSTENDEN); /* OCTL.DevSetHNPEn = 0, OCTL.HNPReq = 0, OCTL.PeriMode=1 */ reg = dwc3_readl(dwc->regs, DWC3_OCTL); reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HNPREQ); reg |= DWC3_OCTL_PERIMODE; dwc3_writel(dwc->regs, DWC3_OCTL, reg); } void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) { int ret; u32 reg; int id; unsigned long flags; if (dwc->dr_mode != USB_DR_MODE_OTG) return; /* don't do anything if debug user changed role to not OTG */ if (dwc->current_dr_role != DWC3_GCTL_PRTCAP_OTG) return; if (!ignore_idstatus) { reg = dwc3_readl(dwc->regs, DWC3_OSTS); id = !!(reg & DWC3_OSTS_CONIDSTS); dwc->desired_otg_role = id ? DWC3_OTG_ROLE_DEVICE : DWC3_OTG_ROLE_HOST; } if (dwc->desired_otg_role == dwc->current_otg_role) return; switch (dwc->current_otg_role) { case DWC3_OTG_ROLE_HOST: dwc3_host_exit(dwc); spin_lock_irqsave(&dwc->lock, flags); dwc3_otg_host_exit(dwc); spin_unlock_irqrestore(&dwc->lock, flags); break; case DWC3_OTG_ROLE_DEVICE: dwc3_gadget_exit(dwc); spin_lock_irqsave(&dwc->lock, flags); dwc3_event_buffers_cleanup(dwc); dwc3_otg_device_exit(dwc); spin_unlock_irqrestore(&dwc->lock, flags); break; default: break; } spin_lock_irqsave(&dwc->lock, flags); dwc->current_otg_role = dwc->desired_otg_role; spin_unlock_irqrestore(&dwc->lock, flags); switch (dwc->desired_otg_role) { case DWC3_OTG_ROLE_HOST: spin_lock_irqsave(&dwc->lock, flags); dwc3_otgregs_init(dwc); dwc3_otg_host_init(dwc); spin_unlock_irqrestore(&dwc->lock, flags); ret = dwc3_host_init(dwc); if (ret) { dev_err(dwc->dev, "failed to initialize host\n"); } else { if (dwc->usb2_phy) otg_set_vbus(dwc->usb2_phy->otg, true); if (dwc->usb2_generic_phy) phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST); } break; case DWC3_OTG_ROLE_DEVICE: spin_lock_irqsave(&dwc->lock, flags); dwc3_otgregs_init(dwc); dwc3_otg_device_init(dwc); dwc3_event_buffers_setup(dwc); spin_unlock_irqrestore(&dwc->lock, flags); if (dwc->usb2_phy) otg_set_vbus(dwc->usb2_phy->otg, false); if (dwc->usb2_generic_phy) phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_DEVICE); ret = dwc3_gadget_init(dwc); if (ret) dev_err(dwc->dev, "failed to initialize peripheral\n"); break; default: break; } } static void dwc3_drd_update(struct dwc3 *dwc) { int id; if (dwc->edev) { 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; } static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) { struct device *dev = dwc->dev; struct device_node *np_phy, *np_conn; struct extcon_dev *edev; if (of_property_read_bool(dev->of_node, "extcon")) return extcon_get_edev_by_phandle(dwc->dev, 0); np_phy = of_parse_phandle(dev->of_node, "phys", 0); np_conn = of_graph_get_remote_node(np_phy, -1, -1); if (np_conn) edev = extcon_find_edev_by_node(np_conn); else edev = NULL; of_node_put(np_conn); of_node_put(np_phy); return edev; } int dwc3_drd_init(struct dwc3 *dwc) { int ret, irq; dwc->edev = dwc3_get_extcon(dwc); if (IS_ERR(dwc->edev)) return PTR_ERR(dwc->edev); if (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); } else { dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG); dwc->current_dr_role = DWC3_GCTL_PRTCAP_OTG; /* use OTG block to get ID event */ irq = dwc3_otg_get_irq(dwc); if (irq < 0) return irq; dwc->otg_irq = irq; /* disable all OTG IRQs */ dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); /* clear all events */ dwc3_otg_clear_events(dwc); ret = request_threaded_irq(dwc->otg_irq, dwc3_otg_irq, dwc3_otg_thread_irq, IRQF_SHARED, "dwc3-otg", dwc); if (ret) { dev_err(dwc->dev, "failed to request irq #%d --> %d\n", dwc->otg_irq, ret); ret = -ENODEV; return ret; } dwc3_otg_init(dwc); dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); } return 0; } void dwc3_drd_exit(struct dwc3 *dwc) { unsigned long flags; if (dwc->edev) extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, &dwc->edev_nb); cancel_work_sync(&dwc->drd_work); /* debug user might have changed role, clean based on current role */ switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_HOST: dwc3_host_exit(dwc); break; case DWC3_GCTL_PRTCAP_DEVICE: dwc3_gadget_exit(dwc); dwc3_event_buffers_cleanup(dwc); break; case DWC3_GCTL_PRTCAP_OTG: dwc3_otg_exit(dwc); spin_lock_irqsave(&dwc->lock, flags); dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE; spin_unlock_irqrestore(&dwc->lock, flags); dwc3_otg_update(dwc, 1); break; default: break; } if (!dwc->edev) free_irq(dwc->otg_irq, dwc); }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1