blob: 4d8175c75fd9cfb25d3fc8cbc8ba056328688e76 [file] [log] [blame]
/**
* 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>
* Minho Lee <minho55.lee@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/mfd/samsung/s2mps18-private.h>
#include <linux/suspend.h>
#include <soc/samsung/exynos-pm.h>
#if defined(CONFIG_TYPEC)
#include <linux/usb/typec.h>
#endif
#include "core.h"
#include "otg.h"
#include "io.h"
#ifdef CONFIG_OF
#include <linux/of_device.h>
#endif
#if defined(CONFIG_CCIC_MAX77705)
#include <linux/ccic/max77705_usbc.h>
#endif
#if defined(CONFIG_USB_PORT_POWER_OPTIMIZATION)
#include "../notify/usb_power_notify.h"
#endif
#define OTG_NO_CONNECT 0
#define OTG_CONNECT_ONLY 1
#define OTG_DEVICE_CONNECT 2
#define LINK_DEBUG_L (0x0C)
#define LINK_DEBUG_H (0x10)
#define BUS_ACTIVITY_CHECK (0x3F << 16)
#define READ_TRANS_OFFSET 10
/* -------------------------------------------------------------------------- */
int otg_connection;
static int dwc3_otg_statemachine(struct otg_fsm *fsm)
{
struct usb_otg *otg = fsm->otg;
enum usb_otg_state prev_state = otg->state;
int ret = 0;
if (fsm->reset) {
if (otg->state == OTG_STATE_A_HOST) {
otg_drv_vbus(fsm, 0);
otg_start_host(fsm, 0);
} else if (otg->state == OTG_STATE_B_PERIPHERAL) {
otg_start_gadget(fsm, 0);
}
otg->state = OTG_STATE_UNDEFINED;
goto exit;
}
switch (otg->state) {
case OTG_STATE_UNDEFINED:
if (fsm->id)
otg->state = OTG_STATE_B_IDLE;
else
otg->state = OTG_STATE_A_IDLE;
break;
case OTG_STATE_B_IDLE:
if (!fsm->id) {
otg->state = OTG_STATE_A_IDLE;
} else if (fsm->b_sess_vld) {
ret = otg_start_gadget(fsm, 1);
if (!ret)
otg->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)
otg->state = OTG_STATE_B_IDLE;
else
pr_err("OTG SM: cannot stop gadget\n");
}
break;
case OTG_STATE_A_IDLE:
if (fsm->id) {
otg->state = OTG_STATE_B_IDLE;
} else {
ret = otg_start_host(fsm, 1);
if (!ret) {
otg_drv_vbus(fsm, 1);
otg->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)
otg->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 = (otg->state != prev_state);
pr_debug("OTG SM: %s => %s\n", usb_otg_state_string(prev_state),
(ret > 0) ? usb_otg_state_string(otg->state) : "(no change)");
return ret;
}
/* -------------------------------------------------------------------------- */
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;
}
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;
if (IS_ERR(dotg->vbus_reg)) {
dev_err(dotg->dwc->dev, "vbus regulator is not available\n");
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 struct device_node *dwc3_otg_parse_dt(void)
{
struct device_node *np = NULL;
np = of_find_compatible_node(NULL, NULL, "synopsys,dwc3");
if (!np) {
pr_err("%s: failed to get the dwc3 device node\n",
__func__);
goto err;
}
return np;
err:
return NULL;
}
static struct dwc3 *dwc3_otg_get_struct(void)
{
struct device_node *np = NULL;
struct platform_device *pdev = NULL;
struct device *dev;
struct dwc3 *dwc;
np = dwc3_otg_parse_dt();
if (np) {
pdev = of_find_device_by_node(np);
dev = &pdev->dev;
of_node_put(np);
if (pdev) {
pr_info("%s: get the %s platform_device\n",
__func__, pdev->name);
dwc = dev->driver_data;
return dwc;
}
}
pr_err("%s: failed to get the platform_device\n", __func__);
return NULL;
}
void dwc3_otg_check_bus_act(struct dwc3 *dwc)
{
u32 reg;
u32 xm_wtran, xm_rtran, xm_ch_status;
int retries = 100;
reg = dwc3_readl(dwc->regs, DWC3_GDBGLSPMUX_HST);
reg |= BUS_ACTIVITY_CHECK;
dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX_HST, reg);
do {
reg = readl(phycon_base_addr + LINK_DEBUG_L);
xm_ch_status = reg & 0x3FF;
xm_rtran = (reg >> READ_TRANS_OFFSET) & 0x3FFFFF;
reg = readl(phycon_base_addr + LINK_DEBUG_H);
xm_wtran = reg & 0x3FFFFF;
if (!xm_rtran && !xm_wtran)
break;
mdelay(1);
} while (--retries);
pr_info("%s %s: retries = %d\n", __func__,
retries ? "clear" : "timeout", retries);
}
int exynos_usbdrd_inform_dp_use(int use, int lane_cnt)
{
struct dwc3 *dwc;
struct dwc3_otg *dotg;
int ret = 0;
pr_info("[%s] dp use = %d, lane_cnt = %d\n",
__func__, use, lane_cnt);
dwc = dwc3_otg_get_struct();
if (!dwc) {
pr_err("[%s] dwc3_otg_get_struct error\n", __func__);
return -ENODEV;
}
dotg = dwc->dotg;
dotg->dp_use_informed = use;
if ((use == 1) && (lane_cnt == 4)) {
ret = xhci_portsc_set(0);
udelay(1);
}
return ret;
}
void exynos_usbdrd_request_phy_isol(void)
{
}
/* owner 0 - USB
owner 1 - DP
*/
int dwc3_otg_phy_enable(struct otg_fsm *fsm, int owner, bool 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;
u8 owner_bit = 0;
mutex_lock(&dotg->lock);
dev_info(dev, "%s phy control=%d owner=%d (usb:0 dp:1) on=%d\n",
__func__, dotg->combo_phy_control, owner, on);
if (owner > 1)
goto out;
owner_bit = (1 << owner);
if (on) {
if (dotg->combo_phy_control) {
dotg->combo_phy_control |= owner_bit;
goto out;
} else {
phy_conn(dwc->usb2_generic_phy, 1);
if (!dotg->pm_qos_int_val)
pm_qos_update_request(&dotg->pm_qos_int_req,
dotg->pm_qos_int_val);
pm_runtime_get_sync(dev);
ret = dwc3_core_init(dwc);
if (ret) {
dev_err(dwc->dev, "%s: failed to reinitialize core\n",
__func__);
goto err;
}
dotg->combo_phy_control |= owner_bit;
}
} else {
dotg->combo_phy_control &= ~(owner_bit);
if (dotg->combo_phy_control == 0) {
dwc3_core_exit(dwc);
err:
dwc3_otg_check_bus_act(dwc);
pm_runtime_put_sync_suspend(dev);
if (!dotg->pm_qos_int_val)
pm_qos_update_request(&dotg->pm_qos_int_req, 0);
phy_conn(dwc->usb2_generic_phy, 0);
}
}
out:
mutex_unlock(&dotg->lock);
return ret;
}
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;
int ret1 = -1;
if (!dotg->dwc->xhci) {
dev_err(dev, "%s: does not have any xhci\n", __func__);
return -EINVAL;
}
dev_info(dev, "Turn %s host\n", on ? "on" : "off");
if (on) {
otg_connection = 1;
ret = dwc3_otg_phy_enable(fsm, 0, on);
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;
}
#if defined(CONFIG_USB_PORT_POWER_OPTIMIZATION)
register_usb_power_notify();
#endif
#if defined(CONFIG_CCIC_MAX77705)
max77705_set_host_turn_on_event(on);
#endif
} else {
#if defined(CONFIG_CCIC_MAX77705)
max77705_set_host_turn_on_event(on);
#endif
#if defined(CONFIG_USB_PORT_POWER_OPTIMIZATION)
xhci_port_power_set(1, 3);
unregister_usb_power_notify();
#endif
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
otg_connection = 0;
#endif
if (dotg->dwc3_suspended) {
pr_info("%s: wait resume completion\n", __func__);
ret1 = wait_for_completion_timeout(&dotg->resume_cmpl,
msecs_to_jiffies(5000));
}
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
if (usb_audio->usb_audio_state !=
USB_AUDIO_DISCONNECT) {
pr_info("%s: wait audio disconnect\n", __func__);
ret1 = wait_for_completion_timeout(&usb_audio
->discon_done,
msecs_to_jiffies(1000));
}
#endif
platform_device_del(dwc->xhci);
err2:
ret = dwc3_otg_phy_enable(fsm, 0, on);
}
err1:
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 (!otg->gadget) {
dev_err(dev, "%s does not have any gadget\n", __func__);
return -EINVAL;
}
dev_info(dev, "Turn %s gadget %s\n",
on ? "on" : "off", otg->gadget->name);
if (on) {
wake_lock(&dotg->wakelock);
dwc->vbus_state = true;
ret = dwc3_otg_phy_enable(fsm, 0, on);
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 {
dwc->vbus_state = false;
if (dwc->is_not_vbus_pad)
dwc3_gadget_disconnect_proc(dwc);
/* avoid missing disconnect interrupt */
ret = wait_for_completion_timeout(&dwc->disconnect,
msecs_to_jiffies(200));
if (!ret) {
dev_err(dwc->dev, "%s: disconnect completion timeout\n",
__func__);
return ret;
}
ret = usb_gadget_vbus_disconnect(otg->gadget);
if (ret)
dev_err(dwc->dev, "%s: vbus disconnect failed\n",
__func__);
err2:
ret = dwc3_otg_phy_enable(fsm, 0, on);
err1:
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);
}
/* Bind/Unbind the peripheral controller driver */
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_info(dev, "Binding gadget %s\n", gadget->name);
otg->gadget = gadget;
} else {
dev_info(dev, "Unbinding gadget\n");
mutex_lock(&fsm->lock);
if (otg->state == OTG_STATE_B_PERIPHERAL) {
/* Reset OTG Statemachine */
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 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);
}
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 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 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);
}
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);
}
/* -------------------------------------------------------------------------- */
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;
return snprintf(buf, PAGE_SIZE, "%s\n",
usb_otg_state_string(otg->state));
}
static DEVICE_ATTR(state, S_IRUSR | S_IRGRP,
dwc3_otg_show_state, NULL);
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;
}
/* -------------------------------------------------------------------------- */
u32 otg_is_connect(void)
{
if (!otg_connection)
return OTG_NO_CONNECT;
#if defined(CONFIG_USB_PORT_POWER_OPTIMIZATION)
else if (is_otg_only)
return OTG_CONNECT_ONLY;
#endif
else
return OTG_DEVICE_CONNECT;
}
EXPORT_SYMBOL_GPL(otg_is_connect);
static int dwc3_otg_pm_notifier(struct notifier_block *nb,
unsigned long action, void *nb_data)
{
struct dwc3_otg *dotg
= container_of(nb, struct dwc3_otg, pm_nb);
switch (action) {
case PM_SUSPEND_PREPARE:
pr_info("%s suspend prepare\n", __func__);
dotg->dwc3_suspended = 1;
reinit_completion(&dotg->resume_cmpl);
break;
case PM_POST_SUSPEND:
pr_info("%s post suspend\n", __func__);
dotg->dwc3_suspended = 0;
complete(&dotg->resume_cmpl);
break;
default:
break;
}
return NOTIFY_OK;
}
int dwc3_otg_init(struct dwc3 *dwc)
{
struct dwc3_otg *dotg;
struct dwc3_ext_otg_ops *ops = NULL;
int ret = 0;
dev_info(dwc->dev, "%s\n", __func__);
/* EXYNOS SoCs don't have HW OTG, but support SW OTG. */
ops = dwc3_otg_exynos_rsw_probe(dwc);
if (!ops)
return 0;
/* 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;
ret = of_property_read_u32(dwc->dev->of_node,"ldos", &dotg->ldos);
if (ret < 0) {
dev_err(dwc->dev, "can't get ldo information\n");
return -EINVAL;
}
ret = of_property_read_u32(dwc->dev->of_node,
"usb-pm-qos-int", &dotg->pm_qos_int_val);
if (ret < 0) {
dev_err(dwc->dev, "couldn't read usb-pm-qos-int %s node, error = %d\n",
dwc->dev->of_node->name, ret);
dotg->pm_qos_int_val = 0;
} else {
pm_qos_add_request(&dotg->pm_qos_int_req,
PM_QOS_DEVICE_THROUGHPUT, 0);
}
dotg->ext_otg_ops = ops;
dotg->otg.set_peripheral = dwc3_otg_set_peripheral;
dotg->otg.set_host = NULL;
dotg->otg.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_err(dwc->dev, "failed to obtain vbus regulator\n");
if (dotg->ext_otg_ops) {
ret = dwc3_ext_otg_setup(dotg);
if (ret) {
dev_err(dwc->dev, "failed to setup OTG\n");
return ret;
}
}
wake_lock_init(&dotg->wakelock, WAKE_LOCK_SUSPEND, "dwc3-otg");
mutex_init(&dotg->lock);
ret = sysfs_create_group(&dwc->dev->kobj, &dwc3_otg_attr_group);
if (ret)
dev_err(dwc->dev, "failed to create dwc3 otg attributes\n");
/* Enable LDO initially */
phy_conn(dwc->usb2_generic_phy, 1);
init_completion(&dotg->resume_cmpl);
dotg->dp_use_informed = 0;
dotg->dwc3_suspended = 0;
dotg->pm_nb.notifier_call = dwc3_otg_pm_notifier;
register_pm_notifier(&dotg->pm_nb);
register_usb_is_connect(otg_is_connect);
return 0;
}
void dwc3_otg_exit(struct dwc3 *dwc)
{
struct dwc3_otg *dotg = dwc->dotg;
if (!dotg->ext_otg_ops)
return;
unregister_pm_notifier(&dotg->pm_nb);
dwc3_ext_otg_exit(dotg);
sysfs_remove_group(&dwc->dev->kobj, &dwc3_otg_attr_group);
wake_lock_destroy(&dotg->wakelock);
free_irq(dotg->irq, dotg);
dotg->otg.state = OTG_STATE_UNDEFINED;
kfree(dotg);
dwc->dotg = NULL;
}