/** * otg.c - DesignWare USB3 DRD Controller OTG * * Copyright (c) 2012, Code Aurora Forum. All rights reserved. * Copyright (c) 2013 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Authors: Ido Shayevitz * Anton Tikhomirov * * 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. */ #include #include #include #include #include #include #include "core.h" #include "otg.h" #include "io.h" #if IS_ENABLED(CONFIG_USB_DWC3_EXYNOS) static struct dwc3_ext_otg_ops *dwc3_otg_exynos_rsw_probe(struct dwc3 *dwc) { struct dwc3_ext_otg_ops *ops; bool ext_otg; ext_otg = dwc3_exynos_rsw_available(dwc->dev->parent); if (!ext_otg) return NULL; /* Allocate and init otg instance */ ops = devm_kzalloc(dwc->dev, sizeof(struct dwc3_ext_otg_ops), GFP_KERNEL); if (!ops) { dev_err(dwc->dev, "unable to allocate dwc3_ext_otg_ops\n"); return NULL; } ops->setup = dwc3_exynos_rsw_setup; ops->exit = dwc3_exynos_rsw_exit; ops->start = dwc3_exynos_rsw_start; ops->stop = dwc3_exynos_rsw_stop; return ops; } #else static struct dwc3_ext_otg_ops *dwc3_otg_exynos_rsw_probe(dwc) { return NULL; } #endif static int dwc3_otg_statemachine(struct otg_fsm *fsm) { struct usb_phy *phy = fsm->otg->phy; enum usb_otg_state prev_state = phy->state; int ret = 0; if (fsm->reset) { if (phy->state == OTG_STATE_A_HOST) { otg_drv_vbus(fsm, 0); otg_start_host(fsm, 0); } else if (phy->state == OTG_STATE_B_PERIPHERAL) { otg_start_gadget(fsm, 0); } phy->state = OTG_STATE_UNDEFINED; goto exit; } switch (phy->state) { case OTG_STATE_UNDEFINED: if (fsm->id) phy->state = OTG_STATE_B_IDLE; else phy->state = OTG_STATE_A_IDLE; break; case OTG_STATE_B_IDLE: if (!fsm->id) { phy->state = OTG_STATE_A_IDLE; } else if (fsm->b_sess_vld) { ret = otg_start_gadget(fsm, 1); if (!ret) phy->state = OTG_STATE_B_PERIPHERAL; else pr_err("OTG SM: cannot start gadget\n"); } break; case OTG_STATE_B_PERIPHERAL: if (!fsm->id || !fsm->b_sess_vld) { ret = otg_start_gadget(fsm, 0); if (!ret) phy->state = OTG_STATE_B_IDLE; else pr_err("OTG SM: cannot stop gadget\n"); } break; case OTG_STATE_A_IDLE: if (fsm->id) { phy->state = OTG_STATE_B_IDLE; } else { ret = otg_start_host(fsm, 1); if (!ret) { otg_drv_vbus(fsm, 1); phy->state = OTG_STATE_A_HOST; } else { pr_err("OTG SM: cannot start host\n"); } } break; case OTG_STATE_A_HOST: if (fsm->id) { otg_drv_vbus(fsm, 0); ret = otg_start_host(fsm, 0); if (!ret) phy->state = OTG_STATE_A_IDLE; else pr_err("OTG SM: cannot stop host\n"); } break; default: pr_err("OTG SM: invalid state\n"); } exit: if (!ret) ret = (phy->state != prev_state); if(log_usb) pr_info("OTG SM: %s => %s\n", usb_otg_state_string(prev_state),(ret > 0) ? usb_otg_state_string(phy->state) : "(no change)"); return ret; } static void dwc3_otg_set_host_mode(struct dwc3_otg *dotg) { struct dwc3 *dwc = dotg->dwc; u32 reg; if (dotg->regs) { reg = dwc3_readl(dotg->regs, DWC3_OCTL); reg &= ~DWC3_OTG_OCTL_PERIMODE; dwc3_writel(dotg->regs, DWC3_OCTL, reg); } else { dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); } } static void dwc3_otg_set_peripheral_mode(struct dwc3_otg *dotg) { struct dwc3 *dwc = dotg->dwc; u32 reg; if (dotg->regs) { reg = dwc3_readl(dotg->regs, DWC3_OCTL); reg |= DWC3_OTG_OCTL_PERIMODE; dwc3_writel(dotg->regs, DWC3_OCTL, reg); } else { dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); } } static void dwc3_otg_drv_vbus(struct otg_fsm *fsm, int on) { struct dwc3_otg *dotg = container_of(fsm, struct dwc3_otg, fsm); int ret; /* Regulator is not available */ if (IS_ERR(dotg->vbus_reg)) return; if (on) ret = regulator_enable(dotg->vbus_reg); else ret = regulator_disable(dotg->vbus_reg); if (ret) dev_err(dotg->dwc->dev, "Failed to turn Vbus %s\n", on ? "on" : "off"); } static int dwc3_otg_start_host(struct otg_fsm *fsm, int on) { struct usb_otg *otg = fsm->otg; struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); struct dwc3 *dwc = dotg->dwc; struct device *dev = dotg->dwc->dev; int ret = 0; if (dwc->dr_mode == USB_DR_MODE_PERIPHERAL) { dev_info(dev, "does not support HOST mode\n"); return 0; } if (!dotg->dwc->xhci) return -EINVAL; dev_err(dev, "Turn %s host\n", on ? "on" : "off"); if (on) { wake_lock(&dotg->wakelock); pm_runtime_get_sync(dev); phy_set(dwc->usb2_generic_phy, SET_EXTREFCLK_REQUEST, NULL); phy_set(dwc->usb3_generic_phy, SET_EXTREFCLK_REQUEST, (void *)&ret); if (ret) { phy_set(dwc->usb2_generic_phy, SET_EXTREFCLK_RELEASE, NULL); phy_set(dwc->usb3_generic_phy, SET_EXTREFCLK_RELEASE, NULL); goto err1; } ret = dwc3_core_init(dwc); if (ret) { dev_err(dwc->dev, "%s: failed to reinitialize core\n", __func__); goto err1; } /** * In case there is not a resistance to detect VBUS, * DP/DM controls by S/W are needed at this point. */ if (dwc->is_not_vbus_pad) { phy_set(dwc->usb2_generic_phy, SET_DPDM_PULLDOWN, NULL); phy_set(dwc->usb3_generic_phy, SET_DPDM_PULLDOWN, NULL); } dwc3_otg_set_host_mode(dotg); ret = platform_device_add(dwc->xhci); if (ret) { dev_err(dev, "%s: cannot add xhci\n", __func__); goto err2; } } else { platform_device_del(dwc->xhci); err2: dwc3_core_exit(dwc); err1: pm_runtime_put_sync(dev); wake_unlock(&dotg->wakelock); } return ret; } static int dwc3_otg_start_gadget(struct otg_fsm *fsm, int on) { struct usb_otg *otg = fsm->otg; struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); struct dwc3 *dwc = dotg->dwc; struct device *dev = dotg->dwc->dev; int ret = 0; if (dwc->dr_mode == USB_DR_MODE_HOST) { dev_info(dev, "does not support PERIPHERAL mode\n"); return ret; } if (!otg->gadget) return -EINVAL; dev_err(dev, "Turn %s gadget %s\n", on ? "on" : "off", otg->gadget->name); if (on) { wake_lock(&dotg->wakelock); pm_runtime_get_sync(dev); phy_set(dwc->usb2_generic_phy, SET_EXTREFCLK_REQUEST, NULL); phy_set(dwc->usb3_generic_phy, SET_EXTREFCLK_REQUEST, (void *)&ret); if (ret) { phy_set(dwc->usb2_generic_phy, SET_EXTREFCLK_RELEASE, NULL); phy_set(dwc->usb3_generic_phy, SET_EXTREFCLK_RELEASE, NULL); goto err1; } ret = dwc3_core_init(dwc); if (ret) { dev_err(dwc->dev, "%s: failed to reinitialize core\n", __func__); goto err1; } dwc3_otg_set_peripheral_mode(dotg); ret = usb_gadget_vbus_connect(otg->gadget); if (ret) { dev_err(dwc->dev, "%s: vbus connect failed\n", __func__); goto err2; } } else { if (dwc->is_not_vbus_pad) dwc3_gadget_disconnect_proc(dwc); /* avoid missing disconnect interrupt */ wait_for_completion_timeout(&dwc->disconnect, msecs_to_jiffies(200)); ret = usb_gadget_vbus_disconnect(otg->gadget); if (ret) dev_err(dwc->dev, "%s: vbus disconnect failed\n", __func__); err2: dwc3_core_exit(dwc); err1: pm_runtime_put_sync(dev); wake_unlock(&dotg->wakelock); } return ret; } static struct otg_fsm_ops dwc3_otg_fsm_ops = { .drv_vbus = dwc3_otg_drv_vbus, .start_host = dwc3_otg_start_host, .start_gadget = dwc3_otg_start_gadget, }; void dwc3_otg_run_sm(struct otg_fsm *fsm) { struct dwc3_otg *dotg = container_of(fsm, struct dwc3_otg, fsm); int state_changed; /* Prevent running SM on early system resume */ if (!dotg->ready) return; mutex_lock(&fsm->lock); do { state_changed = dwc3_otg_statemachine(fsm); } while (state_changed > 0); mutex_unlock(&fsm->lock); } /** * dwc3_otg_set_peripheral - bind/unbind the peripheral controller driver. * * Returns 0 on success otherwise negative errno. */ static int dwc3_otg_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) { struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); struct otg_fsm *fsm = &dotg->fsm; struct device *dev = dotg->dwc->dev; if (gadget) { dev_err(dev, "Binding gadget %s\n", gadget->name); otg->gadget = gadget; } else { dev_err(dev, "Unbinding gadget\n"); mutex_lock(&fsm->lock); if (otg->phy->state == OTG_STATE_B_PERIPHERAL) { /* Reset OTG SM */ fsm->reset = 1; dwc3_otg_statemachine(fsm); fsm->reset = 0; } otg->gadget = NULL; mutex_unlock(&fsm->lock); dwc3_otg_run_sm(fsm); } return 0; } static irqreturn_t dwc3_otg_thread_interrupt(int irq, void *_dotg) { struct dwc3_otg *dotg = (struct dwc3_otg *)_dotg; dwc3_otg_run_sm(&dotg->fsm); return IRQ_HANDLED; } static int dwc3_otg_get_id_state(struct dwc3_otg *dotg) { u32 reg = dwc3_readl(dotg->regs, DWC3_OSTS); return !!(reg & DWC3_OTG_OSTS_CONIDSTS); } static int dwc3_otg_get_b_sess_state(struct dwc3_otg *dotg) { u32 reg = dwc3_readl(dotg->regs, DWC3_OSTS); return !!(reg & DWC3_OTG_OSTS_BSESVALID); } /** * dwc3_otg_interrupt - interrupt handler for dwc3 otg events. * * @irq: irq number. * @_dotg: Pointer to dwc3 otg context structure. */ static irqreturn_t dwc3_otg_interrupt(int irq, void *_dotg) { struct dwc3_otg *dotg = (struct dwc3_otg *)_dotg; struct otg_fsm *fsm = &dotg->fsm; u32 oevt, handled_events = 0; irqreturn_t ret = IRQ_NONE; oevt = dwc3_readl(dotg->regs, DWC3_OEVT); /* ID */ if (oevt & DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT) { fsm->id = dwc3_otg_get_id_state(dotg); handled_events |= DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT; } /* VBus */ if (oevt & DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT) { fsm->b_sess_vld = dwc3_otg_get_b_sess_state(dotg); handled_events |= DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT; } if (handled_events) { dwc3_writel(dotg->regs, DWC3_OEVT, handled_events); ret = IRQ_WAKE_THREAD; } return ret; } static void dwc3_otg_enable_irq(struct dwc3_otg *dotg) { /* Enable only connector ID status & VBUS change events */ dwc3_writel(dotg->regs, DWC3_OEVTEN, DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT | DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT); } static void dwc3_otg_disable_irq(struct dwc3_otg *dotg) { dwc3_writel(dotg->regs, DWC3_OEVTEN, 0x0); } /** * dwc3_otg_reset - reset dwc3 otg registers. * * @dotg: Pointer to dwc3 otg context structure. */ static void dwc3_otg_reset(struct dwc3_otg *dotg) { /* * OCFG[2] - OTG-Version = 0 * OCFG[1] - HNPCap = 0 * OCFG[0] - SRPCap = 0 */ dwc3_writel(dotg->regs, DWC3_OCFG, 0x0); /* * OCTL[6] - PeriMode = 1 * OCTL[5] - PrtPwrCtl = 0 * OCTL[4] - HNPReq = 0 * OCTL[3] - SesReq = 0 * OCTL[2] - TermSelDLPulse = 0 * OCTL[1] - DevSetHNPEn = 0 * OCTL[0] - HstSetHNPEn = 0 */ dwc3_writel(dotg->regs, DWC3_OCTL, DWC3_OTG_OCTL_PERIMODE); /* Clear all otg events (interrupts) indications */ dwc3_writel(dotg->regs, DWC3_OEVT, DWC3_OEVT_CLEAR_ALL); } /* SysFS interface */ static ssize_t dwc3_otg_show_state(struct device *dev, struct device_attribute *attr, char *buf) { struct dwc3 *dwc = dev_get_drvdata(dev); struct usb_otg *otg = &dwc->dotg->otg; struct usb_phy *phy = otg->phy; return snprintf(buf, PAGE_SIZE, "%s\n", usb_otg_state_string(phy->state)); } static DEVICE_ATTR(state, S_IRUSR | S_IRGRP, dwc3_otg_show_state, NULL); /* * id and b_sess attributes allow to change DRD mode and B-Session state. * Can be used for debug purpose. */ static ssize_t dwc3_otg_show_b_sess(struct device *dev, struct device_attribute *attr, char *buf) { struct dwc3 *dwc = dev_get_drvdata(dev); struct otg_fsm *fsm = &dwc->dotg->fsm; return snprintf(buf, PAGE_SIZE, "%d\n", fsm->b_sess_vld); } static ssize_t dwc3_otg_store_b_sess(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { struct dwc3 *dwc = dev_get_drvdata(dev); struct otg_fsm *fsm = &dwc->dotg->fsm; int b_sess_vld; if (sscanf(buf, "%d", &b_sess_vld) != 1) return -EINVAL; fsm->b_sess_vld = !!b_sess_vld; dwc3_otg_run_sm(fsm); return n; } static DEVICE_ATTR(b_sess, S_IWUSR | S_IRUSR | S_IRGRP, dwc3_otg_show_b_sess, dwc3_otg_store_b_sess); static ssize_t dwc3_otg_show_id(struct device *dev, struct device_attribute *attr, char *buf) { struct dwc3 *dwc = dev_get_drvdata(dev); struct otg_fsm *fsm = &dwc->dotg->fsm; return snprintf(buf, PAGE_SIZE, "%d\n", fsm->id); } static ssize_t dwc3_otg_store_id(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { struct dwc3 *dwc = dev_get_drvdata(dev); struct otg_fsm *fsm = &dwc->dotg->fsm; int id; if (sscanf(buf, "%d", &id) != 1) return -EINVAL; fsm->id = !!id; dwc3_otg_run_sm(fsm); return n; } static DEVICE_ATTR(id, S_IWUSR | S_IRUSR | S_IRGRP, dwc3_otg_show_id, dwc3_otg_store_id); static struct attribute *dwc3_otg_attributes[] = { &dev_attr_id.attr, &dev_attr_b_sess.attr, &dev_attr_state.attr, NULL }; static const struct attribute_group dwc3_otg_attr_group = { .attrs = dwc3_otg_attributes, }; /** * dwc3_otg_start * @dwc: pointer to our controller context structure */ int dwc3_otg_start(struct dwc3 *dwc) { struct dwc3_otg *dotg = dwc->dotg; struct otg_fsm *fsm = &dotg->fsm; int ret; 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); 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; } dwc3_otg_enable_irq(dotg); } dotg->ready = 1; dwc3_otg_run_sm(fsm); return 0; } /** * dwc3_otg_stop * @dwc: pointer to our controller context structure */ void dwc3_otg_stop(struct dwc3 *dwc) { struct dwc3_otg *dotg = dwc->dotg; if (dotg->ext_otg_ops) { dwc3_ext_otg_stop(dotg); } else { dwc3_otg_disable_irq(dotg); free_irq(dotg->irq, dotg); } dotg->ready = 0; } /** * dwc3_otg_init - Initializes otg related registers * @dwc: pointer to our controller context structure * * Returns 0 on success otherwise negative errno. */ int dwc3_otg_init(struct dwc3 *dwc) { struct dwc3_otg *dotg; struct dwc3_ext_otg_ops *ops = NULL; u32 reg; int ret = 0; /* * GHWPARAMS6[10] bit is SRPSupport. * This bit also reflects DWC_USB3_EN_OTG */ 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"); /* * Exynos5 SoCs 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 Exynos5 * role switch. */ ops = dwc3_otg_exynos_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) { dev_err(dwc->dev, "unable to allocate dwc3_otg\n"); return -ENOMEM; } /* This reference is used by dwc3 modules for checking otg existance */ dwc->dotg = dotg; dotg->dwc = dwc; dotg->ext_otg_ops = ops; dotg->otg.set_peripheral = dwc3_otg_set_peripheral; dotg->otg.set_host = NULL; dotg->otg.phy = dwc->usb3_phy; dotg->otg.phy->otg = &dotg->otg; dotg->otg.phy->state = OTG_STATE_UNDEFINED; mutex_init(&dotg->fsm.lock); dotg->fsm.ops = &dwc3_otg_fsm_ops; dotg->fsm.otg = &dotg->otg; dotg->vbus_reg = devm_regulator_get(dwc->dev->parent, "dwc3-vbus"); 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; } } wake_lock_init(&dotg->wakelock, WAKE_LOCK_SUSPEND, "dwc3-otg"); ret = sysfs_create_group(&dwc->dev->kobj, &dwc3_otg_attr_group); if (ret) dev_err(dwc->dev, "failed to create dwc3 otg attributes\n"); return 0; } /** * dwc3_otg_exit * @dwc: pointer to our controller context structure */ void dwc3_otg_exit(struct dwc3 *dwc) { struct dwc3_otg *dotg = dwc->dotg; u32 reg; 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); wake_lock_destroy(&dotg->wakelock); free_irq(dotg->irq, dotg); dotg->otg.phy->otg = NULL; dotg->otg.phy->state = OTG_STATE_UNDEFINED; kfree(dotg); dwc->dotg = NULL; }