mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 08:48:05 -04:00
761 lines
17 KiB
C
761 lines
17 KiB
C
/**
|
|
* 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 <idos@codeaurora.org>
|
|
* Anton Tikhomirov <av.tikhomirov@samsung.com>
|
|
*
|
|
* 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 <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/usb/samsung_usb.h>
|
|
#include <linux/phy/phy.h>
|
|
|
|
#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;
|
|
}
|