1449 lines
35 KiB
C
1449 lines
35 KiB
C
/* ehci-msm-hsic.c - HSUSB Host Controller Driver Implementation
|
|
*
|
|
* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
|
|
*
|
|
* Partly derived from ehci-fsl.c and ehci-hcd.c
|
|
* Copyright (c) 2000-2004 by David Brownell
|
|
* Copyright (c) 2005 MontaVista Software
|
|
*
|
|
* All source code in this file is licensed under the following license except
|
|
* where indicated.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 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.
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you can find it at http://www.fsf.org
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/wakelock.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <mach/msm_bus.h>
|
|
|
|
#include <linux/usb/msm_hsusb_hw.h>
|
|
#include <linux/usb/msm_hsusb.h>
|
|
#include <linux/gpio.h>
|
|
#include <mach/clk.h>
|
|
#include <mach/msm_iomap.h>
|
|
#include <mach/msm_xo.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/cpu.h>
|
|
|
|
#define MSM_USB_BASE (hcd->regs)
|
|
|
|
struct msm_hsic_hcd {
|
|
struct ehci_hcd ehci;
|
|
struct device *dev;
|
|
struct clk *ahb_clk;
|
|
struct clk *core_clk;
|
|
struct clk *alt_core_clk;
|
|
struct clk *phy_clk;
|
|
struct clk *cal_clk;
|
|
struct regulator *hsic_vddcx;
|
|
bool async_int;
|
|
atomic_t in_lpm;
|
|
struct wake_lock wlock;
|
|
int peripheral_status_irq;
|
|
int wakeup_irq;
|
|
int wakeup_gpio;
|
|
bool wakeup_irq_enabled;
|
|
atomic_t pm_usage_cnt;
|
|
uint32_t bus_perf_client;
|
|
uint32_t wakeup_int_cnt;
|
|
};
|
|
|
|
static bool debug_bus_voting_enabled = true;
|
|
|
|
static unsigned int enable_dbg_log = 1;
|
|
module_param(enable_dbg_log, uint, S_IRUGO | S_IWUSR);
|
|
/*by default log ep0 and efs sync ep*/
|
|
static unsigned int ep_addr_rxdbg_mask = 9;
|
|
module_param(ep_addr_rxdbg_mask, uint, S_IRUGO | S_IWUSR);
|
|
static unsigned int ep_addr_txdbg_mask = 9;
|
|
module_param(ep_addr_txdbg_mask, uint, S_IRUGO | S_IWUSR);
|
|
|
|
/* Maximum debug message length */
|
|
#define DBG_MSG_LEN 100UL
|
|
|
|
/* Maximum number of messages */
|
|
#define DBG_MAX_MSG 256UL
|
|
|
|
#define TIME_BUF_LEN 20
|
|
|
|
enum event_type {
|
|
EVENT_UNDEF = -1,
|
|
URB_SUBMIT,
|
|
URB_COMPLETE,
|
|
EVENT_NONE,
|
|
};
|
|
|
|
#define EVENT_STR_LEN 5
|
|
|
|
static char *event_to_str(enum event_type e)
|
|
{
|
|
switch (e) {
|
|
case URB_SUBMIT:
|
|
return "S";
|
|
case URB_COMPLETE:
|
|
return "C";
|
|
case EVENT_NONE:
|
|
return "NONE";
|
|
default:
|
|
return "UNDEF";
|
|
}
|
|
}
|
|
|
|
static enum event_type str_to_event(const char *name)
|
|
{
|
|
if (!strncasecmp("S", name, EVENT_STR_LEN))
|
|
return URB_SUBMIT;
|
|
if (!strncasecmp("C", name, EVENT_STR_LEN))
|
|
return URB_COMPLETE;
|
|
if (!strncasecmp("", name, EVENT_STR_LEN))
|
|
return EVENT_NONE;
|
|
|
|
return EVENT_UNDEF;
|
|
}
|
|
|
|
/*log ep0 activity*/
|
|
static struct {
|
|
char (buf[DBG_MAX_MSG])[DBG_MSG_LEN]; /* buffer */
|
|
unsigned idx; /* index */
|
|
rwlock_t lck; /* lock */
|
|
} dbg_hsic_ctrl = {
|
|
.idx = 0,
|
|
.lck = __RW_LOCK_UNLOCKED(lck)
|
|
};
|
|
|
|
static struct {
|
|
char (buf[DBG_MAX_MSG])[DBG_MSG_LEN]; /* buffer */
|
|
unsigned idx; /* index */
|
|
rwlock_t lck; /* lock */
|
|
} dbg_hsic_data = {
|
|
.idx = 0,
|
|
.lck = __RW_LOCK_UNLOCKED(lck)
|
|
};
|
|
|
|
/**
|
|
* dbg_inc: increments debug event index
|
|
* @idx: buffer index
|
|
*/
|
|
static void dbg_inc(unsigned *idx)
|
|
{
|
|
*idx = (*idx + 1) & (DBG_MAX_MSG-1);
|
|
}
|
|
|
|
/*get_timestamp - returns time of day in us */
|
|
static char *get_timestamp(char *tbuf)
|
|
{
|
|
unsigned long long t;
|
|
unsigned long nanosec_rem;
|
|
|
|
t = cpu_clock(smp_processor_id());
|
|
nanosec_rem = do_div(t, 1000000000)/1000;
|
|
scnprintf(tbuf, TIME_BUF_LEN, "[%5lu.%06lu] ", (unsigned long)t,
|
|
nanosec_rem);
|
|
return tbuf;
|
|
}
|
|
|
|
static int allow_dbg_log(int ep_addr)
|
|
{
|
|
int dir, num;
|
|
|
|
dir = ep_addr & USB_DIR_IN ? USB_DIR_IN : USB_DIR_OUT;
|
|
num = ep_addr & ~USB_DIR_IN;
|
|
num = 1 << num;
|
|
|
|
if ((dir == USB_DIR_IN) && (num & ep_addr_rxdbg_mask))
|
|
return 1;
|
|
if ((dir == USB_DIR_OUT) && (num & ep_addr_txdbg_mask))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dbg_log_event(struct urb *urb, char * event, unsigned extra)
|
|
{
|
|
unsigned long flags;
|
|
int ep_addr;
|
|
char tbuf[TIME_BUF_LEN];
|
|
|
|
if (!enable_dbg_log)
|
|
return;
|
|
|
|
if (!urb) {
|
|
write_lock_irqsave(&dbg_hsic_ctrl.lck, flags);
|
|
scnprintf(dbg_hsic_ctrl.buf[dbg_hsic_ctrl.idx], DBG_MSG_LEN,
|
|
"%s: %s : %u\n", get_timestamp(tbuf), event, extra);
|
|
dbg_inc(&dbg_hsic_ctrl.idx);
|
|
write_unlock_irqrestore(&dbg_hsic_ctrl.lck, flags);
|
|
return;
|
|
}
|
|
|
|
ep_addr = urb->ep->desc.bEndpointAddress;
|
|
if (!allow_dbg_log(ep_addr))
|
|
return;
|
|
|
|
if ((ep_addr & 0x0f) == 0x0) {
|
|
/*submit event*/
|
|
if (!str_to_event(event)) {
|
|
write_lock_irqsave(&dbg_hsic_ctrl.lck, flags);
|
|
scnprintf(dbg_hsic_ctrl.buf[dbg_hsic_ctrl.idx],
|
|
DBG_MSG_LEN, "%s: [%s : %p]:[%s] "
|
|
"%02x %02x %04x %04x %04x %u %d\n",
|
|
get_timestamp(tbuf), event, urb,
|
|
(ep_addr & USB_DIR_IN) ? "in" : "out",
|
|
urb->setup_packet[0], urb->setup_packet[1],
|
|
(urb->setup_packet[3] << 8) |
|
|
urb->setup_packet[2],
|
|
(urb->setup_packet[5] << 8) |
|
|
urb->setup_packet[4],
|
|
(urb->setup_packet[7] << 8) |
|
|
urb->setup_packet[6],
|
|
urb->transfer_buffer_length, urb->status);
|
|
|
|
dbg_inc(&dbg_hsic_ctrl.idx);
|
|
write_unlock_irqrestore(&dbg_hsic_ctrl.lck, flags);
|
|
} else {
|
|
write_lock_irqsave(&dbg_hsic_ctrl.lck, flags);
|
|
scnprintf(dbg_hsic_ctrl.buf[dbg_hsic_ctrl.idx],
|
|
DBG_MSG_LEN, "%s: [%s : %p]:[%s] %u %d\n",
|
|
get_timestamp(tbuf), event, urb,
|
|
(ep_addr & USB_DIR_IN) ? "in" : "out",
|
|
urb->actual_length, extra);
|
|
|
|
dbg_inc(&dbg_hsic_ctrl.idx);
|
|
write_unlock_irqrestore(&dbg_hsic_ctrl.lck, flags);
|
|
}
|
|
} else {
|
|
write_lock_irqsave(&dbg_hsic_data.lck, flags);
|
|
scnprintf(dbg_hsic_data.buf[dbg_hsic_data.idx], DBG_MSG_LEN,
|
|
"%s: [%s : %p]:ep%d[%s] %u %d\n",
|
|
get_timestamp(tbuf), event, urb, ep_addr & 0x0f,
|
|
(ep_addr & USB_DIR_IN) ? "in" : "out",
|
|
str_to_event(event) ? urb->actual_length :
|
|
urb->transfer_buffer_length,
|
|
str_to_event(event) ? extra : urb->status);
|
|
|
|
dbg_inc(&dbg_hsic_data.idx);
|
|
write_unlock_irqrestore(&dbg_hsic_data.lck, flags);
|
|
}
|
|
}
|
|
|
|
static inline struct msm_hsic_hcd *hcd_to_hsic(struct usb_hcd *hcd)
|
|
{
|
|
return (struct msm_hsic_hcd *) (hcd->hcd_priv);
|
|
}
|
|
|
|
static inline struct usb_hcd *hsic_to_hcd(struct msm_hsic_hcd *mehci)
|
|
{
|
|
return container_of((void *) mehci, struct usb_hcd, hcd_priv);
|
|
}
|
|
|
|
#define ULPI_IO_TIMEOUT_USEC (10 * 1000)
|
|
|
|
#define USB_PHY_VDD_DIG_VOL_SUSP_MIN 500000 /* uV */
|
|
#define USB_PHY_VDD_DIG_VOL_MIN 1000000 /* uV */
|
|
#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */
|
|
#define USB_PHY_VDD_DIG_LOAD 49360 /* uA */
|
|
|
|
#define HSIC_DBG1_REG 0x38
|
|
|
|
static int msm_hsic_init_vddcx(struct msm_hsic_hcd *mehci, int init)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!init)
|
|
goto disable_reg;
|
|
|
|
mehci->hsic_vddcx = devm_regulator_get(mehci->dev, "HSIC_VDDCX");
|
|
if (IS_ERR(mehci->hsic_vddcx)) {
|
|
dev_err(mehci->dev, "unable to get hsic vddcx\n");
|
|
return PTR_ERR(mehci->hsic_vddcx);
|
|
}
|
|
|
|
ret = regulator_set_voltage(mehci->hsic_vddcx,
|
|
USB_PHY_VDD_DIG_VOL_MIN,
|
|
USB_PHY_VDD_DIG_VOL_MAX);
|
|
if (ret) {
|
|
dev_err(mehci->dev, "unable to set the voltage"
|
|
"for hsic vddcx\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = regulator_set_optimum_mode(mehci->hsic_vddcx,
|
|
USB_PHY_VDD_DIG_LOAD);
|
|
if (ret < 0) {
|
|
pr_err("%s: Unable to set optimum mode of the regulator:"
|
|
"VDDCX\n", __func__);
|
|
goto reg_optimum_mode_err;
|
|
}
|
|
|
|
ret = regulator_enable(mehci->hsic_vddcx);
|
|
if (ret) {
|
|
dev_err(mehci->dev, "unable to enable hsic vddcx\n");
|
|
goto reg_enable_err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
disable_reg:
|
|
regulator_disable(mehci->hsic_vddcx);
|
|
reg_enable_err:
|
|
regulator_set_optimum_mode(mehci->hsic_vddcx, 0);
|
|
reg_optimum_mode_err:
|
|
regulator_set_voltage(mehci->hsic_vddcx, 0,
|
|
USB_PHY_VDD_DIG_VOL_MIN);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int ulpi_write(struct msm_hsic_hcd *mehci, u32 val, u32 reg)
|
|
{
|
|
struct usb_hcd *hcd = hsic_to_hcd(mehci);
|
|
int cnt = 0;
|
|
|
|
/* initiate write operation */
|
|
writel_relaxed(ULPI_RUN | ULPI_WRITE |
|
|
ULPI_ADDR(reg) | ULPI_DATA(val),
|
|
USB_ULPI_VIEWPORT);
|
|
|
|
/* wait for completion */
|
|
while (cnt < ULPI_IO_TIMEOUT_USEC) {
|
|
if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN))
|
|
break;
|
|
udelay(1);
|
|
cnt++;
|
|
}
|
|
|
|
if (cnt >= ULPI_IO_TIMEOUT_USEC) {
|
|
dev_err(mehci->dev, "ulpi_write: timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_hsic_config_gpios(struct msm_hsic_hcd *mehci, int gpio_en)
|
|
{
|
|
int rc = 0;
|
|
struct msm_hsic_host_platform_data *pdata;
|
|
static int gpio_status;
|
|
|
|
pdata = mehci->dev->platform_data;
|
|
|
|
if (!pdata || !pdata->strobe || !pdata->data)
|
|
return rc;
|
|
|
|
if (gpio_status == gpio_en)
|
|
return 0;
|
|
|
|
gpio_status = gpio_en;
|
|
|
|
if (!gpio_en)
|
|
goto free_gpio;
|
|
|
|
rc = gpio_request(pdata->strobe, "HSIC_STROBE_GPIO");
|
|
if (rc < 0) {
|
|
dev_err(mehci->dev, "gpio request failed for HSIC STROBE\n");
|
|
return rc;
|
|
}
|
|
|
|
rc = gpio_request(pdata->data, "HSIC_DATA_GPIO");
|
|
if (rc < 0) {
|
|
dev_err(mehci->dev, "gpio request failed for HSIC DATA\n");
|
|
goto free_strobe;
|
|
}
|
|
|
|
if (mehci->wakeup_gpio) {
|
|
rc = gpio_request(mehci->wakeup_gpio, "HSIC_WAKEUP_GPIO");
|
|
if (rc < 0) {
|
|
dev_err(mehci->dev, "gpio request failed for HSIC WAKEUP\n");
|
|
goto free_data;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
free_gpio:
|
|
if (mehci->wakeup_gpio)
|
|
gpio_free(mehci->wakeup_gpio);
|
|
free_data:
|
|
gpio_free(pdata->data);
|
|
free_strobe:
|
|
gpio_free(pdata->strobe);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_hsic_phy_clk_reset(struct msm_hsic_hcd *mehci)
|
|
{
|
|
int ret;
|
|
|
|
clk_prepare_enable(mehci->alt_core_clk);
|
|
|
|
ret = clk_reset(mehci->core_clk, CLK_RESET_ASSERT);
|
|
if (ret) {
|
|
clk_disable_unprepare(mehci->alt_core_clk);
|
|
dev_err(mehci->dev, "usb phy clk assert failed\n");
|
|
return ret;
|
|
}
|
|
usleep_range(10000, 12000);
|
|
clk_disable_unprepare(mehci->alt_core_clk);
|
|
|
|
ret = clk_reset(mehci->core_clk, CLK_RESET_DEASSERT);
|
|
if (ret)
|
|
dev_err(mehci->dev, "usb phy clk deassert failed\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int msm_hsic_phy_reset(struct msm_hsic_hcd *mehci)
|
|
{
|
|
struct usb_hcd *hcd = hsic_to_hcd(mehci);
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = msm_hsic_phy_clk_reset(mehci);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK;
|
|
writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC);
|
|
|
|
/* Ensure that RESET operation is completed before turning off clock */
|
|
mb();
|
|
dev_dbg(mehci->dev, "phy_reset: success\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define HSIC_GPIO150_PAD_CTL (MSM_TLMM_BASE+0x20C0)
|
|
#define HSIC_GPIO151_PAD_CTL (MSM_TLMM_BASE+0x20C4)
|
|
#define HSIC_CAL_PAD_CTL (MSM_TLMM_BASE+0x20C8)
|
|
#define HSIC_LV_MODE 0x04
|
|
#define HSIC_PAD_CALIBRATION 0xA8
|
|
#define HSIC_GPIO_PAD_VAL 0x0A0AAA10
|
|
#define LINK_RESET_TIMEOUT_USEC (250 * 1000)
|
|
static int msm_hsic_reset(struct msm_hsic_hcd *mehci)
|
|
{
|
|
struct usb_hcd *hcd = hsic_to_hcd(mehci);
|
|
int cnt = 0;
|
|
int ret;
|
|
struct msm_hsic_host_platform_data *pdata = mehci->dev->platform_data;
|
|
|
|
ret = msm_hsic_phy_reset(mehci);
|
|
if (ret) {
|
|
dev_err(mehci->dev, "phy_reset failed\n");
|
|
return ret;
|
|
}
|
|
|
|
writel_relaxed(USBCMD_RESET, USB_USBCMD);
|
|
while (cnt < LINK_RESET_TIMEOUT_USEC) {
|
|
if (!(readl_relaxed(USB_USBCMD) & USBCMD_RESET))
|
|
break;
|
|
udelay(1);
|
|
cnt++;
|
|
}
|
|
if (cnt >= LINK_RESET_TIMEOUT_USEC)
|
|
return -ETIMEDOUT;
|
|
|
|
/* Reset PORTSC and select ULPI phy */
|
|
writel_relaxed(0x80000000, USB_PORTSC);
|
|
|
|
/* TODO: Need to confirm if HSIC PHY also requires delay after RESET */
|
|
msleep(100);
|
|
|
|
/* HSIC PHY Initialization */
|
|
|
|
/* HSIC init sequence when HSIC signals (Strobe/Data) are
|
|
routed via GPIOs */
|
|
if (pdata && pdata->strobe && pdata->data) {
|
|
|
|
/* Enable LV_MODE in HSIC_CAL_PAD_CTL register */
|
|
writel_relaxed(HSIC_LV_MODE, HSIC_CAL_PAD_CTL);
|
|
|
|
/*set periodic calibration interval to ~2.048sec in
|
|
HSIC_IO_CAL_REG */
|
|
ulpi_write(mehci, 0xFF, 0x33);
|
|
|
|
/* Enable periodic IO calibration in HSIC_CFG register */
|
|
ulpi_write(mehci, HSIC_PAD_CALIBRATION, 0x30);
|
|
|
|
/* Configure GPIO 150/151 pins for HSIC functionality mode */
|
|
ret = msm_hsic_config_gpios(mehci, 1);
|
|
if (ret) {
|
|
dev_err(mehci->dev, " gpio configuarion failed\n");
|
|
return ret;
|
|
}
|
|
/* Set LV_MODE=0x1 and DCC=0x2 in HSIC_GPIO150/151_PAD_CTL
|
|
register */
|
|
writel_relaxed(HSIC_GPIO_PAD_VAL, HSIC_GPIO150_PAD_CTL);
|
|
writel_relaxed(HSIC_GPIO_PAD_VAL, HSIC_GPIO151_PAD_CTL);
|
|
/* Enable HSIC mode in HSIC_CFG register */
|
|
ulpi_write(mehci, 0x01, 0x31);
|
|
} else {
|
|
/* HSIC init sequence when HSIC signals (Strobe/Data) are routed
|
|
via dedicated I/O */
|
|
|
|
/* programmable length of connect signaling (33.2ns) */
|
|
ret = ulpi_write(mehci, 3, HSIC_DBG1_REG);
|
|
if (ret) {
|
|
pr_err("%s: Unable to program length of connect "
|
|
"signaling\n", __func__);
|
|
}
|
|
|
|
/*set periodic calibration interval to ~2.048sec in
|
|
HSIC_IO_CAL_REG */
|
|
ulpi_write(mehci, 0xFF, 0x33);
|
|
|
|
/* Enable HSIC mode in HSIC_CFG register */
|
|
ulpi_write(mehci, 0xA9, 0x30);
|
|
}
|
|
|
|
/*disable auto resume*/
|
|
ulpi_write(mehci, ULPI_IFC_CTRL_AUTORESUME, ULPI_CLR(ULPI_IFC_CTRL));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000)
|
|
#define PHY_RESUME_TIMEOUT_USEC (100 * 1000)
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int msm_hsic_suspend(struct msm_hsic_hcd *mehci)
|
|
{
|
|
struct usb_hcd *hcd = hsic_to_hcd(mehci);
|
|
int cnt = 0, ret;
|
|
u32 val;
|
|
|
|
if (atomic_read(&mehci->in_lpm)) {
|
|
dev_dbg(mehci->dev, "%s called in lpm\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (!(readl_relaxed(USB_PORTSC) & PORT_PE)) {
|
|
dev_dbg(mehci->dev, "%s:port is not enabled skip suspend\n",
|
|
__func__);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
disable_irq(hcd->irq);
|
|
|
|
/* make sure we don't race against a remote wakeup */
|
|
if (test_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags) ||
|
|
readl_relaxed(USB_PORTSC) & PORT_RESUME) {
|
|
dev_dbg(mehci->dev, "wakeup pending, aborting suspend\n");
|
|
enable_irq(hcd->irq);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/*
|
|
* PHY may take some time or even fail to enter into low power
|
|
* mode (LPM). Hence poll for 500 msec and reset the PHY and link
|
|
* in failure case.
|
|
*/
|
|
val = readl_relaxed(USB_PORTSC);
|
|
val &= ~PORT_RWC_BITS;
|
|
val |= PORTSC_PHCD;
|
|
writel_relaxed(val, USB_PORTSC);
|
|
while (cnt < PHY_SUSPEND_TIMEOUT_USEC) {
|
|
if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD)
|
|
break;
|
|
udelay(1);
|
|
cnt++;
|
|
}
|
|
|
|
if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) {
|
|
dev_err(mehci->dev, "Unable to suspend PHY\n");
|
|
msm_hsic_config_gpios(mehci, 0);
|
|
msm_hsic_reset(mehci);
|
|
}
|
|
|
|
/*
|
|
* PHY has capability to generate interrupt asynchronously in low
|
|
* power mode (LPM). This interrupt is level triggered. So USB IRQ
|
|
* line must be disabled till async interrupt enable bit is cleared
|
|
* in USBCMD register. Assert STP (ULPI interface STOP signal) to
|
|
* block data communication from PHY.
|
|
*/
|
|
writel_relaxed(readl_relaxed(USB_USBCMD) | ASYNC_INTR_CTRL |
|
|
ULPI_STP_CTRL, USB_USBCMD);
|
|
|
|
/*
|
|
* Ensure that hardware is put in low power mode before
|
|
* clocks are turned OFF and VDD is allowed to minimize.
|
|
*/
|
|
mb();
|
|
|
|
clk_disable_unprepare(mehci->core_clk);
|
|
clk_disable_unprepare(mehci->phy_clk);
|
|
clk_disable_unprepare(mehci->cal_clk);
|
|
clk_disable_unprepare(mehci->ahb_clk);
|
|
|
|
ret = regulator_set_voltage(mehci->hsic_vddcx,
|
|
USB_PHY_VDD_DIG_VOL_SUSP_MIN,
|
|
USB_PHY_VDD_DIG_VOL_MAX);
|
|
if (ret < 0)
|
|
dev_err(mehci->dev, "unable to set vddcx voltage: min:0.5v max:1.3v\n");
|
|
|
|
if (mehci->bus_perf_client && debug_bus_voting_enabled) {
|
|
ret = msm_bus_scale_client_update_request(
|
|
mehci->bus_perf_client, 0);
|
|
if (ret)
|
|
dev_err(mehci->dev, "%s: Failed to dvote for "
|
|
"bus bandwidth %d\n", __func__, ret);
|
|
}
|
|
|
|
atomic_set(&mehci->in_lpm, 1);
|
|
enable_irq(hcd->irq);
|
|
|
|
mehci->wakeup_irq_enabled = 1;
|
|
enable_irq_wake(mehci->wakeup_irq);
|
|
enable_irq(mehci->wakeup_irq);
|
|
|
|
wake_unlock(&mehci->wlock);
|
|
|
|
dev_info(mehci->dev, "HSIC-USB in low power mode\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_hsic_resume(struct msm_hsic_hcd *mehci)
|
|
{
|
|
struct usb_hcd *hcd = hsic_to_hcd(mehci);
|
|
int cnt = 0, ret;
|
|
unsigned temp;
|
|
|
|
if (!atomic_read(&mehci->in_lpm)) {
|
|
dev_dbg(mehci->dev, "%s called in !in_lpm\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (mehci->wakeup_irq_enabled) {
|
|
disable_irq_wake(mehci->wakeup_irq);
|
|
disable_irq_nosync(mehci->wakeup_irq);
|
|
mehci->wakeup_irq_enabled = 0;
|
|
}
|
|
|
|
wake_lock(&mehci->wlock);
|
|
|
|
if (mehci->bus_perf_client && debug_bus_voting_enabled) {
|
|
ret = msm_bus_scale_client_update_request(
|
|
mehci->bus_perf_client, 1);
|
|
if (ret)
|
|
dev_err(mehci->dev, "%s: Failed to vote for "
|
|
"bus bandwidth %d\n", __func__, ret);
|
|
}
|
|
|
|
ret = regulator_set_voltage(mehci->hsic_vddcx,
|
|
USB_PHY_VDD_DIG_VOL_MIN,
|
|
USB_PHY_VDD_DIG_VOL_MAX);
|
|
if (ret < 0)
|
|
dev_err(mehci->dev, "unable to set vddcx voltage: min:1v max:1.3v\n");
|
|
|
|
clk_prepare_enable(mehci->core_clk);
|
|
clk_prepare_enable(mehci->phy_clk);
|
|
clk_prepare_enable(mehci->cal_clk);
|
|
clk_prepare_enable(mehci->ahb_clk);
|
|
|
|
temp = readl_relaxed(USB_USBCMD);
|
|
temp &= ~ASYNC_INTR_CTRL;
|
|
temp &= ~ULPI_STP_CTRL;
|
|
writel_relaxed(temp, USB_USBCMD);
|
|
|
|
if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD))
|
|
goto skip_phy_resume;
|
|
|
|
temp = readl_relaxed(USB_PORTSC);
|
|
temp &= ~(PORT_RWC_BITS | PORTSC_PHCD);
|
|
writel_relaxed(temp, USB_PORTSC);
|
|
while (cnt < PHY_RESUME_TIMEOUT_USEC) {
|
|
if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD) &&
|
|
(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_SYNC_STATE))
|
|
break;
|
|
udelay(1);
|
|
cnt++;
|
|
}
|
|
|
|
if (cnt >= PHY_RESUME_TIMEOUT_USEC) {
|
|
/*
|
|
* This is a fatal error. Reset the link and
|
|
* PHY to make hsic working.
|
|
*/
|
|
dev_err(mehci->dev, "Unable to resume USB. Reset the hsic\n");
|
|
msm_hsic_config_gpios(mehci, 0);
|
|
msm_hsic_reset(mehci);
|
|
}
|
|
|
|
skip_phy_resume:
|
|
|
|
usb_hcd_resume_root_hub(hcd);
|
|
|
|
atomic_set(&mehci->in_lpm, 0);
|
|
|
|
if (mehci->async_int) {
|
|
mehci->async_int = false;
|
|
pm_runtime_put_noidle(mehci->dev);
|
|
enable_irq(hcd->irq);
|
|
}
|
|
|
|
if (atomic_read(&mehci->pm_usage_cnt)) {
|
|
atomic_set(&mehci->pm_usage_cnt, 0);
|
|
pm_runtime_put_noidle(mehci->dev);
|
|
}
|
|
|
|
dev_info(mehci->dev, "HSIC-USB exited from low power mode\n");
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static irqreturn_t msm_hsic_irq(struct usb_hcd *hcd)
|
|
{
|
|
struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
|
|
|
|
if (atomic_read(&mehci->in_lpm)) {
|
|
disable_irq_nosync(hcd->irq);
|
|
dev_dbg(mehci->dev, "phy async intr\n");
|
|
mehci->async_int = true;
|
|
pm_runtime_get(mehci->dev);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
return ehci_irq(hcd);
|
|
}
|
|
|
|
static int ehci_hsic_reset(struct usb_hcd *hcd)
|
|
{
|
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
|
int retval;
|
|
|
|
ehci->caps = USB_CAPLENGTH;
|
|
ehci->regs = USB_CAPLENGTH +
|
|
HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase));
|
|
dbg_hcs_params(ehci, "reset");
|
|
dbg_hcc_params(ehci, "reset");
|
|
|
|
/* cache the data to minimize the chip reads*/
|
|
ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
|
|
|
|
hcd->has_tt = 1;
|
|
ehci->sbrn = HCD_USB2;
|
|
|
|
retval = ehci_halt(ehci);
|
|
if (retval)
|
|
return retval;
|
|
|
|
/* data structure init */
|
|
retval = ehci_init(hcd);
|
|
if (retval)
|
|
return retval;
|
|
|
|
retval = ehci_reset(ehci);
|
|
if (retval)
|
|
return retval;
|
|
|
|
/* bursts of unspecified length. */
|
|
writel_relaxed(0, USB_AHBBURST);
|
|
/* Use the AHB transactor */
|
|
writel_relaxed(0x08, USB_AHBMODE);
|
|
/* Disable streaming mode and select host mode */
|
|
writel_relaxed(0x13, USB_USBMODE);
|
|
|
|
ehci_port_power(ehci, 1);
|
|
return 0;
|
|
}
|
|
|
|
static int ehci_hsic_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
|
|
gfp_t mem_flags)
|
|
{
|
|
dbg_log_event(urb, event_to_str(URB_SUBMIT), 0);
|
|
return ehci_urb_enqueue(hcd, urb, mem_flags);
|
|
}
|
|
|
|
static int ehci_hsic_bus_suspend(struct usb_hcd *hcd)
|
|
{
|
|
dbg_log_event(NULL, "Suspend RH", 0);
|
|
return ehci_bus_suspend(hcd);
|
|
}
|
|
|
|
static int ehci_hsic_bus_resume(struct usb_hcd *hcd)
|
|
{
|
|
dbg_log_event(NULL, "Resume RH", 0);
|
|
return ehci_bus_resume(hcd);
|
|
}
|
|
|
|
static struct hc_driver msm_hsic_driver = {
|
|
.description = hcd_name,
|
|
.product_desc = "Qualcomm EHCI Host Controller using HSIC",
|
|
.hcd_priv_size = sizeof(struct msm_hsic_hcd),
|
|
|
|
/*
|
|
* generic hardware linkage
|
|
*/
|
|
.irq = msm_hsic_irq,
|
|
.flags = HCD_USB2 | HCD_MEMORY,
|
|
|
|
.reset = ehci_hsic_reset,
|
|
.start = ehci_run,
|
|
|
|
.stop = ehci_stop,
|
|
.shutdown = ehci_shutdown,
|
|
|
|
/*
|
|
* managing i/o requests and associated device resources
|
|
*/
|
|
.urb_enqueue = ehci_hsic_urb_enqueue,
|
|
.urb_dequeue = ehci_urb_dequeue,
|
|
.endpoint_disable = ehci_endpoint_disable,
|
|
.endpoint_reset = ehci_endpoint_reset,
|
|
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
|
|
|
|
/*
|
|
* scheduling support
|
|
*/
|
|
.get_frame_number = ehci_get_frame,
|
|
|
|
/*
|
|
* root hub support
|
|
*/
|
|
.hub_status_data = ehci_hub_status_data,
|
|
.hub_control = ehci_hub_control,
|
|
.relinquish_port = ehci_relinquish_port,
|
|
.port_handed_over = ehci_port_handed_over,
|
|
|
|
/*
|
|
* PM support
|
|
*/
|
|
.bus_suspend = ehci_hsic_bus_suspend,
|
|
.bus_resume = ehci_hsic_bus_resume,
|
|
|
|
.log_urb_complete = dbg_log_event,
|
|
};
|
|
|
|
static int msm_hsic_init_clocks(struct msm_hsic_hcd *mehci, u32 init)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!init)
|
|
goto put_clocks;
|
|
|
|
/*core_clk is required for LINK protocol engine
|
|
*clock rate appropriately set by target specific clock driver */
|
|
mehci->core_clk = clk_get(mehci->dev, "core_clk");
|
|
if (IS_ERR(mehci->core_clk)) {
|
|
dev_err(mehci->dev, "failed to get core_clk\n");
|
|
ret = PTR_ERR(mehci->core_clk);
|
|
return ret;
|
|
}
|
|
|
|
/* alt_core_clk is for LINK to be used during PHY RESET
|
|
* clock rate appropriately set by target specific clock driver */
|
|
mehci->alt_core_clk = clk_get(mehci->dev, "alt_core_clk");
|
|
if (IS_ERR(mehci->alt_core_clk)) {
|
|
dev_err(mehci->dev, "failed to core_clk\n");
|
|
ret = PTR_ERR(mehci->alt_core_clk);
|
|
goto put_core_clk;
|
|
}
|
|
|
|
/* phy_clk is required for HSIC PHY operation
|
|
* clock rate appropriately set by target specific clock driver */
|
|
mehci->phy_clk = clk_get(mehci->dev, "phy_clk");
|
|
if (IS_ERR(mehci->phy_clk)) {
|
|
dev_err(mehci->dev, "failed to get phy_clk\n");
|
|
ret = PTR_ERR(mehci->phy_clk);
|
|
goto put_alt_core_clk;
|
|
}
|
|
|
|
/* 10MHz cal_clk is required for calibration of I/O pads */
|
|
mehci->cal_clk = clk_get(mehci->dev, "cal_clk");
|
|
if (IS_ERR(mehci->cal_clk)) {
|
|
dev_err(mehci->dev, "failed to get cal_clk\n");
|
|
ret = PTR_ERR(mehci->cal_clk);
|
|
goto put_phy_clk;
|
|
}
|
|
clk_set_rate(mehci->cal_clk, 10000000);
|
|
|
|
/* ahb_clk is required for data transfers */
|
|
mehci->ahb_clk = clk_get(mehci->dev, "iface_clk");
|
|
if (IS_ERR(mehci->ahb_clk)) {
|
|
dev_err(mehci->dev, "failed to get iface_clk\n");
|
|
ret = PTR_ERR(mehci->ahb_clk);
|
|
goto put_cal_clk;
|
|
}
|
|
|
|
clk_prepare_enable(mehci->core_clk);
|
|
clk_prepare_enable(mehci->phy_clk);
|
|
clk_prepare_enable(mehci->cal_clk);
|
|
clk_prepare_enable(mehci->ahb_clk);
|
|
|
|
return 0;
|
|
|
|
put_clocks:
|
|
if (!atomic_read(&mehci->in_lpm)) {
|
|
clk_disable_unprepare(mehci->core_clk);
|
|
clk_disable_unprepare(mehci->phy_clk);
|
|
clk_disable_unprepare(mehci->cal_clk);
|
|
clk_disable_unprepare(mehci->ahb_clk);
|
|
}
|
|
clk_put(mehci->ahb_clk);
|
|
put_cal_clk:
|
|
clk_put(mehci->cal_clk);
|
|
put_phy_clk:
|
|
clk_put(mehci->phy_clk);
|
|
put_alt_core_clk:
|
|
clk_put(mehci->alt_core_clk);
|
|
put_core_clk:
|
|
clk_put(mehci->core_clk);
|
|
|
|
return ret;
|
|
}
|
|
static irqreturn_t hsic_peripheral_status_change(int irq, void *dev_id)
|
|
{
|
|
struct msm_hsic_hcd *mehci = dev_id;
|
|
|
|
pr_debug("%s: mehci:%p dev_id:%p\n", __func__, mehci, dev_id);
|
|
|
|
if (mehci)
|
|
msm_hsic_config_gpios(mehci, 0);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t msm_hsic_wakeup_irq(int irq, void *data)
|
|
{
|
|
struct msm_hsic_hcd *mehci = data;
|
|
|
|
mehci->wakeup_int_cnt++;
|
|
dbg_log_event(NULL, "Remote Wakeup IRQ", mehci->wakeup_int_cnt);
|
|
dev_dbg(mehci->dev, "%s: hsic remote wakeup interrupt cnt: %u\n",
|
|
__func__, mehci->wakeup_int_cnt);
|
|
|
|
wake_lock(&mehci->wlock);
|
|
|
|
if (mehci->wakeup_irq_enabled) {
|
|
mehci->wakeup_irq_enabled = 0;
|
|
disable_irq_wake(irq);
|
|
disable_irq_nosync(irq);
|
|
}
|
|
|
|
if (!atomic_read(&mehci->pm_usage_cnt)) {
|
|
atomic_set(&mehci->pm_usage_cnt, 1);
|
|
pm_runtime_get(mehci->dev);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int ehci_hsic_msm_bus_show(struct seq_file *s, void *unused)
|
|
{
|
|
if (debug_bus_voting_enabled)
|
|
seq_printf(s, "enabled\n");
|
|
else
|
|
seq_printf(s, "disabled\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ehci_hsic_msm_bus_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, ehci_hsic_msm_bus_show, inode->i_private);
|
|
}
|
|
|
|
static ssize_t ehci_hsic_msm_bus_write(struct file *file,
|
|
const char __user *ubuf, size_t count, loff_t *ppos)
|
|
{
|
|
char buf[8];
|
|
int ret;
|
|
struct seq_file *s = file->private_data;
|
|
struct msm_hsic_hcd *mehci = s->private;
|
|
|
|
memset(buf, 0x00, sizeof(buf));
|
|
|
|
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
|
|
return -EFAULT;
|
|
|
|
if (!strncmp(buf, "enable", 6)) {
|
|
/* Do not vote here. Let hsic driver decide when to vote */
|
|
debug_bus_voting_enabled = true;
|
|
} else {
|
|
debug_bus_voting_enabled = false;
|
|
if (mehci->bus_perf_client) {
|
|
ret = msm_bus_scale_client_update_request(
|
|
mehci->bus_perf_client, 0);
|
|
if (ret)
|
|
dev_err(mehci->dev, "%s: Failed to devote "
|
|
"for bus bw %d\n", __func__, ret);
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
const struct file_operations ehci_hsic_msm_bus_fops = {
|
|
.open = ehci_hsic_msm_bus_open,
|
|
.read = seq_read,
|
|
.write = ehci_hsic_msm_bus_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int ehci_hsic_msm_wakeup_cnt_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct msm_hsic_hcd *mehci = s->private;
|
|
|
|
seq_printf(s, "%u\n", mehci->wakeup_int_cnt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ehci_hsic_msm_wakeup_cnt_open(struct inode *inode, struct file *f)
|
|
{
|
|
return single_open(f, ehci_hsic_msm_wakeup_cnt_show, inode->i_private);
|
|
}
|
|
|
|
const struct file_operations ehci_hsic_msm_wakeup_cnt_fops = {
|
|
.open = ehci_hsic_msm_wakeup_cnt_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int ehci_hsic_msm_data_events_show(struct seq_file *s, void *unused)
|
|
{
|
|
unsigned long flags;
|
|
unsigned i;
|
|
|
|
read_lock_irqsave(&dbg_hsic_data.lck, flags);
|
|
|
|
i = dbg_hsic_data.idx;
|
|
for (dbg_inc(&i); i != dbg_hsic_data.idx; dbg_inc(&i)) {
|
|
if (!strnlen(dbg_hsic_data.buf[i], DBG_MSG_LEN))
|
|
continue;
|
|
seq_printf(s, "%s\n", dbg_hsic_data.buf[i]);
|
|
}
|
|
|
|
read_unlock_irqrestore(&dbg_hsic_data.lck, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ehci_hsic_msm_data_events_open(struct inode *inode, struct file *f)
|
|
{
|
|
return single_open(f, ehci_hsic_msm_data_events_show, inode->i_private);
|
|
}
|
|
|
|
const struct file_operations ehci_hsic_msm_dbg_data_fops = {
|
|
.open = ehci_hsic_msm_data_events_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int ehci_hsic_msm_ctrl_events_show(struct seq_file *s, void *unused)
|
|
{
|
|
unsigned long flags;
|
|
unsigned i;
|
|
|
|
read_lock_irqsave(&dbg_hsic_ctrl.lck, flags);
|
|
|
|
i = dbg_hsic_ctrl.idx;
|
|
for (dbg_inc(&i); i != dbg_hsic_ctrl.idx; dbg_inc(&i)) {
|
|
if (!strnlen(dbg_hsic_ctrl.buf[i], DBG_MSG_LEN))
|
|
continue;
|
|
seq_printf(s, "%s\n", dbg_hsic_ctrl.buf[i]);
|
|
}
|
|
|
|
read_unlock_irqrestore(&dbg_hsic_ctrl.lck, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ehci_hsic_msm_ctrl_events_open(struct inode *inode, struct file *f)
|
|
{
|
|
return single_open(f, ehci_hsic_msm_ctrl_events_show, inode->i_private);
|
|
}
|
|
|
|
const struct file_operations ehci_hsic_msm_dbg_ctrl_fops = {
|
|
.open = ehci_hsic_msm_ctrl_events_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static struct dentry *ehci_hsic_msm_dbg_root;
|
|
static int ehci_hsic_msm_debugfs_init(struct msm_hsic_hcd *mehci)
|
|
{
|
|
struct dentry *ehci_hsic_msm_dentry;
|
|
|
|
ehci_hsic_msm_dbg_root = debugfs_create_dir("ehci_hsic_msm_dbg", NULL);
|
|
|
|
if (!ehci_hsic_msm_dbg_root || IS_ERR(ehci_hsic_msm_dbg_root))
|
|
return -ENODEV;
|
|
|
|
ehci_hsic_msm_dentry = debugfs_create_file("bus_voting",
|
|
S_IRUGO | S_IWUSR,
|
|
ehci_hsic_msm_dbg_root, mehci,
|
|
&ehci_hsic_msm_bus_fops);
|
|
|
|
if (!ehci_hsic_msm_dentry) {
|
|
debugfs_remove_recursive(ehci_hsic_msm_dbg_root);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ehci_hsic_msm_dentry = debugfs_create_file("wakeup_cnt",
|
|
S_IRUGO,
|
|
ehci_hsic_msm_dbg_root, mehci,
|
|
&ehci_hsic_msm_wakeup_cnt_fops);
|
|
|
|
if (!ehci_hsic_msm_dentry) {
|
|
debugfs_remove_recursive(ehci_hsic_msm_dbg_root);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ehci_hsic_msm_dentry = debugfs_create_file("show_ctrl_events",
|
|
S_IRUGO,
|
|
ehci_hsic_msm_dbg_root, mehci,
|
|
&ehci_hsic_msm_dbg_ctrl_fops);
|
|
|
|
if (!ehci_hsic_msm_dentry) {
|
|
debugfs_remove_recursive(ehci_hsic_msm_dbg_root);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ehci_hsic_msm_dentry = debugfs_create_file("show_data_events",
|
|
S_IRUGO,
|
|
ehci_hsic_msm_dbg_root, mehci,
|
|
&ehci_hsic_msm_dbg_data_fops);
|
|
|
|
if (!ehci_hsic_msm_dentry) {
|
|
debugfs_remove_recursive(ehci_hsic_msm_dbg_root);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ehci_hsic_msm_debugfs_cleanup(void)
|
|
{
|
|
debugfs_remove_recursive(ehci_hsic_msm_dbg_root);
|
|
}
|
|
|
|
static int __devinit ehci_hsic_msm_probe(struct platform_device *pdev)
|
|
{
|
|
struct usb_hcd *hcd;
|
|
struct resource *res;
|
|
struct msm_hsic_hcd *mehci;
|
|
struct msm_hsic_host_platform_data *pdata;
|
|
int ret;
|
|
|
|
dev_dbg(&pdev->dev, "ehci_msm-hsic probe\n");
|
|
|
|
/* After parent device's probe is executed, it will be put in suspend
|
|
* mode. When child device's probe is called, driver core is not
|
|
* resuming parent device due to which parent will be in suspend even
|
|
* though child is active. Hence resume the parent device explicitly.
|
|
*/
|
|
if (pdev->dev.parent)
|
|
pm_runtime_get_sync(pdev->dev.parent);
|
|
|
|
hcd = usb_create_hcd(&msm_hsic_driver, &pdev->dev,
|
|
dev_name(&pdev->dev));
|
|
if (!hcd) {
|
|
dev_err(&pdev->dev, "Unable to create HCD\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
hcd->irq = platform_get_irq(pdev, 0);
|
|
if (hcd->irq < 0) {
|
|
dev_err(&pdev->dev, "Unable to get IRQ resource\n");
|
|
ret = hcd->irq;
|
|
goto put_hcd;
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(&pdev->dev, "Unable to get memory resource\n");
|
|
ret = -ENODEV;
|
|
goto put_hcd;
|
|
}
|
|
|
|
hcd->rsrc_start = res->start;
|
|
hcd->rsrc_len = resource_size(res);
|
|
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
|
if (!hcd->regs) {
|
|
dev_err(&pdev->dev, "ioremap failed\n");
|
|
ret = -ENOMEM;
|
|
goto put_hcd;
|
|
}
|
|
|
|
mehci = hcd_to_hsic(hcd);
|
|
mehci->dev = &pdev->dev;
|
|
|
|
mehci->ehci.susp_sof_bug = 1;
|
|
|
|
mehci->ehci.max_log2_irq_thresh = 6;
|
|
|
|
res = platform_get_resource_byname(pdev,
|
|
IORESOURCE_IRQ,
|
|
"peripheral_status_irq");
|
|
if (res)
|
|
mehci->peripheral_status_irq = res->start;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_IO, "wakeup");
|
|
if (res) {
|
|
mehci->wakeup_gpio = res->start;
|
|
mehci->wakeup_irq = MSM_GPIO_TO_INT(res->start);
|
|
dev_dbg(mehci->dev, "wakeup_irq: %d\n", mehci->wakeup_irq);
|
|
}
|
|
|
|
ret = msm_hsic_init_clocks(mehci, 1);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "unable to initialize clocks\n");
|
|
ret = -ENODEV;
|
|
goto unmap;
|
|
}
|
|
|
|
ret = msm_hsic_init_vddcx(mehci, 1);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "unable to initialize VDDCX\n");
|
|
ret = -ENODEV;
|
|
goto deinit_clocks;
|
|
}
|
|
|
|
ret = msm_hsic_reset(mehci);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "unable to initialize PHY\n");
|
|
goto deinit_vddcx;
|
|
}
|
|
|
|
ret = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "unable to register HCD\n");
|
|
goto unconfig_gpio;
|
|
}
|
|
|
|
device_init_wakeup(&pdev->dev, 1);
|
|
wake_lock_init(&mehci->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev));
|
|
wake_lock(&mehci->wlock);
|
|
|
|
if (mehci->peripheral_status_irq) {
|
|
ret = request_threaded_irq(mehci->peripheral_status_irq,
|
|
NULL, hsic_peripheral_status_change,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
|
|
| IRQF_SHARED,
|
|
"hsic_peripheral_status", mehci);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "%s:request_irq:%d failed:%d",
|
|
__func__, mehci->peripheral_status_irq, ret);
|
|
}
|
|
|
|
/* configure wakeup irq */
|
|
if (mehci->wakeup_irq) {
|
|
ret = request_irq(mehci->wakeup_irq, msm_hsic_wakeup_irq,
|
|
IRQF_TRIGGER_HIGH,
|
|
"msm_hsic_wakeup", mehci);
|
|
if (!ret) {
|
|
disable_irq_nosync(mehci->wakeup_irq);
|
|
} else {
|
|
dev_err(&pdev->dev, "request_irq(%d) failed: %d\n",
|
|
mehci->wakeup_irq, ret);
|
|
mehci->wakeup_irq = 0;
|
|
}
|
|
}
|
|
|
|
ret = ehci_hsic_msm_debugfs_init(mehci);
|
|
if (ret)
|
|
dev_dbg(&pdev->dev, "mode debugfs file is"
|
|
"not available\n");
|
|
|
|
pdata = mehci->dev->platform_data;
|
|
if (pdata && pdata->bus_scale_table) {
|
|
mehci->bus_perf_client =
|
|
msm_bus_scale_register_client(pdata->bus_scale_table);
|
|
/* Configure BUS performance parameters for MAX bandwidth */
|
|
if (mehci->bus_perf_client) {
|
|
ret = msm_bus_scale_client_update_request(
|
|
mehci->bus_perf_client, 1);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "%s: Failed to vote for "
|
|
"bus bandwidth %d\n", __func__, ret);
|
|
} else {
|
|
dev_err(&pdev->dev, "%s: Failed to register BUS "
|
|
"scaling client!!\n", __func__);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This pdev->dev is assigned parent of root-hub by USB core,
|
|
* hence, runtime framework automatically calls this driver's
|
|
* runtime APIs based on root-hub's state.
|
|
*/
|
|
pm_runtime_set_active(&pdev->dev);
|
|
pm_runtime_enable(&pdev->dev);
|
|
/* Decrement the parent device's counter after probe.
|
|
* As child is active, parent will not be put into
|
|
* suspend mode.
|
|
*/
|
|
if (pdev->dev.parent)
|
|
pm_runtime_put_sync(pdev->dev.parent);
|
|
|
|
return 0;
|
|
|
|
unconfig_gpio:
|
|
msm_hsic_config_gpios(mehci, 0);
|
|
deinit_vddcx:
|
|
msm_hsic_init_vddcx(mehci, 0);
|
|
deinit_clocks:
|
|
msm_hsic_init_clocks(mehci, 0);
|
|
unmap:
|
|
iounmap(hcd->regs);
|
|
put_hcd:
|
|
usb_put_hcd(hcd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __devexit ehci_hsic_msm_remove(struct platform_device *pdev)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
|
struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
|
|
|
|
if (mehci->peripheral_status_irq)
|
|
free_irq(mehci->peripheral_status_irq, mehci);
|
|
|
|
if (mehci->wakeup_irq) {
|
|
if (mehci->wakeup_irq_enabled)
|
|
disable_irq_wake(mehci->wakeup_irq);
|
|
free_irq(mehci->wakeup_irq, mehci);
|
|
}
|
|
|
|
if (mehci->bus_perf_client)
|
|
msm_bus_scale_unregister_client(mehci->bus_perf_client);
|
|
|
|
ehci_hsic_msm_debugfs_cleanup();
|
|
device_init_wakeup(&pdev->dev, 0);
|
|
pm_runtime_set_suspended(&pdev->dev);
|
|
|
|
usb_remove_hcd(hcd);
|
|
msm_hsic_config_gpios(mehci, 0);
|
|
msm_hsic_init_vddcx(mehci, 0);
|
|
|
|
msm_hsic_init_clocks(mehci, 0);
|
|
wake_lock_destroy(&mehci->wlock);
|
|
iounmap(hcd->regs);
|
|
usb_put_hcd(hcd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int msm_hsic_pm_suspend(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
|
|
|
|
dev_dbg(dev, "ehci-msm-hsic PM suspend\n");
|
|
|
|
dbg_log_event(NULL, "PM Suspend", 0);
|
|
|
|
if (device_may_wakeup(dev))
|
|
enable_irq_wake(hcd->irq);
|
|
|
|
ret = msm_hsic_suspend(mehci);
|
|
|
|
if (ret && device_may_wakeup(dev))
|
|
disable_irq_wake(hcd->irq);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int msm_hsic_pm_resume(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
|
|
|
|
dbg_log_event(NULL, "PM Resume", 0);
|
|
|
|
if (device_may_wakeup(dev))
|
|
disable_irq_wake(hcd->irq);
|
|
|
|
ret = msm_hsic_resume(mehci);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Bring the device to full powered state upon system resume */
|
|
pm_runtime_disable(dev);
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PM_RUNTIME
|
|
static int msm_hsic_runtime_idle(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "EHCI runtime idle\n");
|
|
return 0;
|
|
}
|
|
|
|
static int msm_hsic_runtime_suspend(struct device *dev)
|
|
{
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
|
|
|
|
dev_dbg(dev, "EHCI runtime suspend\n");
|
|
|
|
dbg_log_event(NULL, "Run Time PM Suspend", 0);
|
|
|
|
return msm_hsic_suspend(mehci);
|
|
}
|
|
|
|
static int msm_hsic_runtime_resume(struct device *dev)
|
|
{
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
|
|
|
|
dev_dbg(dev, "EHCI runtime resume\n");
|
|
|
|
dbg_log_event(NULL, "Run Time PM Resume", 0);
|
|
|
|
return msm_hsic_resume(mehci);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PM
|
|
static const struct dev_pm_ops msm_hsic_dev_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(msm_hsic_pm_suspend, msm_hsic_pm_resume)
|
|
SET_RUNTIME_PM_OPS(msm_hsic_runtime_suspend, msm_hsic_runtime_resume,
|
|
msm_hsic_runtime_idle)
|
|
};
|
|
#endif
|
|
|
|
static struct platform_driver ehci_msm_hsic_driver = {
|
|
.probe = ehci_hsic_msm_probe,
|
|
.remove = __devexit_p(ehci_hsic_msm_remove),
|
|
.driver = {
|
|
.name = "msm_hsic_host",
|
|
#ifdef CONFIG_PM
|
|
.pm = &msm_hsic_dev_pm_ops,
|
|
#endif
|
|
},
|
|
};
|