blob: eebd9d426781aa1db0c86bf728545cc7ef89e582 [file] [log] [blame]
/* drivers/usb/function/msm_hsusb.c
*
* Driver for HighSpeed USB Client Controller in MSM7K
*
* Copyright (C) 2007 Google, Inc.
* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved.
* Author: Brian Swetland <swetland@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/platform_device.h>
#include <linux/debugfs.h>
#include <linux/workqueue.h>
#include <linux/clk.h>
#include <linux/spinlock.h>
#include <linux/switch.h>
#include <linux/usb/ch9.h>
#include <linux/io.h>
#include <asm/mach-types.h>
#include <mach/vreg.h>
#include <mach/board.h>
#include <mach/msm_hsusb.h>
#include <mach/rpc_hsusb.h>
#include <mach/rpc_pmapp.h>
#include <mach/gpio.h>
#include <mach/msm_hsusb_hw.h>
#include <mach/msm_otg.h>
#include <linux/wakelock.h>
#include <linux/pm_qos_params.h>
#include <mach/clk.h>
#define MSM_USB_BASE ((unsigned) ui->addr)
#include "usb_function.h"
#define EPT_FLAG_IN 0x0001
#define USB_DIR_MASK USB_DIR_IN
#define SETUP_BUF_SIZE 4096
/* IDs for string descriptors */
#define STRING_LANGUAGE_ID 0
#define STRING_SERIAL 1
#define STRING_PRODUCT 2
#define STRING_MANUFACTURER 3
#define LANGUAGE_ID 0x0409 /* en-US */
#define SOC_ROC_2_0 0x10002 /* ROC 2.0 */
#define TRUE 1
#define FALSE 0
#define USB_LINK_RESET_TIMEOUT (msecs_to_jiffies(10))
#define USB_CHG_DET_DELAY msecs_to_jiffies(1000)
#define is_phy_45nm() (PHY_MODEL(ui->phy_info) == USB_PHY_MODEL_45NM)
#define is_phy_external() (PHY_TYPE(ui->phy_info) == USB_PHY_EXTERNAL)
static int pid = 0x9018;
struct usb_fi_ept {
struct usb_endpoint *ept;
struct usb_endpoint_descriptor desc;
};
struct usb_function_info {
struct list_head list;
unsigned enabled;
struct usb_function *func;
};
struct msm_request {
struct usb_request req;
struct usb_info *ui;
struct msm_request *next;
unsigned busy:1;
unsigned live:1;
unsigned alloced:1;
unsigned dead:1;
dma_addr_t dma;
struct ept_queue_item *item;
dma_addr_t item_dma;
};
static unsigned char str_lang_desc[] = {4,
USB_DT_STRING,
(unsigned char)LANGUAGE_ID,
(unsigned char)(LANGUAGE_ID >> 8)};
#define to_msm_request(r) container_of(r, struct msm_request, req)
static int usb_hw_reset(struct usb_info *ui);
static void usb_vbus_online(struct usb_info *);
static void usb_vbus_offline(struct usb_info *ui);
static void usb_lpm_exit(struct usb_info *ui);
static void usb_lpm_wakeup_phy(struct work_struct *);
static void usb_exit(void);
static int usb_is_online(struct usb_info *ui);
static void usb_do_work(struct work_struct *w);
static int usb_lpm_enter(struct usb_info *ui);
int (*usb_lpm_config_gpio)(int);
static void usb_enable_pullup(struct usb_info *ui);
static void usb_disable_pullup(struct usb_info *ui);
static struct workqueue_struct *usb_work;
static void usb_chg_stop(struct work_struct *w);
#define USB_STATE_IDLE 0
#define USB_STATE_ONLINE 1
#define USB_STATE_OFFLINE 2
#define USB_FLAG_START 0x0001
#define USB_FLAG_VBUS_ONLINE 0x0002
#define USB_FLAG_VBUS_OFFLINE 0x0004
#define USB_FLAG_RESET 0x0008
#define USB_FLAG_SUSPEND 0x0010
#define USB_FLAG_CONFIGURE 0x0020
#define USB_FLAG_RESUME 0x0040
#define USB_FLAG_REG_OTG 0x0080
#define USB_MSC_ONLY_FUNC_MAP 0x10
#define DRIVER_NAME "msm_hsusb_peripheral"
struct lpm_info {
struct work_struct wakeup_phy;
};
enum charger_type {
USB_CHG_TYPE__SDP,
USB_CHG_TYPE__CARKIT,
USB_CHG_TYPE__WALLCHARGER,
USB_CHG_TYPE__INVALID
};
struct usb_info {
/* lock for register/queue/device state changes */
spinlock_t lock;
/* single request used for handling setup transactions */
struct usb_request *setup_req;
struct usb_request *ep0out_req;
struct platform_device *pdev;
struct msm_hsusb_platform_data *pdata;
int irq;
int gpio_irq[2];
void *addr;
unsigned state;
unsigned flags;
unsigned online;
unsigned running;
unsigned bound;
struct dma_pool *pool;
/* dma page to back the queue heads and items */
unsigned char *buf;
dma_addr_t dma;
struct ept_queue_head *head;
/* used for allocation */
unsigned next_item;
unsigned next_ifc_num;
unsigned stopped:1;
unsigned remote_wakeup:1;
unsigned configured:1;
unsigned selfpowered:1;
unsigned iad:1;
unsigned char maxpower;
enum usb_device_speed speed;
unsigned phy_info;
/* endpoints are ordered based on their status bits,
** so they are OUT0, OUT1, ... OUT15, IN0, IN1, ... IN15
*/
struct usb_endpoint ept[32];
struct delayed_work work;
struct delayed_work chg_legacy_det;
unsigned phy_status;
unsigned phy_fail_count;
struct usb_composition *composition;
struct usb_function_info **func;
unsigned num_funcs;
struct usb_function_map *functions_map;
#define MAX_INTERFACE_NUM 15
struct usb_function *func2ifc_map[MAX_INTERFACE_NUM];
#define ep0out ept[0]
#define ep0in ept[16]
struct clk *clk;
struct clk *pclk;
struct clk *cclk;
unsigned int clk_enabled;
struct vreg *vreg;
unsigned int vreg_enabled;
unsigned in_lpm;
struct lpm_info li;
enum charger_type chg_type;
struct work_struct chg_stop;
#define MAX_STRDESC_NUM 100
char **strdesc;
int strdesc_index;
u16 test_mode;
struct wake_lock wlock;
struct msm_otg_transceiver *xceiv;
int active;
enum usb_device_state usb_state;
int vbus_sn_notif;
struct switch_dev sdev;
};
static struct usb_info *the_usb_info;
static unsigned short usb_validate_product_id(unsigned short pid);
static unsigned short usb_get_product_id(unsigned long enabled_functions);
static void usb_switch_composition(unsigned short pid);
static unsigned short usb_set_composition(unsigned short pid);
static void usb_configure_device_descriptor(struct usb_info *ui);
static void usb_uninit(struct usb_info *ui);
static unsigned ulpi_read(struct usb_info *ui, unsigned reg);
static int ulpi_write(struct usb_info *ui, unsigned val, unsigned reg);
struct usb_device_descriptor desc_device = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 64,
/* the following fields are filled in by usb_probe */
.idVendor = 0,
.idProduct = 0,
.bcdDevice = 0,
.iManufacturer = 0,
.iProduct = 0,
.iSerialNumber = 0,
.bNumConfigurations = 1,
};
static void flush_endpoint(struct usb_endpoint *ept);
static void msm_hsusb_suspend_locks_acquire(struct usb_info *, int);
static ssize_t print_switch_name(struct switch_dev *sdev, char *buf)
{
return sprintf(buf, "%s\n", DRIVER_NAME);
}
static ssize_t print_switch_state(struct switch_dev *sdev, char *buf)
{
struct usb_info *ui = the_usb_info;
return sprintf(buf, "%s\n", (ui->online ? "online" : "offline"));
}
#define USB_WALLCHARGER_CHG_CURRENT 1800
static int usb_get_max_power(struct usb_info *ui)
{
unsigned long flags;
enum charger_type temp;
int suspended;
int configured;
spin_lock_irqsave(&ui->lock, flags);
temp = ui->chg_type;
suspended = ui->usb_state == USB_STATE_SUSPENDED ? 1 : 0;
configured = ui->configured;
spin_unlock_irqrestore(&ui->lock, flags);
if (temp == USB_CHG_TYPE__INVALID)
return -ENODEV;
if (temp == USB_CHG_TYPE__WALLCHARGER)
return USB_WALLCHARGER_CHG_CURRENT;
if (suspended || !configured)
return 0;
return ui->maxpower * 2;
}
static void usb_chg_legacy_detect(struct work_struct *w)
{
struct usb_info *ui = the_usb_info;
unsigned long flags;
enum charger_type temp = USB_CHG_TYPE__INVALID;
int maxpower;
int ret = 0;
spin_lock_irqsave(&ui->lock, flags);
if (ui->usb_state == USB_STATE_NOTATTACHED) {
ret = -ENODEV;
goto chg_legacy_det_out;
}
if ((readl(USB_PORTSC) & PORTSC_LS) == PORTSC_LS) {
ui->chg_type = temp = USB_CHG_TYPE__WALLCHARGER;
goto chg_legacy_det_out;
}
ui->chg_type = temp = USB_CHG_TYPE__SDP;
chg_legacy_det_out:
spin_unlock_irqrestore(&ui->lock, flags);
if (ret)
return;
msm_chg_usb_charger_connected(temp);
maxpower = usb_get_max_power(ui);
if (maxpower > 0)
msm_chg_usb_i_is_available(maxpower);
/* USB driver prevents idle and suspend power collapse(pc)
* while usb cable is connected. But when dedicated charger is
* connected, driver can vote for idle and suspend pc. In order
* to allow pc, driver has to initiate low power mode which it
* cannot do as phy cannot be accessed when dedicated charger
* is connected due to phy lockup issues. Just to allow idle &
* suspend pc when dedicated charger is connected, release the
* wakelock, set driver latency to default and act as if we are
* in low power mode so that, driver will re-acquire wakelocks
* for any sub-sequent usb interrupts.
*/
if (temp == USB_CHG_TYPE__WALLCHARGER) {
pr_info("\n%s: WALL-CHARGER\n", __func__);
spin_lock_irqsave(&ui->lock, flags);
if (ui->usb_state == USB_STATE_NOTATTACHED) {
spin_unlock_irqrestore(&ui->lock, flags);
return;
}
ui->in_lpm = 1;
spin_unlock_irqrestore(&ui->lock, flags);
msm_hsusb_suspend_locks_acquire(ui, 0);
} else
pr_info("\n%s: Standard Downstream Port\n", __func__);
}
int usb_msm_get_next_strdesc_id(char *str)
{
struct usb_info *ui = the_usb_info;
unsigned id;
unsigned long flags;
int len;
len = strlen(str);
if (!len) {
printk(KERN_ERR "usb next_strdesc_id(); null string\n");
return -EPERM;
}
/* for null character */
len = len + 1;
spin_lock_irqsave(&ui->lock, flags);
id = ui->strdesc_index;
if (id >= MAX_STRDESC_NUM) {
id = -EPERM;
printk(KERN_ERR "reached max strdesc number\n");
goto get_strd_id_exit;
}
ui->strdesc[id] = kmalloc(len, GFP_ATOMIC);
if (ui->strdesc[id]) {
memcpy(ui->strdesc[id], str, len);
ui->strdesc_index++;
} else {
id = -EPERM;
printk(KERN_ERR "usb next_strdesc_id(); Out of memory:(%s)\n",
str);
}
get_strd_id_exit:
spin_unlock_irqrestore(&ui->lock, flags);
return id;
}
EXPORT_SYMBOL(usb_msm_get_next_strdesc_id);
inline int usb_msm_is_iad(void)
{
return the_usb_info->iad;
}
EXPORT_SYMBOL(usb_msm_is_iad);
inline void usb_msm_enable_iad(void)
{
the_usb_info->iad = 1;
}
EXPORT_SYMBOL(usb_msm_enable_iad);
int usb_msm_get_speed()
{
return the_usb_info->speed;
}
EXPORT_SYMBOL(usb_msm_get_speed);
int usb_msm_get_next_ifc_number(struct usb_function *driver)
{
struct usb_info *ui = the_usb_info;
int ifc_num = -1;
unsigned long flags;
int i;
spin_lock_irqsave(&ui->lock, flags);
for (i = 0; i < ui->pdata->num_functions; i++) {
if (strcmp(ui->functions_map[i].name, driver->name))
continue;
if (!(ui->composition->functions & (1 << i)))
continue;
ifc_num = ui->next_ifc_num++;
ui->func2ifc_map[ifc_num] = driver;
break;
}
spin_unlock_irqrestore(&ui->lock, flags);
return ifc_num;
}
EXPORT_SYMBOL(usb_msm_get_next_ifc_number);
static inline int usb_msm_get_selfpowered(void)
{
struct usb_info *ui = the_usb_info;
return ui->selfpowered;
}
static inline int usb_msm_get_remotewakeup(void)
{
struct usb_info *ui = the_usb_info;
return ui->remote_wakeup;
}
static void usb_clk_enable(struct usb_info *ui)
{
if (!ui->clk_enabled) {
clk_enable(ui->pclk);
if (ui->cclk)
clk_enable(ui->cclk);
ui->clk_enabled = 1;
}
}
static void usb_clk_disable(struct usb_info *ui)
{
if (ui->clk_enabled) {
clk_disable(ui->pclk);
if (ui->cclk)
clk_disable(ui->cclk);
ui->clk_enabled = 0;
}
}
static void usb_vreg_enable(struct usb_info *ui)
{
if (ui->vreg && !IS_ERR(ui->vreg) && !ui->vreg_enabled) {
vreg_enable(ui->vreg);
ui->vreg_enabled = 1;
}
}
static void usb_vreg_disable(struct usb_info *ui)
{
if (ui->vreg && !IS_ERR(ui->vreg) && ui->vreg_enabled) {
vreg_disable(ui->vreg);
ui->vreg_enabled = 0;
}
}
static unsigned ulpi_read(struct usb_info *ui, unsigned reg)
{
unsigned timeout = 100000;
/* initiate read operation */
writel(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg),
USB_ULPI_VIEWPORT);
/* wait for completion */
while ((readl(USB_ULPI_VIEWPORT) & ULPI_RUN) && (--timeout)) ;
if (timeout == 0) {
printk(KERN_ERR "ulpi_read: timeout %08x\n",
readl(USB_ULPI_VIEWPORT));
return 0xffffffff;
}
return ULPI_DATA_READ(readl(USB_ULPI_VIEWPORT));
}
static int ulpi_write(struct usb_info *ui, unsigned val, unsigned reg)
{
unsigned timeout = 10000;
/* initiate write operation */
writel(ULPI_RUN | ULPI_WRITE |
ULPI_ADDR(reg) | ULPI_DATA(val),
USB_ULPI_VIEWPORT);
/* wait for completion */
while((readl(USB_ULPI_VIEWPORT) & ULPI_RUN) && (--timeout)) ;
if (timeout == 0) {
printk(KERN_ERR "ulpi_write: timeout\n");
return -1;
}
return 0;
}
static void msm_hsusb_suspend_locks_acquire(struct usb_info *ui, int acquire)
{
if (acquire) {
wake_lock(&ui->wlock);
pm_qos_update_requirement(PM_QOS_CPU_DMA_LATENCY,
DRIVER_NAME, ui->pdata->swfi_latency);
/* targets like 7x30 have introduced core clock
* to remove the dependency on max axi frequency
*/
if (!ui->cclk)
pm_qos_update_requirement(PM_QOS_SYSTEM_BUS_FREQ,
DRIVER_NAME, MSM_AXI_MAX_FREQ);
} else {
wake_lock_timeout(&ui->wlock, HZ / 2);
pm_qos_update_requirement(PM_QOS_CPU_DMA_LATENCY,
DRIVER_NAME,
PM_QOS_DEFAULT_VALUE);
if (!ui->cclk)
pm_qos_update_requirement(PM_QOS_SYSTEM_BUS_FREQ,
DRIVER_NAME, PM_QOS_DEFAULT_VALUE);
}
}
static void msm_hsusb_suspend_locks_init(struct usb_info *ui, int init)
{
if (init) {
wake_lock_init(&ui->wlock, WAKE_LOCK_SUSPEND,
"usb_bus_active");
pm_qos_add_requirement(PM_QOS_CPU_DMA_LATENCY,
DRIVER_NAME,
PM_QOS_DEFAULT_VALUE);
pm_qos_add_requirement(PM_QOS_SYSTEM_BUS_FREQ,
DRIVER_NAME, PM_QOS_DEFAULT_VALUE);
} else {
wake_lock_destroy(&ui->wlock);
pm_qos_remove_requirement(PM_QOS_CPU_DMA_LATENCY, DRIVER_NAME);
pm_qos_remove_requirement(PM_QOS_SYSTEM_BUS_FREQ, DRIVER_NAME);
}
}
static void init_endpoints(struct usb_info *ui)
{
unsigned n;
for (n = 0; n < 32; n++) {
struct usb_endpoint *ept = ui->ept + n;
ept->ui = ui;
ept->bit = n;
ept->num = n & 15;
ept->alloced = 0;
if (ept->bit > 15) {
/* IN endpoint */
ept->head = ui->head + (ept->num << 1) + 1;
ept->flags = EPT_FLAG_IN;
} else {
/* OUT endpoint */
ept->head = ui->head + (ept->num << 1);
ept->flags = 0;
}
}
}
void usb_configure_endpoint(struct usb_endpoint *ep,
struct usb_endpoint_descriptor *ep_desc)
{
unsigned cfg = 0;
unsigned long flags;
struct usb_info *ui = ep->ui;
if (!ui)
return;
spin_lock_irqsave(&ui->lock, flags);
if (ep_desc) {
ep->max_pkt = ep_desc->wMaxPacketSize;
ep->ep_descriptor = ep_desc;
}
if (!ep->max_pkt) {
printk(KERN_ERR "cannot configure zero length max pkt\n");
goto cfg_ept_end;
}
cfg = CONFIG_MAX_PKT(ep->max_pkt) | CONFIG_ZLT;
/* ep0 out needs interrupt-on-setup */
if (ep->bit == 0)
cfg |= CONFIG_IOS;
ep->head->config = cfg;
ep->head->next = TERMINATE;
pr_debug("ept #%d %s max:%d head:%p bit:%d\n",
ep->num,
(ep->flags & EPT_FLAG_IN) ? "in" : "out",
ep->max_pkt, ep->head, ep->bit);
cfg_ept_end:
spin_unlock_irqrestore(&ui->lock, flags);
}
EXPORT_SYMBOL(usb_configure_endpoint);
#define NUM_EPTS 15 /* number of in or out non-ctrl endpoints */
struct usb_endpoint *usb_alloc_endpoint(unsigned direction)
{
struct usb_info *ui = the_usb_info;
struct usb_endpoint *ept = NULL;
int i;
unsigned long flags;
spin_lock_irqsave(&ui->lock, flags);
if (direction & USB_DIR_IN)
ept = (&ui->ep0in);
else
ept = (&ui->ep0out);
for (i = 0; i < NUM_EPTS; i++) {
ept++;
if (!ept->alloced) {
ept->alloced = 1;
ept->ui = ui;
spin_unlock_irqrestore(&ui->lock, flags);
return ept;
}
}
spin_unlock_irqrestore(&ui->lock, flags);
return NULL;
}
EXPORT_SYMBOL(usb_alloc_endpoint);
int usb_free_endpoint(struct usb_endpoint *ept)
{
struct usb_info *ui = the_usb_info;
unsigned long flags;
if (!ept)
return -EINVAL;
spin_lock_irqsave(&ui->lock, flags);
ept->alloced = 0;
ept->ui = 0;
spin_unlock_irqrestore(&ui->lock, flags);
return 0;
}
EXPORT_SYMBOL(usb_free_endpoint);
struct usb_request *usb_ept_alloc_req(struct usb_endpoint *ept,
unsigned bufsize)
{
struct usb_info *ui = ept->ui;
struct msm_request *req;
if (!ui)
return NULL;
req = kzalloc(sizeof(*req), GFP_ATOMIC);
if (!req)
goto fail1;
req->item = dma_pool_alloc(ui->pool, GFP_ATOMIC, &req->item_dma);
if (!req->item)
goto fail2;
if (bufsize) {
req->req.buf = kmalloc(bufsize, GFP_ATOMIC);
if (!req->req.buf)
goto fail3;
req->alloced = 1;
}
return &req->req;
fail3:
dma_pool_free(ui->pool, req->item, req->item_dma);
fail2:
kfree(req);
fail1:
return NULL;
}
EXPORT_SYMBOL(usb_ept_alloc_req);
static void do_free_req(struct usb_info *ui, struct msm_request *req)
{
if (req->alloced)
kfree(req->req.buf);
dma_pool_free(ui->pool, req->item, req->item_dma);
kfree(req);
}
void usb_ept_free_req(struct usb_endpoint *ept, struct usb_request *_req)
{
struct msm_request *req, *temp_req, *prev_req;
struct usb_info *ui;
unsigned long flags;
int dead = 0;
if (!ept || !_req)
return;
ui = ept->ui;
if (!ui)
return;
req = to_msm_request(_req);
spin_lock_irqsave(&ui->lock, flags);
/* defer freeing resources if request is still busy */
if (req->busy)
dead = req->dead = 1;
spin_unlock_irqrestore(&ui->lock, flags);
/* if req->dead, then we will clean up when the request finishes */
if (!dead) {
temp_req = ept->req;
prev_req = temp_req;
while (temp_req != NULL) {
if (req == temp_req && ept->req != temp_req)
prev_req->next = temp_req->next;
prev_req = temp_req;
temp_req = temp_req->next;
}
if (ept->req == req)
ept->req = req->next;
req->req.complete = NULL;
do_free_req(ui, req);
} else
pr_err("%s: req is busy, can't free req\n", __func__);
}
EXPORT_SYMBOL(usb_ept_free_req);
void usb_ept_enable(struct usb_endpoint *ept, int yes)
{
struct usb_info *ui;
int in;
unsigned n;
unsigned char xfer;
if (!ept || !ept->ui)
return;
ui = ept->ui;
in = ept->flags & EPT_FLAG_IN;
if (!ept->ep_descriptor)
return;
if (ui->in_lpm) {
pr_err("%s: controller is in lpm, cannot proceed\n", __func__);
return;
}
xfer = ept->ep_descriptor->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
n = readl(USB_ENDPTCTRL(ept->num));
if (in) {
if (xfer == USB_ENDPOINT_XFER_BULK)
n = (n & (~CTRL_TXT_MASK)) | CTRL_TXT_BULK;
else if (xfer == USB_ENDPOINT_XFER_INT)
n = (n & (~CTRL_TXT_MASK)) | CTRL_TXT_INT;
if (yes)
n |= CTRL_TXE | CTRL_TXR;
else
n &= (~CTRL_TXE);
} else {
if (xfer == USB_ENDPOINT_XFER_BULK)
n = (n & (~CTRL_RXT_MASK)) | CTRL_RXT_BULK;
else if (xfer == USB_ENDPOINT_XFER_INT)
n = (n & (~CTRL_RXT_MASK)) | CTRL_RXT_INT;
if (yes)
n |= CTRL_RXE | CTRL_RXR;
else
n &= ~(CTRL_RXE);
}
/* complete all the updates to ept->head before enabling endpoint*/
dma_coherent_pre_ops();
writel(n, USB_ENDPTCTRL(ept->num));
}
EXPORT_SYMBOL(usb_ept_enable);
static void usb_ept_start(struct usb_endpoint *ept)
{
struct usb_info *ui = ept->ui;
struct msm_request *req = ept->req;
BUG_ON(req->live);
/* link the hw queue head to the request's transaction item */
ept->head->next = req->item_dma;
ept->head->info = 0;
/* memory barrier to flush the data before priming endpoint*/
dma_coherent_pre_ops();
/* start the endpoint */
writel(1 << ept->bit, USB_ENDPTPRIME);
/* mark this chain of requests as live */
while (req) {
req->live = 1;
if (req->item->next == TERMINATE)
break;
req = req->next;
}
}
int usb_ept_queue_xfer(struct usb_endpoint *ept, struct usb_request *_req)
{
unsigned long flags;
struct msm_request *req = to_msm_request(_req);
struct msm_request *last;
struct usb_info *ui = ept->ui;
struct ept_queue_item *item = req->item;
unsigned length = req->req.length;
if (length > 0x4000)
return -EMSGSIZE;
if (ui->in_lpm) {
req->req.status = usb_remote_wakeup();
if (req->req.status) {
pr_debug("%s:RWakeup generation failed, EP = %x\n",
__func__, ept->bit);
return req->req.status;
}
}
spin_lock_irqsave(&ui->lock, flags);
if (req->busy) {
req->req.status = -EBUSY;
spin_unlock_irqrestore(&ui->lock, flags);
printk(KERN_INFO
"usb_ept_queue_xfer() tried to queue busy request\n");
return -EBUSY;
}
if (!ui->online && (ept->num != 0)) {
req->req.status = -ENODEV;
spin_unlock_irqrestore(&ui->lock, flags);
printk(KERN_INFO "usb_ept_queue_xfer() tried to queue request"
"while offline; ept->bit: %x\n", ept->bit);
return -ENODEV;
}
req->busy = 1;
req->live = 0;
req->next = 0;
req->req.status = -EBUSY;
req->dma = dma_map_single(NULL, req->req.buf, length,
(ept->flags & EPT_FLAG_IN) ?
DMA_TO_DEVICE : DMA_FROM_DEVICE);
/* prepare the transaction descriptor item for the hardware */
item->next = TERMINATE;
item->info = INFO_BYTES(length) | INFO_IOC | INFO_ACTIVE;
item->page0 = req->dma;
item->page1 = (req->dma + 0x1000) & 0xfffff000;
item->page2 = (req->dma + 0x2000) & 0xfffff000;
item->page3 = (req->dma + 0x3000) & 0xfffff000;
/* Add the new request to the end of the queue */
last = ept->last;
if (last) {
/* Already requests in the queue. add us to the
* end, but let the completion interrupt actually
* start things going, to avoid hw issues
*/
last->next = req;
/* only modify the hw transaction next pointer if
* that request is not live
*/
if (!last->live)
last->item->next = req->item_dma;
} else {
/* queue was empty -- kick the hardware */
ept->req = req;
usb_ept_start(ept);
}
ept->last = req;
spin_unlock_irqrestore(&ui->lock, flags);
return 0;
}
EXPORT_SYMBOL(usb_ept_queue_xfer);
int usb_ept_flush(struct usb_endpoint *ept)
{
printk("usb_ept_flush \n");
flush_endpoint(ept);
return 0;
}
int usb_ept_get_max_packet(struct usb_endpoint *ept)
{
return ept->max_pkt;
}
EXPORT_SYMBOL(usb_ept_get_max_packet);
int usb_remote_wakeup(void)
{
struct usb_info *ui = the_usb_info;
unsigned long flags;
spin_lock_irqsave(&ui->lock, flags);
if (!ui->remote_wakeup) {
spin_unlock_irqrestore(&ui->lock, flags);
pr_err("%s: remote wakeup not supported\n", __func__);
return -ENOTSUPP;
}
if (!ui->online) {
spin_unlock_irqrestore(&ui->lock, flags);
pr_err("%s: device is not configured\n", __func__);
return -ENODEV;
}
if (ui->in_lpm)
usb_lpm_exit(ui);
spin_unlock_irqrestore(&ui->lock, flags);
/* if usb_lpm_exit is unable to set PHCD,
* it would initiate workthread to set the PHCD
*/
if (cancel_work_sync(&ui->li.wakeup_phy))
usb_lpm_wakeup_phy(NULL);
spin_lock_irqsave(&ui->lock, flags);
if (ui->in_lpm) {
spin_unlock_irqrestore(&ui->lock, flags);
pr_err("%s: cannot bring controller out of lpm\n", __func__);
return -ENODEV;
}
if (!usb_is_online(ui)) {
pr_debug("%s: enabling force resume\n", __func__);
writel(readl(USB_PORTSC) | PORTSC_FPR, USB_PORTSC);
} else
pr_debug("%s: controller seems to be out of suspend already\n",
__func__);
spin_unlock_irqrestore(&ui->lock, flags);
return 0;
}
EXPORT_SYMBOL(usb_remote_wakeup);
/* --- endpoint 0 handling --- */
static void set_configuration(struct usb_info *ui, int yes)
{
unsigned i;
ui->online = !!yes;
for (i = 0; i < ui->num_funcs; i++) {
struct usb_function_info *fi = ui->func[i];
if (!fi || !(ui->composition->functions & (1 << i)))
continue;
if (fi->func->configure)
fi->func->configure(yes, fi->func->context);
}
}
static void ep0out_complete(struct usb_endpoint *ept, struct usb_request *req)
{
req->complete = 0;
}
static void ep0in_complete(struct usb_endpoint *ept, struct usb_request *req)
{
/* queue up the receive of the ACK response from the host */
if (req->status == 0) {
struct usb_info *ui = ept->ui;
req->length = 0;
req->complete = ep0out_complete;
usb_ept_queue_xfer(&ui->ep0out, req);
}
}
static void ep0in_complete_sendzero(
struct usb_endpoint *ept, struct usb_request *req)
{
if (req->status == 0) {
struct usb_info *ui = ept->ui;
req->length = 0;
req->complete = ep0in_complete;
usb_ept_queue_xfer(&ui->ep0in, req);
}
}
static void ep0_status_complete(
struct usb_endpoint *ept, struct usb_request *req)
{
struct usb_info *ui = ept->ui;
unsigned int i;
if (!ui->test_mode)
return;
switch (ui->test_mode) {
case J_TEST:
pr_info("usb electrical test mode: (J)\n");
i = readl(USB_PORTSC) & (~PORTSC_PTC);
writel(i | PORTSC_PTC_J_STATE, USB_PORTSC);
break;
case K_TEST:
pr_info("usb electrical test mode: (K)\n");
i = readl(USB_PORTSC) & (~PORTSC_PTC);
writel(i | PORTSC_PTC_K_STATE, USB_PORTSC);
break;
case SE0_NAK_TEST:
pr_info("usb electrical test mode: (SE0-NAK)\n");
i = readl(USB_PORTSC) & (~PORTSC_PTC);
writel(i | PORTSC_PTC_SE0_NAK, USB_PORTSC);
break;
case TST_PKT_TEST:
pr_info("usb electrical test mode: (TEST_PKT)\n");
i = readl(USB_PORTSC) & (~PORTSC_PTC);
writel(i | PORTSC_PTC_TST_PKT, USB_PORTSC);
break;
default:
pr_err("usb:%s: undefined test mode: (%x)\n",
__func__, ui->test_mode);
}
}
static void ep0_setup_ack(struct usb_info *ui)
{
struct usb_request *req = ui->setup_req;
req->length = 0;
req->complete = ep0_status_complete;
usb_ept_queue_xfer(&ui->ep0in, req);
}
static void ep0_setup_stall(struct usb_info *ui)
{
writel((1<<16) | (1<<0), USB_ENDPTCTRL(0));
}
static void ep0_setup_receive(struct usb_info *ui, unsigned len)
{
ui->ep0out_req->length = len;
usb_ept_queue_xfer(&ui->ep0out, ui->ep0out_req);
}
static void ep0_setup_send(struct usb_info *ui, unsigned wlen)
{
struct usb_request *req = ui->setup_req;
struct usb_endpoint *ept = &ui->ep0in;
/* never send more data than the host requested */
if (req->length > wlen)
req->length = wlen;
/* if we are sending a short response that ends on
* a packet boundary, we'll need to send a zero length
* packet as well.
*/
if ((req->length != wlen) && ((req->length & 63) == 0)) {
req->complete = ep0in_complete_sendzero;
} else {
req->complete = ep0in_complete;
}
usb_ept_queue_xfer(ept, req);
}
static int usb_find_descriptor(struct usb_info *ui, struct usb_ctrlrequest *ctl,
struct usb_request *req);
static void handle_setup(struct usb_info *ui)
{
struct usb_ctrlrequest ctl;
memcpy(&ctl, ui->ep0out.head->setup_data, sizeof(ctl));
writel(EPT_RX(0), USB_ENDPTSETUPSTAT);
/* any pending ep0 transactions must be canceled */
flush_endpoint(&ui->ep0out);
flush_endpoint(&ui->ep0in);
/* let functions handle vendor and class requests */
if ((ctl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) {
struct usb_function *func;
/* Send stall if received interface number is invalid */
if (ctl.wIndex >= ui->next_ifc_num)
goto stall;
func = ui->func2ifc_map[ctl.wIndex];
if (func && func->setup) {
if (ctl.bRequestType & USB_DIR_IN) {
struct usb_request *req = ui->setup_req;
int ret = func->setup(&ctl,
req->buf, SETUP_BUF_SIZE,
func->context);
if (ret >= 0) {
req->length = ret;
ep0_setup_send(ui, ctl.wLength);
return;
}
} else {
int ret = func->setup(&ctl, NULL, 0,
func->context);
if (ret == 0) {
ep0_setup_ack(ui);
return;
} else if (ret > 0) {
ep0_setup_receive(ui, ret);
return;
}
}
}
goto stall;
return;
}
switch (ctl.bRequest) {
case USB_REQ_GET_STATUS:
{
struct usb_request *req = ui->setup_req;
if ((ctl.bRequestType & (USB_DIR_MASK)) != (USB_DIR_IN))
break;
if (ctl.wLength != 2)
break;
req->length = 2;
switch (ctl.bRequestType & USB_RECIP_MASK) {
case USB_RECIP_ENDPOINT:
{
unsigned num = ctl.wIndex & USB_ENDPOINT_NUMBER_MASK;
struct usb_endpoint *ept;
if (num == 0)
break;
if (ctl.wIndex & USB_ENDPOINT_DIR_MASK)
num += 16;
ept = ui->ept + num;
memcpy(req->buf, &ept->ept_halted, 2);
break;
}
case USB_RECIP_DEVICE:
{
unsigned short temp = 0;
if (usb_msm_get_selfpowered())
temp = 1 << USB_DEVICE_SELF_POWERED;
if (usb_msm_get_remotewakeup())
temp |= 1 << USB_DEVICE_REMOTE_WAKEUP;
memcpy(req->buf, &temp, 2);
break;
}
case USB_RECIP_INTERFACE:
memset(req->buf, 0, 2);
break;
default:
printk(KERN_ERR "Unreconginized recipient\n");
break;
}
ep0_setup_send(ui, 2);
return;
}
case USB_REQ_GET_DESCRIPTOR:
{
struct usb_request *req;
if ((ctl.bRequestType & (USB_DIR_MASK)) != (USB_DIR_IN))
break;
req = ui->setup_req;
if (!usb_find_descriptor(ui, &ctl, req)) {
if (req->length > ctl.wLength)
req->length = ctl.wLength;
ep0_setup_send(ui, ctl.wLength);
return;
}
break;
}
case USB_REQ_SET_FEATURE:
if ((ctl.bRequestType & (USB_DIR_MASK)) != (USB_DIR_OUT))
break;
if (ctl.wLength != 0)
break;
switch (ctl.bRequestType & USB_RECIP_MASK) {
case USB_RECIP_DEVICE:
if (ctl.wValue == USB_DEVICE_REMOTE_WAKEUP) {
ui->remote_wakeup = 1;
ep0_setup_ack(ui);
return;
} else if (ctl.wValue == USB_DEVICE_TEST_MODE) {
if (ctl.wIndex & 0x0f)
break;
ui->test_mode = ctl.wIndex;
ep0_setup_ack(ui);
return;
}
break;
case USB_RECIP_ENDPOINT:
{
unsigned num = ctl.wIndex & USB_ENDPOINT_NUMBER_MASK;
if ((num == 0) || (ctl.wValue != 0))
break;
if (ctl.wIndex & USB_ENDPOINT_DIR_MASK)
num += 16;
usb_ept_set_halt(ui->ept + num);
ep0_setup_ack(ui);
return;
}
default:
pr_err("usb: %s: set_feature: unrecognized recipient\n",
__func__);
break;
}
break;
case USB_REQ_CLEAR_FEATURE:
{
if ((ctl.bRequestType & (USB_DIR_MASK)) != (USB_DIR_OUT))
break;
if (ctl.wLength != 0)
break;
switch (ctl.bRequestType & USB_RECIP_MASK) {
case USB_RECIP_DEVICE:
if (ctl.wValue != USB_DEVICE_REMOTE_WAKEUP)
break;
ui->remote_wakeup = 0;
ep0_setup_ack(ui);
return;
case USB_RECIP_ENDPOINT:
{
unsigned num;
if (ctl.wValue != USB_ENDPOINT_HALT)
break;
num = ctl.wIndex & USB_ENDPOINT_NUMBER_MASK;
if (num != 0) {
if (ctl.wIndex & USB_ENDPOINT_DIR_MASK)
num += 16;
usb_ept_clear_halt(ui->ept + num);
}
ep0_setup_ack(ui);
return;
}
default:
pr_info("unsupported clear feature command\n");
pr_info("Request-type:(%08x) wValue:(%08x) "
"wIndex:(%08x) wLength:(%08x)\n",
ctl.bRequestType, ctl.wValue,
ctl.wIndex, ctl.wLength);
break;
}
break;
}
case USB_REQ_SET_INTERFACE:
if ((ctl.bRequestType & (USB_DIR_MASK | USB_RECIP_MASK))
!= (USB_DIR_OUT | USB_RECIP_INTERFACE))
break;
if (ui->func2ifc_map[ctl.wIndex]->set_interface) {
ui->func2ifc_map[ctl.wIndex]->set_interface(ctl.wIndex,
ctl.wValue,
ui->func2ifc_map[ctl.wIndex]->context);
ep0_setup_ack(ui);
return;
}
break;
case USB_REQ_GET_INTERFACE:
{
struct usb_function *f;
struct usb_request *req = ui->setup_req;
int ifc_num = ctl.wIndex;
int ret = 0;
if ((ctl.bRequestType & (USB_DIR_MASK | USB_RECIP_MASK))
!= (USB_DIR_IN | USB_RECIP_INTERFACE))
break;
f = ui->func2ifc_map[ifc_num];
if (!f->get_interface)
break;
ret = f->get_interface(ifc_num,
ui->func2ifc_map[ifc_num]->context);
if (ret < 0)
break;
req->length = ctl.wLength;
memcpy(req->buf, &ret, req->length);
ep0_setup_send(ui, ctl.wLength);
return;
}
case USB_REQ_SET_CONFIGURATION:
if ((ctl.bRequestType & USB_DIR_MASK) != USB_DIR_OUT)
break;
ui->configured = ctl.wValue;
pr_info("hsusb set_configuration wValue = %d usbcmd = %x\n",
ctl.wValue, readl(USB_USBCMD));
set_configuration(ui, ctl.wValue);
ep0_setup_ack(ui);
ui->flags = USB_FLAG_CONFIGURE;
if (ui->configured)
ui->usb_state = USB_STATE_CONFIGURED;
queue_delayed_work(usb_work, &ui->work, 0);
return;
case USB_REQ_GET_CONFIGURATION:
{
unsigned conf;
struct usb_request *req = ui->setup_req;
req->length = 1;
conf = ui->configured;
memcpy(req->buf, &conf, req->length);
ep0_setup_send(ui, ctl.wLength);
return;
}
case USB_REQ_SET_ADDRESS:
if ((ctl.bRequestType & (USB_DIR_MASK | USB_RECIP_MASK))
!= (USB_DIR_OUT | USB_RECIP_DEVICE))
break;
ui->usb_state = USB_STATE_ADDRESS;
writel((ctl.wValue << 25) | (1 << 24), USB_DEVICEADDR);
ep0_setup_ack(ui);
return;
}
stall:
ep0_setup_stall(ui);
return;
}
static void handle_endpoint(struct usb_info *ui, unsigned bit)
{
struct usb_endpoint *ept = ui->ept + bit;
struct msm_request *req;
unsigned long flags;
unsigned info;
#if 0
printk(KERN_INFO "handle_endpoint() %d %s req=%p(%08x)\n",
ept->num, (ept->flags & EPT_FLAG_IN) ? "in" : "out",
ept->req, ept->req ? ept->req->item_dma : 0);
#endif
if (!ept) {
pr_err("%s: ept is null: ep bit = %d\n", __func__, bit);
return;
}
/* expire all requests that are no longer active */
spin_lock_irqsave(&ui->lock, flags);
while ((req = ept->req)) {
/* clean speculative fetches on req->item->info */
dma_coherent_post_ops();
info = req->item->info;
/* if we've processed all live requests, time to
* restart the hardware on the next non-live request
*/
if (!req->live) {
usb_ept_start(ept);
break;
}
/* if the transaction is still in-flight, stop here */
if (info & INFO_ACTIVE)
break;
/* advance ept queue to the next request */
ept->req = req->next;
if (ept->req == 0)
ept->last = 0;
dma_unmap_single(NULL, req->dma, req->req.length,
(ept->flags & EPT_FLAG_IN) ?
DMA_TO_DEVICE : DMA_FROM_DEVICE);
if (info & (INFO_HALTED | INFO_BUFFER_ERROR | INFO_TXN_ERROR)) {
/* XXX pass on more specific error code */
req->req.status = -EIO;
req->req.actual = 0;
printk(KERN_INFO "hsusb: ept %d %s error. info=%08x\n",
ept->num,
(ept->flags & EPT_FLAG_IN) ? "in" : "out",
info);
} else {
req->req.status = 0;
req->req.actual = req->req.length - ((info >> 16) & 0x7FFF);
}
req->busy = 0;
req->live = 0;
if (req->dead)
do_free_req(ui, req);
if (req->req.complete) {
spin_unlock_irqrestore(&ui->lock, flags);
req->req.complete(ept, &req->req);
spin_lock_irqsave(&ui->lock, flags);
}
}
spin_unlock_irqrestore(&ui->lock, flags);
}
static void flush_endpoint_hw(struct usb_info *ui, unsigned bits)
{
/* flush endpoint, canceling transactions
** - this can take a "large amount of time" (per databook)
** - the flush can fail in some cases, thus we check STAT
** and repeat if we're still operating
** (does the fact that this doesn't use the tripwire matter?!)
*/
if (ui->in_lpm) {
pr_err("%s: controller is in lpm, cannot proceed\n", __func__);
return;
}
do {
writel(bits, USB_ENDPTFLUSH);
while (readl(USB_ENDPTFLUSH) & bits)
udelay(100);
} while (readl(USB_ENDPTSTAT) & bits);
}
static void flush_endpoint_sw(struct usb_endpoint *ept)
{
struct usb_info *ui = ept->ui;
struct msm_request *req, *next;
unsigned long flags;
/* inactive endpoints have nothing to do here */
if (!ui || !ept->alloced || !ept->max_pkt)
return;
/* put the queue head in a sane state */
ept->head->info = 0;
ept->head->next = TERMINATE;
/* cancel any pending requests */
spin_lock_irqsave(&ui->lock, flags);
req = ept->req;
ept->req = 0;
ept->last = 0;
while (req != 0) {
next = req->next;
req->busy = 0;
req->live = 0;
req->req.status = -ENODEV;
req->req.actual = 0;
if (req->req.complete) {
spin_unlock_irqrestore(&ui->lock, flags);
req->req.complete(ept, &req->req);
spin_lock_irqsave(&ui->lock, flags);
}
if (req->dead)
do_free_req(ui, req);
req = req->next;
}
spin_unlock_irqrestore(&ui->lock, flags);
}
static void flush_endpoint(struct usb_endpoint *ept)
{
if (!ept->ui)
return;
flush_endpoint_hw(ept->ui, (1 << ept->bit));
flush_endpoint_sw(ept);
}
static void flush_all_endpoints(struct usb_info *ui)
{
unsigned n;
flush_endpoint_hw(ui, 0xffffffff);
for (n = 0; n < 32; n++)
flush_endpoint_sw(ui->ept + n);
}
#define HW_DELAY_FOR_LPM msecs_to_jiffies(1000)
#define DELAY_FOR_USB_VBUS_STABILIZE msecs_to_jiffies(500)
static irqreturn_t usb_interrupt(int irq, void *data)
{
struct usb_info *ui = data;
unsigned n;
unsigned speed;
if (!ui->active)
return IRQ_HANDLED;
if (ui->in_lpm) {
usb_lpm_exit(ui);
return IRQ_HANDLED;
}
n = readl(USB_USBSTS);
writel(n, USB_USBSTS);
/* somehow we got an IRQ while in the reset sequence: ignore it */
if (ui->running == 0) {
pr_err("%s: ui->running is zero\n", __func__);
return IRQ_HANDLED;
}
if (n & STS_PCI) {
if (!(readl(USB_PORTSC) & PORTSC_PORT_RESET)) {
speed = (readl(USB_PORTSC) & PORTSC_PORT_SPEED_MASK);
switch (speed) {
case PORTSC_PORT_SPEED_HIGH:
pr_info("hsusb resume: speed = HIGH\n");
ui->speed = USB_SPEED_HIGH;
break;
case PORTSC_PORT_SPEED_FULL:
pr_info("hsusb resume: speed = FULL\n");
ui->speed = USB_SPEED_FULL;
break;
default:
pr_err("hsusb resume: Unknown Speed\n");
ui->speed = USB_SPEED_UNKNOWN;
break;
}
}
/* pci interrutpt would also be generated when resuming
* from bus suspend, following check would avoid kick
* starting usb main thread in case of pci interrupts
* during enumeration
*/
if (ui->configured && ui->chg_type == USB_CHG_TYPE__SDP) {
ui->usb_state = USB_STATE_CONFIGURED;
ui->flags = USB_FLAG_RESUME;
queue_delayed_work(usb_work, &ui->work, 0);
}
}
if (n & STS_URI) {
pr_info("hsusb reset interrupt\n");
ui->usb_state = USB_STATE_DEFAULT;
ui->configured = 0;
schedule_work(&ui->chg_stop);
writel(readl(USB_ENDPTSETUPSTAT), USB_ENDPTSETUPSTAT);
writel(readl(USB_ENDPTCOMPLETE), USB_ENDPTCOMPLETE);
writel(0xffffffff, USB_ENDPTFLUSH);
writel(0, USB_ENDPTCTRL(1));
if (ui->online != 0) {
/* marking us offline will cause ept queue attempts to fail */
ui->online = 0;
flush_all_endpoints(ui);
/* XXX: we can't seem to detect going offline, so deconfigure
* XXX: on reset for the time being
*/
set_configuration(ui, 0);
}
}
if (n & STS_SLI) {
pr_info("hsusb suspend interrupt\n");
ui->usb_state = USB_STATE_SUSPENDED;
/* stop usb charging */
schedule_work(&ui->chg_stop);
}
if (n & STS_UI) {
n = readl(USB_ENDPTSETUPSTAT);
if (n & EPT_RX(0))
handle_setup(ui);
n = readl(USB_ENDPTCOMPLETE);
writel(n, USB_ENDPTCOMPLETE);
while (n) {
unsigned bit = __ffs(n);
handle_endpoint(ui, bit);
n = n & (~(1 << bit));
}
}
n = readl(USB_OTGSC);
writel(n, USB_OTGSC);
if (n & OTGSC_BSVIS) {
/*Verify B Session Valid Bit to verify vbus status*/
if (B_SESSION_VALID & n) {
pr_info("usb cable connected\n");
ui->usb_state = USB_STATE_POWERED;
ui->flags = USB_FLAG_VBUS_ONLINE;
/* Wait for 100ms to stabilize VBUS before initializing
* USB and detecting charger type
*/
queue_delayed_work(usb_work, &ui->work, 0);
} else {
int i;
usb_disable_pullup(ui);
printk(KERN_INFO "usb cable disconnected\n");
ui->usb_state = USB_STATE_NOTATTACHED;
ui->configured = 0;
for (i = 0; i < ui->num_funcs; i++) {
struct usb_function_info *fi = ui->func[i];
if (!fi ||
!(ui->composition->functions & (1 << i)))
continue;
if (fi->func->disconnect)
fi->func->disconnect
(fi->func->context);
}
ui->flags = USB_FLAG_VBUS_OFFLINE;
queue_delayed_work(usb_work, &ui->work, 0);
}
}
return IRQ_HANDLED;
}
static void usb_prepare(struct usb_info *ui)
{
memset(ui->buf, 0, 4096);
ui->head = (void *) (ui->buf + 0);
/* only important for reset/reinit */
memset(ui->ept, 0, sizeof(ui->ept));
ui->next_item = 0;
ui->speed = USB_SPEED_UNKNOWN;
init_endpoints(ui);
ui->ep0in.max_pkt = 64;
ui->ep0in.ui = ui;
ui->ep0in.alloced = 1;
ui->ep0out.max_pkt = 64;
ui->ep0out.ui = ui;
ui->ep0out.alloced = 1;
ui->setup_req = usb_ept_alloc_req(&ui->ep0in, SETUP_BUF_SIZE);
ui->ep0out_req = usb_ept_alloc_req(&ui->ep0out, ui->ep0out.max_pkt);
INIT_WORK(&ui->chg_stop, usb_chg_stop);
INIT_WORK(&ui->li.wakeup_phy, usb_lpm_wakeup_phy);
INIT_DELAYED_WORK(&ui->work, usb_do_work);
INIT_DELAYED_WORK(&ui->chg_legacy_det, usb_chg_legacy_detect);
}
static int usb_is_online(struct usb_info *ui)
{
/* continue lpm if bus is suspended or disconnected or stopped*/
if (((readl(USB_PORTSC) & PORTSC_SUSP) == PORTSC_SUSP) ||
((readl(USB_PORTSC) & PORTSC_CCS) == 0) ||
((readl(USB_USBCMD) & USBCMD_RS) == 0))
return 0;
pr_debug("usb is online\n");
pr_debug("usbcmd:(%08x) usbsts:(%08x) portsc:(%08x)\n",
readl(USB_USBCMD),
readl(USB_USBSTS),
readl(USB_PORTSC));
return -1;
}
static int usb_wakeup_phy(struct usb_info *ui)
{
int i;
writel(readl(USB_USBCMD) & ~ULPI_STP_CTRL, USB_USBCMD);
/* some circuits automatically clear PHCD bit */
for (i = 0; i < 5 && (readl(USB_PORTSC) & PORTSC_PHCD); i++) {
writel(readl(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC);
msleep(1);
}
if ((readl(USB_PORTSC) & PORTSC_PHCD)) {
pr_err("%s: cannot clear phcd bit\n", __func__);
return -1;
}
return 0;
}
static int usb_suspend_phy(struct usb_info *ui)
{
int i;
unsigned long flags;
if (usb_is_online(ui))
return -1;
/* spec talks about following bits in LPM for external phy.
* But they are ignored because
* 1. disabling interface protection circuit: by disabling
* interface protection curcuit we cannot come out
* of lpm as async interrupts would be disabled
* 2. setting the suspendM bit: this bit would be set by usb
* controller once we set phcd bit.
*/
switch (PHY_TYPE(ui->phy_info)) {
case USB_PHY_INTEGRATED:
if (!is_phy_45nm())
ulpi_read(ui, 0x14);
/* turn on/off otg comparators */
if (ui->vbus_sn_notif &&
ui->usb_state == USB_STATE_NOTATTACHED)
ulpi_write(ui, 0x00, 0x30);
else
ulpi_write(ui, 0x01, 0x30);
if (!is_phy_45nm())
ulpi_write(ui, 0x08, 0x09);
break;
case USB_PHY_UNDEFINED:
pr_err("%s: undefined phy type\n", __func__);
return -1;
}
/* loop for large amount of time */
for (i = 0; i < 500; i++) {
spin_lock_irqsave(&ui->lock, flags);
if (usb_is_online(ui)) {
spin_unlock_irqrestore(&ui->lock, flags);
return -1;
}
/* set phy to be in lpm */
writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC);
spin_unlock_irqrestore(&ui->lock, flags);
msleep(1);
if (readl(USB_PORTSC) & PORTSC_PHCD)
goto blk_stp_sig;
}
if (!(readl(USB_PORTSC) & PORTSC_PHCD)) {
pr_err("unable to set phcd of portsc reg\n");
pr_err("Reset HW link and phy to recover from phcd error\n");
usb_hw_reset(ui);
return -1;
}
/* we have to set this bit again to work-around h/w bug */
writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC);
blk_stp_sig:
/* block the stop signal */
writel(readl(USB_USBCMD) | ULPI_STP_CTRL, USB_USBCMD);
return 0;
}
/* SW workarounds
Issue#2 - Integrated PHY Calibration
Symptom - Electrical compliance failure in eye-diagram tests
SW workaround - Try to raise amplitude to 400mV
Issue#3 - AHB Posted Writes
Symptom - USB stability
SW workaround - This programs xtor ON, BURST disabled and
unspecified length of INCR burst enabled
*/
static int usb_hw_reset(struct usb_info *ui)
{
unsigned i;
struct msm_hsusb_platform_data *pdata;
unsigned long timeout;
unsigned val = 0;
pdata = ui->pdev->dev.platform_data;
clk_enable(ui->clk);
/* reset the phy before resetting link */
if (readl(USB_PORTSC) & PORTSC_PHCD)
usb_wakeup_phy(ui);
/* rpc call for phy_reset */
if (ui->pdata->phy_reset)
ui->pdata->phy_reset(ui->addr);
else
msm_hsusb_phy_reset();
/* Give some delay to settle phy after reset */
msleep(100);
/* RESET */
writel(USBCMD_RESET, USB_USBCMD);
timeout = jiffies + USB_LINK_RESET_TIMEOUT;
while (readl(USB_USBCMD) & USBCMD_RESET) {
if (time_after(jiffies, timeout)) {
dev_err(&ui->pdev->dev, "usb link reset timeout\n");
break;
}
msleep(1);
}
/* select DEVICE mode with SDIS active */
writel((USBMODE_SDIS | USBMODE_DEVICE), USB_USBMODE);
msleep(1);
/* select ULPI phy */
i = (readl(USB_PORTSC) & ~PORTSC_PTS);
writel(i | PORTSC_PTS_ULPI, USB_PORTSC);
/* set usb controller interrupt latency to zero*/
writel((readl(USB_USBCMD) & ~USBCMD_ITC_MASK) | USBCMD_ITC(0),
USB_USBCMD);
/* If the target is 7x01 and roc version is > 1.2, set
* the AHB mode to 2 for maximum performance, else set
* it to 1, to bypass the AHB transactor for stability.
*/
if (PHY_TYPE(ui->phy_info) == USB_PHY_EXTERNAL) {
if (pdata->soc_version >= SOC_ROC_2_0)
writel(0x02, USB_ROC_AHB_MODE);
else
writel(0x01, USB_ROC_AHB_MODE);
} else {
unsigned cfg_val;
/* Raise amplitude to 400mV
* SW workaround, Issue#2
*/
cfg_val = ulpi_read(ui, ULPI_CONFIG_REG);
cfg_val |= ULPI_AMPLITUDE_MAX;
ulpi_write(ui, cfg_val, ULPI_CONFIG_REG);
writel(0x0, USB_AHB_BURST);
writel(0x00, USB_AHB_MODE);
}
/* TBD: do we have to add DpRise, ChargerRise and
* IdFloatRise for 45nm
*/
/* Disable VbusValid and SessionEnd comparators */
val = ULPI_VBUS_VALID | ULPI_SESS_END;
/* enable id interrupt only when transceiver is available */
if (ui->xceiv)
writel(readl(USB_OTGSC) | OTGSC_BSVIE | OTGSC_IDIE, USB_OTGSC);
else {
writel((readl(USB_OTGSC) | OTGSC_BSVIE) & ~OTGSC_IDPU,
USB_OTGSC);
ulpi_write(ui, ULPI_IDPU, ULPI_OTG_CTRL_CLR);
val |= ULPI_HOST_DISCONNECT | ULPI_ID_GND;
}
ulpi_write(ui, val, ULPI_INT_RISE_CLR);
ulpi_write(ui, val, ULPI_INT_FALL_CLR);
/* we are just setting the pointer in the hwblock. Since the
* endpoint isnt enabled the hw block doenst read the contents
* of ui->dma - so we dont need a barrier here
* */
writel(ui->dma, USB_ENDPOINTLISTADDR);
clk_disable(ui->clk);
return 0;
}
static void usb_reset(struct usb_info *ui)
{
unsigned long flags;
spin_lock_irqsave(&ui->lock, flags);
ui->running = 0;
spin_unlock_irqrestore(&ui->lock, flags);
#if 0
/* we should flush and shutdown cleanly if already running */
writel(0xffffffff, USB_ENDPTFLUSH);
msleep(2);
#endif
if (usb_hw_reset(ui)) {
pr_info("%s: h/w reset failed\n", __func__);
return;
}
usb_configure_endpoint(&ui->ep0in, NULL);
usb_configure_endpoint(&ui->ep0out, NULL);
/* marking us offline will cause ept queue attempts to fail */
ui->online = 0;
/* terminate any pending transactions */
flush_all_endpoints(ui);
set_configuration(ui, 0);
spin_lock_irqsave(&ui->lock, flags);
ui->running = 1;
spin_unlock_irqrestore(&ui->lock, flags);
}
static void usb_enable(void *handle, int enable)
{
struct usb_info *ui = handle;
unsigned long flags;
spin_lock_irqsave(&ui->lock, flags);
if (enable) {
ui->flags |= USB_FLAG_RESET;
ui->active = 1;
spin_unlock_irqrestore(&ui->lock, flags);
usb_do_work(&ui->work.work);
} else {
ui->active = 0;
spin_unlock_irqrestore(&ui->lock, flags);
usb_clk_disable(ui);
msm_hsusb_suspend_locks_acquire(ui, 0);
}
}
static struct msm_otg_ops dcd_ops = {
.request = usb_enable,
};
void usb_start(struct usb_info *ui)
{
int i, ret;
for (i = 0; i < ui->num_funcs; i++) {
struct usb_function_info *fi = ui->func[i];
if (!fi || !(ui->composition->functions & (1<<i)))
continue;
if (fi->enabled) {
pr_info("usb_bind_func() (%s)\n", fi->func->name);
fi->func->bind(fi->func->context);
}
}
ui->clk_enabled = 0;
ui->vreg_enabled = 0;
ui->xceiv = msm_otg_get_transceiver();
if (ui->xceiv) {
ui->flags = USB_FLAG_REG_OTG;
queue_delayed_work(usb_work, &ui->work, 0);
} else {
/*Initialize pm app RPC */
ret = msm_pm_app_rpc_init();
if (ret) {
pr_err("%s: pm_app_rpc connect failed\n", __func__);
goto out;
}
pr_info("%s: pm_app_rpc connect success\n", __func__);
ret = msm_pm_app_register_vbus_sn(&msm_hsusb_set_vbus_state);
if (ret) {
pr_err("%s:PMIC VBUS SN notif not supported\n", \
__func__);
msm_pm_app_rpc_deinit();
goto out;
}
pr_info("%s:PMIC VBUS SN notif supported\n", \
__func__);
ret = msm_pm_app_enable_usb_ldo(1);
if (ret) {
pr_err("%s: unable to turn on internal LDO", \
__func__);
msm_pm_app_unregister_vbus_sn(
&msm_hsusb_set_vbus_state);
msm_pm_app_rpc_deinit();
goto out;
}
ui->vbus_sn_notif = 1;
out:
ui->active = 1;
ui->flags |= (USB_FLAG_START | USB_FLAG_RESET);
queue_delayed_work(usb_work, &ui->work, 0);
}
}
static LIST_HEAD(usb_function_list);
static DEFINE_MUTEX(usb_function_list_lock);
static struct usb_function_info *usb_find_function(const char *name)
{
struct list_head *entry;
list_for_each(entry, &usb_function_list) {
struct usb_function_info *fi =
list_entry(entry, struct usb_function_info, list);
if (fi) {
if (!strcmp(name, fi->func->name))
return fi;
}
}
return NULL;
}
static void usb_try_to_bind(void)
{
struct usb_info *ui = the_usb_info;
unsigned long enabled_functions = 0;
int i;
if (!ui || ui->bound || !ui->pdev || !ui->composition)
return;
for (i = 0; i < ui->num_funcs; i++) {
if (ui->func[i])
enabled_functions |= (1 << i);
}
if ((enabled_functions & ui->composition->functions)
!= ui->composition->functions)
return;
usb_set_composition(ui->composition->product_id);
usb_configure_device_descriptor(ui);
/* we have found all the needed functions */
ui->bound = 1;
printk(KERN_INFO "msm_hsusb: functions bound. starting.\n");
usb_start(ui);
}
static int usb_get_function_index(const char *name)
{
struct usb_info *ui = the_usb_info;
int i;
for (i = 0; i < ui->num_funcs; i++) {
if (!strcmp(name, ui->functions_map[i].name))
return i;
}
return -1;
}
int usb_function_register(struct usb_function *driver)
{
struct usb_info *ui = the_usb_info;
struct usb_function_info *fi;
int ret = 0;
int index;
mutex_lock(&usb_function_list_lock);
index = usb_get_function_index(driver->name);
if (index < 0) {
pr_err("%s: unsupported function = %s\n",
__func__, driver->name);
ret = -EINVAL;
goto fail;
}
fi = kzalloc(sizeof(*fi), GFP_KERNEL);
if (!fi) {
ret = -ENOMEM;
goto fail;
}
fi->func = driver;
list_add(&fi->list, &usb_function_list);
ui->func[index] = fi;
fi->func->ep0_out_req = ui->ep0out_req;
fi->func->ep0_in_req = ui->setup_req;
fi->func->ep0_out = &ui->ep0out;
fi->func->ep0_in = &ui->ep0in;
pr_info("%s: name = '%s', map = %d\n", __func__, driver->name, index);
usb_try_to_bind();
fail:
mutex_unlock(&usb_function_list_lock);
return ret;
}
EXPORT_SYMBOL(usb_function_register);
static unsigned short usb_validate_product_id(unsigned short pid)
{
struct usb_info *ui = the_usb_info;
int i;
if (!ui || !ui->pdata)
return -1;
/* set idProduct based on which functions are enabled */
for (i = 0; i < ui->pdata->num_compositions; i++) {
if (ui->pdata->compositions[i].product_id == pid)
break;
}
if (i < ui->pdata->num_compositions) {
struct usb_composition *comp = &ui->pdata->compositions[i];
for (i = 0; i < ui->num_funcs; i++) {
if (comp->functions & (1 << i)) {
if (!ui->func[i]) {
pr_err("%s: func(%d) not available\n",
__func__, i);
return 0;
}
}
}
return comp->product_id;
} else
pr_err("%s: Product id (%x) is not supported\n", __func__, pid);
return 0;
}
static unsigned short usb_get_product_id(unsigned long enabled_functions)
{
struct usb_info *ui = the_usb_info;
int i;
if (!(ui && ui->pdata))
return -1;
/* set idProduct based on which functions are enabled */
for (i = 0; i < ui->pdata->num_compositions; i++) {
if (ui->pdata->compositions[i].functions == enabled_functions)
return ui->pdata->compositions[i].product_id;
}
return 0;
}
static void usb_uninit(struct usb_info *ui)
{
int i;
for (i = 0; i < ui->strdesc_index; i++)
kfree(ui->strdesc[i]);
ui->strdesc_index = 1;
ui->next_ifc_num = 0;
}
static unsigned short usb_set_composition(unsigned short pid)
{
struct usb_info *ui = the_usb_info;
int i;
if (!(ui && ui->pdata))
return 0;
/* Retrieve product id on enabled functions */
for (i = 0; i < ui->pdata->num_compositions; i++) {
if (ui->pdata->compositions[i].product_id == pid) {
ui->composition = &ui->pdata->compositions[i];
for (i = 0; i < ui->num_funcs; i++) {
struct usb_function_info *fi = ui->func[i];
if (ui->func && fi && fi->func) {
fi->enabled = (ui->composition->
functions >> i) & 1;
}
}
pr_info("%s: composition set to product id = %x\n",
__func__, ui->composition->product_id);
return ui->composition->product_id;
}
}
pr_err("%s: product id (%x) not supported\n", __func__, pid);
return 0;
}
static void usb_switch_composition(unsigned short pid)
{
struct usb_info *ui = the_usb_info;
int i;
unsigned long flags;
if (!ui->active)
return;
if (!usb_validate_product_id(pid))
return;
disable_irq(ui->irq);
if (cancel_delayed_work_sync(&ui->work))
pr_info("%s: Removed work successfully\n", __func__);
if (ui->running) {
spin_lock_irqsave(&ui->lock, flags);
ui->running = 0;
ui->online = 0;
ui->bound = 0;
spin_unlock_irqrestore(&ui->lock, flags);
/* we should come out of lpm to access registers */
if (ui->in_lpm) {
if (PHY_TYPE(ui->phy_info) == USB_PHY_EXTERNAL) {
disable_irq(ui->gpio_irq[0]);
disable_irq(ui->gpio_irq[1]);
}
if (ui->usb_state == USB_STATE_NOTATTACHED
&& ui->vbus_sn_notif)
msm_pm_app_enable_usb_ldo(1);
usb_lpm_exit(ui);
if (cancel_work_sync(&ui->li.wakeup_phy))
usb_lpm_wakeup_phy(NULL);
ui->in_lpm = 0;
}
/* disable usb and session valid interrupts */
writel(0, USB_USBINTR);
writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC);
/* stop the controller */
usb_disable_pullup(ui);
ui->usb_state = USB_STATE_NOTATTACHED;
switch_set_state(&ui->sdev, 0);
/* Before starting again, wait for 300ms
* to make sure host detects soft disconnection
**/
msleep(300);
}
for (i = 0; i < ui->num_funcs; i++) {
struct usb_function_info *fi = ui->func[i];
if (!fi || !fi->func || !fi->enabled)
continue;
if (fi->func->configure)
fi->func->configure(0, fi->func->context);
if (fi->func->unbind)
fi->func->unbind(fi->func->context);
}
usb_uninit(ui);
usb_set_composition(pid);
usb_configure_device_descriptor(ui);
/* initialize functions */
for (i = 0; i < ui->num_funcs; i++) {
struct usb_function_info *fi = ui->func[i];
if (!fi || !(ui->composition->functions & (1 << i)))
continue;
if (fi->enabled) {
if (fi->func->bind)
fi->func->bind(fi->func->context);
}
}
ui->bound = 1;
ui->flags = USB_FLAG_RESET;
queue_delayed_work(usb_work, &ui->work, 0);
enable_irq(ui->irq);
}
void usb_function_enable(const char *function, int enable)
{
struct usb_function_info *fi;
struct usb_info *ui = the_usb_info;
unsigned long functions_mask;
int curr_enable;
unsigned short pid;
int i;
if (!ui)
return;
pr_info("%s: name = %s, enable = %d\n", __func__, function, enable);
fi = usb_find_function(function);
if (!fi) {
pr_err("%s: function (%s) not registered with DCD\n",
__func__, function);
return;
}
if (fi->enabled == enable) {
pr_err("%s: function (%s) state is same\n",
__func__, function);
return;
}
functions_mask = 0;
curr_enable = fi->enabled;
fi->enabled = enable;
for (i = 0; i < ui->num_funcs; i++) {
struct usb_function_info *fi = ui->func[i];
if (fi && fi->enabled)
functions_mask |= (1 << i);
}
pid = usb_get_product_id(functions_mask);
if (!pid) {
fi->enabled = curr_enable;
pr_err("%s: mask (%lx) not matching with any products\n",
__func__, functions_mask);
pr_err("%s: continuing with current composition\n", __func__);
return;
}
usb_switch_composition(pid);
}
EXPORT_SYMBOL(usb_function_enable);
static int usb_free(struct usb_info *ui, int ret)
{
disable_irq_wake(ui->irq);
free_irq(ui->irq, ui);
if (ui->gpio_irq[0])
free_irq(ui->gpio_irq[0], NULL);
if (ui->gpio_irq[1])
free_irq(ui->gpio_irq[1], NULL);
dma_pool_destroy(ui->pool);
dma_free_coherent(&ui->pdev->dev, 4096, ui->buf, ui->dma);
kfree(ui->func);
kfree(ui->strdesc);
iounmap(ui->addr);
clk_put(ui->clk);
clk_put(ui->pclk);
clk_put(ui->cclk);
msm_hsusb_suspend_locks_init(ui, 0);
kfree(ui);
return ret;
}
static int usb_vbus_is_on(struct usb_info *ui)
{
unsigned tmp;
/* disable session valid raising and falling interrupts */
ulpi_write(ui, ULPI_SESSION_VALID_RAISE, ULPI_USBINTR_ENABLE_RASING_C);
ulpi_write(ui, ULPI_SESSION_VALID_FALL, ULPI_USBINTR_ENABLE_FALLING_C);
tmp = ulpi_read(ui, ULPI_USBINTR_STATUS);
/* enable session valid raising and falling interrupts */
ulpi_write(ui, ULPI_SESSION_VALID_RAISE, ULPI_USBINTR_ENABLE_RASING_S);
ulpi_write(ui, ULPI_SESSION_VALID_FALL, ULPI_USBINTR_ENABLE_FALLING_S);
if (tmp & (1 << 2))
return 1;
return 0;
}
static void usb_do_work(struct work_struct *w)
{
struct usb_info *ui = container_of(w, struct usb_info, work.work);
unsigned long iflags;
unsigned long flags, ret;
for (;;) {
spin_lock_irqsave(&ui->lock, iflags);
flags = ui->flags;
ui->flags = 0;
spin_unlock_irqrestore(&ui->lock, iflags);
/* give up if we have nothing to do */
if (flags == 0)
break;
switch (ui->state) {
case USB_STATE_IDLE:
if (flags & USB_FLAG_REG_OTG) {
dcd_ops.handle = (void *) ui;
ret = ui->xceiv->set_peripheral(ui->xceiv,
&dcd_ops);
if (ret)
pr_err("%s: Can't register peripheral"
"driver with OTG", __func__);
break;
}
if ((flags & USB_FLAG_START) ||
(flags & USB_FLAG_RESET)) {
disable_irq(ui->irq);
if (ui->vbus_sn_notif)
msm_pm_app_enable_usb_ldo(1);
usb_clk_enable(ui);
usb_vreg_enable(ui);
usb_vbus_online(ui);
/* if VBUS is present move to ONLINE state
* otherwise move to OFFLINE state
*/
if (usb_vbus_is_on(ui)) {
ui->usb_state = USB_STATE_POWERED;
msm_hsusb_suspend_locks_acquire(ui, 1);
ui->state = USB_STATE_ONLINE;
usb_enable_pullup(ui);
schedule_delayed_work(
&ui->chg_legacy_det,
USB_CHG_DET_DELAY);
pr_info("hsusb: IDLE -> ONLINE\n");
} else {
ui->usb_state = USB_STATE_NOTATTACHED;
ui->state = USB_STATE_OFFLINE;
msleep(500);
usb_lpm_enter(ui);
pr_info("hsusb: IDLE -> OFFLINE\n");
if (ui->vbus_sn_notif)
msm_pm_app_enable_usb_ldo(0);
}
enable_irq(ui->irq);
break;
}
goto reset;
case USB_STATE_ONLINE:
/* If at any point when we were online, we received
* the signal to go offline, we must honor it
*/
if (flags & USB_FLAG_VBUS_OFFLINE) {
enum charger_type temp;
unsigned long f;
cancel_delayed_work_sync(&ui->chg_legacy_det);
spin_lock_irqsave(&ui->lock, f);
temp = ui->chg_type;
ui->chg_type = USB_CHG_TYPE__INVALID;
spin_unlock_irqrestore(&ui->lock, f);
if (temp != USB_CHG_TYPE__INVALID) {
/* re-acquire wakelock and restore axi
* freq if they have been reduced by
* charger work item
*/
msm_hsusb_suspend_locks_acquire(ui, 1);
msm_chg_usb_i_is_not_available();
msm_chg_usb_charger_disconnected();
}
/* reset usb core and usb phy */
disable_irq(ui->irq);
if (ui->in_lpm)
usb_lpm_exit(ui);
usb_vbus_offline(ui);
usb_lpm_enter(ui);
if ((ui->vbus_sn_notif) &&
(ui->usb_state == USB_STATE_NOTATTACHED))
msm_pm_app_enable_usb_ldo(0);
ui->state = USB_STATE_OFFLINE;
enable_irq(ui->irq);
switch_set_state(&ui->sdev, 0);
pr_info("hsusb: ONLINE -> OFFLINE\n");
break;
}
if (flags & USB_FLAG_SUSPEND) {
ui->usb_state = USB_STATE_SUSPENDED;
usb_lpm_enter(ui);
msm_hsusb_suspend_locks_acquire(ui, 1);
break;
}
if ((flags & USB_FLAG_RESUME) ||
(flags & USB_FLAG_CONFIGURE)) {
int maxpower = usb_get_max_power(ui);
if (maxpower > 0)
msm_chg_usb_i_is_available(maxpower);
if (flags & USB_FLAG_CONFIGURE)
switch_set_state(&ui->sdev, 1);
break;
}
goto reset;
case USB_STATE_OFFLINE:
/* If we were signaled to go online and vbus is still
* present when we received the signal, go online.
*/
if ((flags & USB_FLAG_VBUS_ONLINE)) {
msm_hsusb_suspend_locks_acquire(ui, 1);
disable_irq(ui->irq);
ui->state = USB_STATE_ONLINE;
if (ui->in_lpm)
usb_lpm_exit(ui);
usb_vbus_online(ui);
if (!(B_SESSION_VALID & readl(USB_OTGSC))) {
writel(((readl(USB_OTGSC) &
~OTGSC_INTR_STS_MASK) |
OTGSC_BSVIS), USB_OTGSC);
enable_irq(ui->irq);
goto reset;
}
usb_enable_pullup(ui);
schedule_delayed_work(
&ui->chg_legacy_det,
USB_CHG_DET_DELAY);
pr_info("hsusb: OFFLINE -> ONLINE\n");
enable_irq(ui->irq);
break;
}
if (flags & USB_FLAG_SUSPEND) {
usb_lpm_enter(ui);
wake_unlock(&ui->wlock);
break;
}
default:
reset:
/* For RESET or any unknown flag in a particular state
* go to IDLE state and reset HW to bring to known state
*/
ui->flags = USB_FLAG_RESET;
ui->state = USB_STATE_IDLE;
}
}
}
void msm_hsusb_set_vbus_state(int online)
{
struct usb_info *ui = the_usb_info;
if (ui && online) {
msm_pm_app_enable_usb_ldo(1);
usb_lpm_exit(ui);
/* Turn on PHY comparators */
if (!(ulpi_read(ui, 0x30) & 0x01))
ulpi_write(ui, 0x01, 0x30);
}
}
static irqreturn_t usb_lpm_gpio_isr(int irq, void *data)
{
disable_irq(irq);
return IRQ_HANDLED;
}
static void usb_lpm_exit(struct usb_info *ui)
{
if (ui->in_lpm == 0)
return;
if (usb_lpm_config_gpio)
usb_lpm_config_gpio(0);
wake_lock(&ui->wlock);
usb_clk_enable(ui);
usb_vreg_enable(ui);
writel(readl(USB_USBCMD) & ~ASYNC_INTR_CTRL, USB_USBCMD);
writel(readl(USB_USBCMD) & ~ULPI_STP_CTRL, USB_USBCMD);
if (readl(USB_PORTSC) & PORTSC_PHCD) {
disable_irq(ui->irq);
schedule_work(&ui->li.wakeup_phy);
} else {
ui->in_lpm = 0;
if (ui->xceiv)
ui->xceiv->set_suspend(ui->xceiv, 0);
}
pr_info("%s(): USB exited from low power mode\n", __func__);
}
static int usb_lpm_enter(struct usb_info *ui)
{
unsigned long flags;
unsigned connected;
spin_lock_irqsave(&ui->lock, flags);
if (ui->in_lpm) {
spin_unlock_irqrestore(&ui->lock, flags);
pr_debug("already in lpm, nothing to do\n");
return 0;
}
if (usb_is_online(ui)) {
spin_unlock_irqrestore(&ui->lock, flags);
pr_info("%s: lpm procedure aborted\n", __func__);
return -1;
}
ui->in_lpm = 1;
if (ui->xceiv)
ui->xceiv->set_suspend(ui->xceiv, 1);
disable_irq(ui->irq);
spin_unlock_irqrestore(&ui->lock, flags);
if (usb_suspend_phy(ui)) {
ui->in_lpm = 0;
ui->flags = USB_FLAG_RESET;
enable_irq(ui->irq);
pr_err("%s: phy suspend failed, lpm procedure aborted\n",
__func__);
return -1;
}
if ((B_SESSION_VALID & readl(USB_OTGSC)) &&
(ui->usb_state == USB_STATE_NOTATTACHED)) {
ui->in_lpm = 0;
writel(((readl(USB_OTGSC) & ~OTGSC_INTR_STS_MASK) |
OTGSC_BSVIS), USB_OTGSC);
ui->flags = USB_FLAG_VBUS_ONLINE;
ui->usb_state = USB_STATE_POWERED;
usb_wakeup_phy(ui);
enable_irq(ui->irq);
return -1;
}
/* enable async interrupt */
writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL, USB_USBCMD);
connected = readl(USB_USBCMD) & USBCMD_RS;
usb_vreg_disable(ui);
usb_clk_disable(ui);
if (usb_lpm_config_gpio) {
if (usb_lpm_config_gpio(1)) {
spin_lock_irqsave(&ui->lock, flags);
usb_lpm_exit(ui);
spin_unlock_irqrestore(&ui->lock, flags);
enable_irq(ui->irq);
return -1;
}
enable_irq(ui->gpio_irq[0]);
enable_irq(ui->gpio_irq[1]);
}
enable_irq(ui->irq);
msm_hsusb_suspend_locks_acquire(ui, 0);
pr_info("%s: usb in low power mode\n", __func__);
return 0;
}
static void usb_enable_pullup(struct usb_info *ui)
{
disable_irq(ui->irq);
writel(STS_URI | STS_SLI | STS_UI | STS_PCI, USB_USBINTR);
writel(readl(USB_USBCMD) | USBCMD_RS, USB_USBCMD);
enable_irq(ui->irq);
}
/* SW workarounds
Issue #1 - USB Spoof Disconnect Failure
Symptom - Writing 0 to run/stop bit of USBCMD doesn't cause disconnect
SW workaround - Making opmode non-driving and SuspendM set in function
register of SMSC phy
*/
static void usb_disable_pullup(struct usb_info *ui)
{
disable_irq(ui->irq);
writel(readl(USB_USBINTR) & ~(STS_URI | STS_SLI | STS_UI | STS_PCI),
USB_USBINTR);
writel(readl(USB_USBCMD) & ~USBCMD_RS, USB_USBCMD);
/* S/W workaround, Issue#1 */
if (!is_phy_external() && !is_phy_45nm())
ulpi_write(ui, 0x48, 0x04);
enable_irq(ui->irq);
}
static void usb_chg_stop(struct work_struct *w)
{
struct usb_info *ui = the_usb_info;
enum charger_type temp;
unsigned long flags;
spin_lock_irqsave(&ui->lock, flags);
temp = ui->chg_type;
spin_unlock_irqrestore(&ui->lock, flags);
if (temp == USB_CHG_TYPE__SDP)
msm_chg_usb_i_is_not_available();
}
static void usb_vbus_online(struct usb_info *ui)
{
if (ui->in_lpm) {
if (usb_lpm_config_gpio)
usb_lpm_config_gpio(0);
usb_vreg_enable(ui);
usb_clk_enable(ui);
usb_wakeup_phy(ui);
ui->in_lpm = 0;
}
usb_reset(ui);
}
static void usb_vbus_offline(struct usb_info *ui)
{
unsigned long timeout;
unsigned val = 0;
if (ui->online != 0) {
ui->online = 0;
flush_all_endpoints(ui);
set_configuration(ui, 0);
}
/* reset h/w at cable disconnetion becasuse
* of h/w bugs and to flush any resource that
* h/w might be holding
*/
clk_enable(ui->clk);
if (readl(USB_PORTSC) & PORTSC_PHCD)
usb_wakeup_phy(ui);
if (ui->pdata->phy_reset)
ui->pdata->phy_reset(ui->addr);
else
msm_hsusb_phy_reset();
/* Give some delay to settle phy after reset */
msleep(100);
writel(USBCMD_RESET, USB_USBCMD);
timeout = jiffies + USB_LINK_RESET_TIMEOUT;
while (readl(USB_USBCMD) & USBCMD_RESET) {
if (time_after(jiffies, timeout)) {
dev_err(&ui->pdev->dev, "usb link reset timeout\n");
break;
}
msleep(1);
}
/* Disable VbusValid and SessionEnd comparators */
val = ULPI_VBUS_VALID | ULPI_SESS_END;
/* enable id interrupt only when transceiver is available */
if (ui->xceiv)
writel(readl(USB_OTGSC) | OTGSC_BSVIE | OTGSC_IDIE, USB_OTGSC);
else {
writel((readl(USB_OTGSC) | OTGSC_BSVIE) & ~OTGSC_IDPU,
USB_OTGSC);
ulpi_write(ui, ULPI_IDPU, ULPI_OTG_CTRL_CLR);
val |= ULPI_HOST_DISCONNECT | ULPI_ID_GND;
}
ulpi_write(ui, val, ULPI_INT_RISE_CLR);
ulpi_write(ui, val, ULPI_INT_FALL_CLR);
clk_disable(ui->clk);
}
static void usb_lpm_wakeup_phy(struct work_struct *w)
{
struct usb_info *ui = the_usb_info;
unsigned long flags;
if (usb_wakeup_phy(ui)) {
pr_err("fatal error: cannot bring phy out of lpm\n");
pr_err("%s: resetting controller\n", __func__);
spin_lock_irqsave(&ui->lock, flags);
usb_disable_pullup(ui);
ui->flags = USB_FLAG_RESET;
queue_delayed_work(usb_work, &ui->work, 0);
enable_irq(ui->irq);
spin_unlock_irqrestore(&ui->lock, flags);
return;
}
ui->in_lpm = 0;
if (ui->xceiv)
ui->xceiv->set_suspend(ui->xceiv, 0);
enable_irq(ui->irq);
}
void usb_function_reenumerate(void)
{
struct usb_info *ui = the_usb_info;
/* disable and re-enable the D+ pullup */
pr_info("hsusb: disable pullup\n");
usb_disable_pullup(ui);
msleep(10);
pr_info("hsusb: enable pullup\n");
usb_enable_pullup(ui);
}
#if defined(CONFIG_DEBUG_FS)
static char debug_buffer[PAGE_SIZE];
static ssize_t debug_read_status(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct usb_info *ui = file->private_data;
char *buf = debug_buffer;
unsigned long flags;
struct usb_endpoint *ept;
struct msm_request *req;
int n;
int i = 0;
spin_lock_irqsave(&ui->lock, flags);
i += scnprintf(buf + i, PAGE_SIZE - i,
"regs: setup=%08x prime=%08x stat=%08x done=%08x\n",
readl(USB_ENDPTSETUPSTAT),
readl(USB_ENDPTPRIME),
readl(USB_ENDPTSTAT),
readl(USB_ENDPTCOMPLETE));
i += scnprintf(buf + i, PAGE_SIZE - i,
"regs: cmd=%08x sts=%08x intr=%08x port=%08x\n\n",
readl(USB_USBCMD),
readl(USB_USBSTS),
readl(USB_USBINTR),
readl(USB_PORTSC));
for (n = 0; n < 32; n++) {
ept = ui->ept + n;
if (ept->max_pkt == 0)
continue;
i += scnprintf(buf + i, PAGE_SIZE - i,
"ept%d %s cfg=%08x active=%08x next=%08x info=%08x\n",
ept->num, (ept->flags & EPT_FLAG_IN) ? "in " : "out",
ept->head->config, ept->head->active,
ept->head->next, ept->head->info);
for (req = ept->req; req; req = req->next)
i += scnprintf(buf + i, PAGE_SIZE - i,
" req @%08x next=%08x info=%08x page0=%08x %c %c\n",
req->item_dma, req->item->next,
req->item->info, req->item->page0,
req->busy ? 'B' : ' ',
req->live ? 'L' : ' '
);
}
i += scnprintf(buf + i, PAGE_SIZE - i,
"phy failure count: %d\n", ui->phy_fail_count);
spin_unlock_irqrestore(&ui->lock, flags);
return simple_read_from_buffer(ubuf, count, ppos, buf, i);
}
static ssize_t debug_write_reset(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct usb_info *ui = file->private_data;
unsigned long flags;
spin_lock_irqsave(&ui->lock, flags);
ui->flags |= USB_FLAG_RESET;
queue_delayed_work(usb_work, &ui->work, 0);
spin_unlock_irqrestore(&ui->lock, flags);
return count;
}
static ssize_t debug_write_cycle(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
usb_function_reenumerate();
return count;
}
static int debug_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
const struct file_operations debug_stat_ops = {
.open = debug_open,
.read = debug_read_status,
};
const struct file_operations debug_reset_ops = {
.open = debug_open,
.write = debug_write_reset,
};
const struct file_operations debug_cycle_ops = {
.open = debug_open,
.write = debug_write_cycle,
};
static struct dentry *debugfs_dent;
static struct dentry *debugfs_status;
static struct dentry *debugfs_reset;
static struct dentry *debugfs_cycle;
static void usb_debugfs_init(struct usb_info *ui)
{
debugfs_dent = debugfs_create_dir("usb", 0);
if (IS_ERR(debugfs_dent))
return;
debugfs_status = debugfs_create_file("status", 0444,
debugfs_dent, ui, &debug_stat_ops);
debugfs_reset = debugfs_create_file("reset", 0222,
debugfs_dent, ui, &debug_reset_ops);
debugfs_cycle = debugfs_create_file("cycle", 0222,
debugfs_dent, ui, &debug_cycle_ops);
}
static void usb_debugfs_uninit(void)
{
debugfs_remove(debugfs_status);
debugfs_remove(debugfs_reset);
debugfs_remove(debugfs_cycle);
debugfs_remove(debugfs_dent);
}
#else
static void usb_debugfs_init(struct usb_info *ui) {}
static void usb_debugfs_uninit(void) {}
#endif
static void usb_configure_device_descriptor(struct usb_info *ui)
{
desc_device.idVendor = ui->pdata->vendor_id;
desc_device.idProduct = ui->composition->product_id;
desc_device.bcdDevice = ui->pdata->version;
if (ui->pdata->serial_number)
desc_device.iSerialNumber =
usb_msm_get_next_strdesc_id(ui->pdata->serial_number);
if (ui->pdata->product_name)
desc_device.iProduct =
usb_msm_get_next_strdesc_id(ui->pdata->product_name);
if (ui->pdata->manufacturer_name)
desc_device.iManufacturer =
usb_msm_get_next_strdesc_id(
ui->pdata->manufacturer_name);
/* Send Serial number to A9 for software download */
if (ui->pdata->serial_number) {
msm_hsusb_is_serial_num_null(FALSE);
msm_hsusb_send_serial_number(ui->pdata->serial_number);
} else
msm_hsusb_is_serial_num_null(TRUE);
msm_hsusb_send_productID(desc_device.idProduct);
}
static ssize_t msm_hsusb_store_func_enable(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
char name[20];
int enable = 0;
int i;
for (i = 0; buf[i] != 0; i++) {
if (buf[i] == '=')
break;
name[i] = buf[i];
}
name[i++] = 0;
if (buf[i] == '0' || buf[i] == '1')
enable = buf[i] - '0';
else
return size;
pr_info("%s: name = %s, enable = %d\n", __func__, name, enable);
usb_function_enable(name, enable);
return size;
}
static ssize_t msm_hsusb_show_compswitch(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct usb_info *ui = the_usb_info;
int i;
if (ui->composition)
i = scnprintf(buf, PAGE_SIZE,
"composition product id = %x\n",
ui->composition->product_id);
else
i = scnprintf(buf, PAGE_SIZE,
"composition product id = 0\n");
return i;
}
static ssize_t msm_hsusb_store_compswitch(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
unsigned long pid;
if (!strict_strtoul(buf, 16, &pid)) {
pr_info("%s: Requested New Product id = %lx\n", __func__, pid);
usb_switch_composition((unsigned short)pid);
} else
pr_info("%s: strict_strtoul conversion failed\n", __func__);
return size;
}
static ssize_t msm_hsusb_store_autoresume(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
usb_remote_wakeup();
return size;
}
static ssize_t msm_hsusb_show_state(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct usb_info *ui = the_usb_info;
int i;
char *state[] = {"USB_STATE_NOTATTACHED", "USB_STATE_ATTACHED",
"USB_STATE_POWERED", "USB_STATE_UNAUTHENTICATED",
"USB_STATE_RECONNECTING", "USB_STATE_DEFAULT",
"USB_STATE_ADDRESS", "USB_STATE_CONFIGURED",
"USB_STATE_SUSPENDED"
};
i = scnprintf(buf, PAGE_SIZE, "%s\n", state[ui->usb_state]);
return i;
}
static ssize_t msm_hsusb_show_lpm(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct usb_info *ui = the_usb_info;
int i;
i = scnprintf(buf, PAGE_SIZE, "%d\n", ui->in_lpm);
return i;
}
static ssize_t msm_hsusb_show_speed(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct usb_info *ui = the_usb_info;
int i;
char *speed[] = {"USB_SPEED_UNKNOWN", "USB_SPEED_LOW",
"USB_SPEED_FULL", "USB_SPEED_HIGH"};
i = scnprintf(buf, PAGE_SIZE, "%s\n", speed[ui->speed]);
return i;
}
static DEVICE_ATTR(composition, 0664,
msm_hsusb_show_compswitch, msm_hsusb_store_compswitch);
static DEVICE_ATTR(func_enable, S_IWUSR,
NULL, msm_hsusb_store_func_enable);
static DEVICE_ATTR(autoresume, 0222,
NULL, msm_hsusb_store_autoresume);
static DEVICE_ATTR(state, 0664, msm_hsusb_show_state, NULL);
static DEVICE_ATTR(lpm, 0664, msm_hsusb_show_lpm, NULL);
static DEVICE_ATTR(speed, 0664, msm_hsusb_show_speed, NULL);
static struct attribute *msm_hsusb_attrs[] = {
&dev_attr_composition.attr,
&dev_attr_func_enable.attr,
&dev_attr_autoresume.attr,
&dev_attr_state.attr,
&dev_attr_lpm.attr,
&dev_attr_speed.attr,
NULL,
};
static struct attribute_group msm_hsusb_attr_grp = {
.attrs = msm_hsusb_attrs,
};
#define msm_hsusb_func_attr(function, index) \
static ssize_t show_##function(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct usb_info *ui = the_usb_info; \
struct usb_function_info *fi = ui->func[index]; \
\
return sprintf(buf, "%d", fi->enabled); \
\
} \
\
static DEVICE_ATTR(function, S_IRUGO, show_##function, NULL);
msm_hsusb_func_attr(diag, 0);
msm_hsusb_func_attr(adb, 1);
msm_hsusb_func_attr(modem, 2);
msm_hsusb_func_attr(nmea, 3);
msm_hsusb_func_attr(mass_storage, 4);
msm_hsusb_func_attr(ethernet, 5);
msm_hsusb_func_attr(rmnet, 6);
static struct attribute *msm_hsusb_func_attrs[] = {
&dev_attr_diag.attr,
&dev_attr_adb.attr,
&dev_attr_modem.attr,
&dev_attr_nmea.attr,
&dev_attr_mass_storage.attr,
&dev_attr_ethernet.attr,
&dev_attr_rmnet.attr,
NULL,
};
static struct attribute_group msm_hsusb_func_attr_grp = {
.name = "functions",
.attrs = msm_hsusb_func_attrs,
};
static int __init usb_probe(struct platform_device *pdev)
{
struct resource *res;
struct usb_info *ui;
int irq;
int ulpi_irq1 = 0;
int ulpi_irq2 = 0;
int i;
int ret = 0;
if (!pdev || !pdev->dev.platform_data) {
pr_err("%s:pdev or platform data is null\n", __func__);
return -ENODEV;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
pr_err("%s: failed to get irq num from platform_get_irq\n",
__func__);
return -ENODEV;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
pr_err("%s: failed to get mem resource\n", __func__);
return -ENODEV;
}
ret = sysfs_create_group(&pdev->dev.kobj, &msm_hsusb_attr_grp);
if (ret) {
pr_err("%s: unable to create sysfs group\n", __func__);
return ret;
}
usb_work = create_singlethread_workqueue("usb_work");
if (!usb_work) {
pr_err("%s: unable to create work queue\n", __func__);
ret = -ENOMEM;
goto free_sysfs_grp;
}
ui = kzalloc(sizeof(struct usb_info), GFP_KERNEL);
if (!ui) {
pr_err("%s: unable to allocate memory for ui\n", __func__);
ret = -ENOMEM;
goto free_workqueue;
}
ui->pdev = pdev;
ui->pdata = pdev->dev.platform_data;
for (i = 0; i < ui->pdata->num_compositions; i++)
if (ui->pdata->compositions[i].product_id == pid) {
ui->composition = &ui->pdata->compositions[i];
break;
}
if (!ui->composition) {
pr_err("%s: unable to find the composition with pid:(%d)\n",
__func__, pid);
ret = -ENODEV;
goto free_ui;
}
ui->phy_info = ui->pdata->phy_info;
if (ui->phy_info == USB_PHY_UNDEFINED) {
pr_err("undefined phy_info: (%d)\n", ui->phy_info);
ret = -ENOMEM;
goto free_ui;
}
/* zero is reserved for language id */
ui->strdesc_index = 1;
ui->strdesc = kzalloc(sizeof(char *) * MAX_STRDESC_NUM, GFP_KERNEL);
if (!ui->strdesc) {
pr_err("%s: unable allocate mem for string descriptors\n",
__func__);
ret = -ENOMEM;
goto free_ui;
}
ui->num_funcs = ui->pdata->num_functions;
ui->func = kzalloc(sizeof(struct usb_function *) * ui->num_funcs,
GFP_KERNEL);
if (!ui->func) {
pr_err("%s: unable allocate mem for functions\n", __func__);
ret = -ENOMEM;
goto free_str_desc;
}
ret = sysfs_create_group(&pdev->dev.kobj, &msm_hsusb_func_attr_grp);
if (ret) {
pr_err("%s: unable to create functions sysfs group\n",
__func__);
goto free_func;
}
ui->addr = ioremap(res->start, resource_size(res));
if (!ui->addr) {
pr_err("%s: unable ioremap\n", __func__);
ret = -ENOMEM;
goto free_func_sysfs_grp;
}
ui->buf = dma_alloc_coherent(&pdev->dev, 4096, &ui->dma, GFP_KERNEL);
if (!ui->buf) {
pr_err("%s: failed allocate dma coherent memory\n", __func__);
ret = -ENOMEM;
goto free_iounmap;
}
ui->pool = dma_pool_create("hsusb", NULL, 32, 32, 0);
if (!ui->pool) {
pr_err("%s: unable to allocate dma pool\n", __func__);
ret = -ENOMEM;
goto free_dma_coherent;
}
ui->clk = clk_get(&pdev->dev, "usb_hs_clk");
if (IS_ERR(ui->clk)) {
pr_err("%s: unable get usb_hs_clk\n", __func__);
ret = PTR_ERR(ui->clk);
goto free_dma_pool;
}
ui->pclk = clk_get(&pdev->dev, "usb_hs_pclk");
if (IS_ERR(ui->pclk)) {
pr_err("%s: unable get usb_hs_pclk\n", __func__);
ret = PTR_ERR(ui->pclk);
goto free_hs_clk;
}
if (ui->pdata->core_clk) {
ui->cclk = clk_get(&pdev->dev, "usb_hs_core_clk");
if (IS_ERR(ui->cclk)) {
pr_err("%s: unable get usb_hs_core_clk\n", __func__);
ret = PTR_ERR(ui->cclk);
goto free_hs_pclk;
}
}
if (ui->pdata->vreg5v_required) {
ui->vreg = vreg_get(NULL, "boost");
if (IS_ERR(ui->vreg)) {
pr_err("%s: vreg get failed\n", __func__);
ui->vreg = NULL;
ret = PTR_ERR(ui->vreg);
goto free_hs_cclk;
}
}
/* disable interrupts before requesting irq */
usb_clk_enable(ui);
writel(0, USB_USBINTR);
writel(readl(USB_OTGSC) & ~OTGSC_INTR_MASK, USB_OTGSC);
usb_clk_disable(ui);
ret = request_irq(irq, usb_interrupt, IRQF_SHARED, pdev->name, ui);
if (ret) {
pr_err("%s: request_irq failed\n", __func__);
goto free_vreg5v;
}
ui->irq = irq;
if (ui->pdata->config_gpio) {
usb_lpm_config_gpio = ui->pdata->config_gpio;
ulpi_irq1 = platform_get_irq_byname(pdev, "vbus_interrupt");
if (ulpi_irq1 < 0) {
pr_err("%s: failed to get vbus gpio interrupt\n",
__func__);
return -ENODEV;
}
ulpi_irq2 = platform_get_irq_byname(pdev, "id_interrupt");
if (ulpi_irq2 < 0) {
pr_err("%s: failed to get id gpio interrupt\n",
__func__);
return -ENODEV;
}
ret = request_irq(ulpi_irq1,
&usb_lpm_gpio_isr,
IRQF_TRIGGER_HIGH,
"vbus_interrupt", NULL);
if (ret) {
pr_err("%s: failed to request vbus interrupt:(%d)\n",
__func__, ulpi_irq1);
goto free_irq;
}
ret = request_irq(ulpi_irq2,
&usb_lpm_gpio_isr,
IRQF_TRIGGER_RISING,
"usb_ulpi_data3", NULL);
if (ret) {
pr_err("%s: failed to request irq ulpi_data_3:(%d)\n",
__func__, ulpi_irq2);
goto free_ulpi_irq1;
}
ui->gpio_irq[0] = ulpi_irq1;
ui->gpio_irq[1] = ulpi_irq2;
}
ui->sdev.name = DRIVER_NAME;
ui->sdev.print_name = print_switch_name;
ui->sdev.print_state = print_switch_state;
ret = switch_dev_register(&ui->sdev);
if (ret < 0) {
pr_err("%s(): switch_dev_register failed ret = %d\n",
__func__, ret);
goto free_ulpi_irq2;
}
the_usb_info = ui;
ui->functions_map = ui->pdata->function_map;
ui->selfpowered = 0;
ui->remote_wakeup = 0;
ui->maxpower = 0xFA;
ui->chg_type = USB_CHG_TYPE__INVALID;
/* to allow swfi latency, driver latency
* must be above listed swfi latency
*/
ui->pdata->swfi_latency += 1;
spin_lock_init(&ui->lock);
msm_hsusb_suspend_locks_init(ui, 1);
enable_irq_wake(irq);
/* memory barrier initialization in non-interrupt context */
dmb();
usb_debugfs_init(ui);
usb_prepare(ui);
pr_info("%s: io=%p, irq=%d, dma=%p(%x)\n",
__func__, ui->addr, ui->irq, ui->buf, ui->dma);
return 0;
free_ulpi_irq2:
free_irq(ulpi_irq2, NULL);
free_ulpi_irq1:
free_irq(ulpi_irq1, NULL);
free_irq:
free_irq(ui->irq, ui);
free_vreg5v:
if (ui->pdata->vreg5v_required)
vreg_put(ui->vreg);
free_hs_cclk:
clk_put(ui->cclk);
free_hs_pclk:
clk_put(ui->pclk);
free_hs_clk:
clk_put(ui->clk);
free_dma_pool:
dma_pool_destroy(ui->pool);
free_dma_coherent:
dma_free_coherent(&pdev->dev, 4096, ui->buf, ui->dma);
free_iounmap:
iounmap(ui->addr);
free_func_sysfs_grp:
sysfs_remove_group(&pdev->dev.kobj, &msm_hsusb_func_attr_grp);
free_func:
kfree(ui->func);
free_str_desc:
kfree(ui->strdesc);
free_ui:
kfree(ui);
free_workqueue:
destroy_workqueue(usb_work);
free_sysfs_grp:
sysfs_remove_group(&pdev->dev.kobj, &msm_hsusb_attr_grp);
return ret;
}
#ifdef CONFIG_PM
static int usb_platform_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct usb_info *ui = the_usb_info;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&ui->lock, flags);
if (!ui->active) {
spin_unlock_irqrestore(&ui->lock, flags);
pr_info("%s: peripheral mode is not active"
"nothing to be done\n", __func__);
return 0;
}
if (ui->in_lpm) {
spin_unlock_irqrestore(&ui->lock, flags);
pr_info("%s: we are already in lpm, nothing to be done\n",
__func__);
return 0;
}
spin_unlock_irqrestore(&ui->lock, flags);
ret = usb_lpm_enter(ui);
if (ret)
pr_err("%s: failed to enter lpm\n", __func__);
return ret;
}
#endif
static struct platform_driver usb_driver = {
.probe = usb_probe,
#ifdef CONFIG_PM
.suspend = usb_platform_suspend,
#endif
.driver = { .name = DRIVER_NAME, },
};
static int __init usb_module_init(void)
{
/* rpc connect for phy_reset */
msm_hsusb_rpc_connect();
/* rpc connect for charging */
msm_chg_rpc_connect();
return platform_driver_register(&usb_driver);
}
static void free_usb_info(void)
{
struct usb_info *ui = the_usb_info;
unsigned long flags;
int i;
if (ui) {
INIT_LIST_HEAD(&usb_function_list);
for (i = 0; i < ui->num_funcs; i++)
kfree(ui->func[i]);
ui->num_funcs = 0;
usb_uninit(ui);
kfree(ui->strdesc);
usb_ept_free_req(&ui->ep0in, ui->setup_req);
if (ui->ept[0].ui == ui)
flush_all_endpoints(ui);
spin_lock_irqsave(&ui->lock, flags);
usb_clk_disable(ui);
usb_vreg_disable(ui);
spin_unlock_irqrestore(&ui->lock, flags);
usb_free(ui, 0);
the_usb_info = NULL;
}
}
static void usb_exit(void)
{
struct usb_info *ui = the_usb_info;
/* free the dev state structure */
if (!ui)
return;
if (ui->xceiv) {
ui->xceiv->set_peripheral(ui->xceiv, NULL);
msm_otg_put_transceiver(ui->xceiv);
}
cancel_work_sync(&ui->li.wakeup_phy);
destroy_workqueue(usb_work);
/* free the usb_info structure */
free_usb_info();
switch_dev_unregister(&ui->sdev);
sysfs_remove_group(&ui->pdev->dev.kobj, &msm_hsusb_func_attr_grp);
sysfs_remove_group(&ui->pdev->dev.kobj, &msm_hsusb_attr_grp);
usb_debugfs_uninit();
platform_driver_unregister(&usb_driver);
msm_hsusb_rpc_close();
msm_chg_rpc_close();
msm_pm_app_unregister_vbus_sn(&msm_hsusb_set_vbus_state);
msm_pm_app_rpc_deinit();
}
static void __exit usb_module_exit(void)
{
usb_exit();
}
module_param(pid, int, 0);
MODULE_PARM_DESC(pid, "Product ID of the desired composition");
module_init(usb_module_init);
module_exit(usb_module_exit);
static void copy_string_descriptor(char *string, char *buffer)
{
int length, i;
if (string) {
length = strlen(string);
buffer[0] = 2 * length + 2;
buffer[1] = USB_DT_STRING;
for (i = 0; i < length; i++) {
buffer[2 * i + 2] = string[i];
buffer[2 * i + 3] = 0;
}
}
}
static int get_qualifier_descriptor(struct usb_qualifier_descriptor *dq)
{
struct usb_qualifier_descriptor *dev_qualifier = dq;
dev_qualifier->bLength = sizeof(struct usb_qualifier_descriptor),
dev_qualifier->bDescriptorType = USB_DT_DEVICE_QUALIFIER,
dev_qualifier->bcdUSB = __constant_cpu_to_le16(0x0200),
dev_qualifier->bDeviceClass = USB_CLASS_PER_INTERFACE,
dev_qualifier->bDeviceSubClass = 0;
dev_qualifier->bDeviceProtocol = 0;
dev_qualifier->bMaxPacketSize0 = 64;
dev_qualifier->bNumConfigurations = 1;
dev_qualifier->bRESERVED = 0;
return sizeof(struct usb_qualifier_descriptor);
}
static int usb_fill_descriptors(void *ptr,
struct usb_descriptor_header **descriptors)
{
unsigned char *buf = ptr;
struct usb_descriptor_header *item = descriptors[0];
unsigned cnt = 0;
while (NULL != item) {
unsigned len = item->bLength;
memcpy(buf, item, len);
buf += len;
cnt++;
item = descriptors[cnt];
}
return buf-(u8 *)ptr;
}
static int usb_find_descriptor(struct usb_info *ui, struct usb_ctrlrequest *ctl,
struct usb_request *req)
{
int i;
unsigned short id = ctl->wValue;
unsigned short type = id >> 8;
id &= 0xff;
if ((type == USB_DT_DEVICE) && (id == 0)) {
req->length = sizeof(desc_device);
if (usb_msm_is_iad()) {
desc_device.bDeviceClass = 0xEF;
desc_device.bDeviceSubClass = 0x02;
desc_device.bDeviceProtocol = 0x01;
}
memcpy(req->buf, &desc_device, req->length);
return 0;
}
if ((type == USB_DT_DEVICE_QUALIFIER) && (id == 0)) {
struct usb_qualifier_descriptor dq;
req->length = get_qualifier_descriptor(&dq);
if (usb_msm_is_iad()) {
dq.bDeviceClass = 0xEF;
dq.bDeviceSubClass = 0x02;
dq.bDeviceProtocol = 0x01;
}
memcpy(req->buf, &dq, req->length);
return 0;
}
if ((type == USB_DT_OTHER_SPEED_CONFIG) && (id == 0))
goto get_config;
if ((type == USB_DT_CONFIG) && (id == 0)) {
struct usb_config_descriptor cfg;
unsigned ifc_count = 0;
char *ptr, *start;
get_config:
ifc_count = 0;
start = req->buf;
ptr = start + USB_DT_CONFIG_SIZE;
ifc_count = ui->next_ifc_num;
for (i = 0; i < ui->num_funcs; i++) {
struct usb_function_info *fi = ui->func[i];
struct usb_descriptor_header **dh = NULL;
if (!fi || !(ui->composition->functions & (1 << i)))
continue;
switch (ui->speed) {
case USB_SPEED_HIGH:
if (type == USB_DT_OTHER_SPEED_CONFIG)
dh = fi->func->fs_descriptors;
else
dh = fi->func->hs_descriptors;
break;
case USB_SPEED_FULL:
if (type == USB_DT_OTHER_SPEED_CONFIG)
dh = fi->func->hs_descriptors;
else
dh = fi->func->fs_descriptors;
break;
default:
printk(KERN_ERR "Unsupported speed(%x)\n",
ui->speed);
return -1;
}
ptr += usb_fill_descriptors(ptr, dh);
}
#define USB_REMOTE_WAKEUP_SUPPORT 1
cfg.bLength = USB_DT_CONFIG_SIZE;
if (type == USB_DT_OTHER_SPEED_CONFIG)
cfg.bDescriptorType = USB_DT_OTHER_SPEED_CONFIG;
else
cfg.bDescriptorType = USB_DT_CONFIG;
cfg.wTotalLength = ptr - start;
cfg.bNumInterfaces = ifc_count;
cfg.bConfigurationValue = 1;
cfg.iConfiguration = 0;
cfg.bmAttributes = USB_CONFIG_ATT_ONE |
ui->selfpowered << USB_CONFIG_ATT_SELFPOWER_POS |
USB_REMOTE_WAKEUP_SUPPORT << USB_CONFIG_ATT_WAKEUP_POS;
cfg.bMaxPower = ui->maxpower;
memcpy(start, &cfg, USB_DT_CONFIG_SIZE);
req->length = ptr - start;
return 0;
}
if (type == USB_DT_STRING) {
char *buffer = req->buf;
buffer[0] = 0;
if (id > ui->strdesc_index)
return -1;
if (id == STRING_LANGUAGE_ID)
memcpy(buffer, str_lang_desc, str_lang_desc[0]);
else
copy_string_descriptor(ui->strdesc[id], buffer);
if (buffer[0]) {
req->length = buffer[0];
return 0;
} else
return -1;
}
return -1;
}
/*****Gadget Framework Functions***/
struct device *usb_get_device(void)
{
if (the_usb_info) {
if (the_usb_info->pdev)
return &(the_usb_info->pdev->dev);
}
return NULL;
}
EXPORT_SYMBOL(usb_get_device);
int usb_ept_cancel_xfer(struct usb_endpoint *ept, struct usb_request *_req)
{
struct usb_info *ui = the_usb_info;
struct msm_request *req = to_msm_request(_req);
struct msm_request *temp_req, *prev_req;
unsigned long flags;
if (!(ui && req && ept->req))
return -EINVAL;
spin_lock_irqsave(&ui->lock, flags);
if (req->busy) {
req->req.status = 0;
req->busy = 0;
/* See if the request is the first request in the ept queue */
if (ept->req == req) {
/* Stop the transfer */
do {
writel((1 << ept->bit), USB_ENDPTFLUSH);
while (readl(USB_ENDPTFLUSH) & (1 << ept->bit))
udelay(100);
} while (readl(USB_ENDPTSTAT) & (1 << ept->bit));
if (!req->next)
ept->last = NULL;
ept->req = req->next;
ept->head->next = req->item->next;
goto cancel_req;
}
/* Request could be in the middle of ept queue */
prev_req = temp_req = ept->req;
do {
if (req == temp_req) {
if (req->live) {
/* Stop the transfer */
do {
writel((1 << ept->bit),
USB_ENDPTFLUSH);
while (readl(USB_ENDPTFLUSH) &
(1 << ept->bit))
udelay(100);
} while (readl(USB_ENDPTSTAT) &
(1 << ept->bit));
}
prev_req->next = temp_req->next;
prev_req->item->next = temp_req->item->next;
if (!req->next)
ept->last = prev_req;
goto cancel_req;
}
prev_req = temp_req;
temp_req = temp_req->next;
} while (temp_req != NULL);
goto error;
cancel_req:
if (req->live) {
/* prepare the transaction descriptor item for the hardware */
req->item->next = TERMINATE;
req->item->info = 0;
req->live = 0;
dma_unmap_single(NULL, req->dma, req->req.length,
(ept->flags & EPT_FLAG_IN) ?
DMA_TO_DEVICE : DMA_FROM_DEVICE);
/* Reprime the endpoint for the remaining transfers */
if (ept->req) {
temp_req = ept->req;
while (temp_req != NULL) {
temp_req->live = 0;
temp_req = temp_req->next;
}
usb_ept_start(ept);
}
} else
dma_unmap_single(NULL, req->dma, req->req.length,
(ept->flags & EPT_FLAG_IN) ?
DMA_TO_DEVICE : DMA_FROM_DEVICE);
spin_unlock_irqrestore(&ui->lock, flags);
return 0;
}
error:
spin_unlock_irqrestore(&ui->lock, flags);
return -EINVAL;
}
EXPORT_SYMBOL(usb_ept_cancel_xfer);
int usb_ept_set_halt(struct usb_endpoint *ept)
{
struct usb_info *ui = ept->ui;
int in = ept->flags & EPT_FLAG_IN;
unsigned n;
if (ui->in_lpm) {
pr_err("%s: controller is in lpm, cannot proceed\n", __func__);
return -1;
}
ept->ept_halted = 1;
n = readl(USB_ENDPTCTRL(ept->num));
if (in)
n |= CTRL_TXS;
else
n |= CTRL_RXS;
writel(n, USB_ENDPTCTRL(ept->num));
return 0;
}
EXPORT_SYMBOL(usb_ept_set_halt);
int usb_ept_clear_halt(struct usb_endpoint *ept)
{
struct usb_info *ui = ept->ui;
int in = ept->flags & EPT_FLAG_IN;
unsigned n;
if (ui->in_lpm) {
pr_err("%s: controller is in lpm, cannot proceed\n", __func__);
return -1;
}
if (ept->ept_halted)
ept->ept_halted = 0;
n = readl(USB_ENDPTCTRL(ept->num));
/*clear stall bit and set data toggle bit*/
if (in) {
n &= (~CTRL_TXS);
n |= (CTRL_TXR);
} else {
n &= ~(CTRL_RXS);
n |= (CTRL_RXR);
}
writel(n, USB_ENDPTCTRL(ept->num));
return 0;
}
EXPORT_SYMBOL(usb_ept_clear_halt);
int usb_ept_is_stalled(struct usb_endpoint *ept)
{
struct usb_info *ui = ept->ui;
int in = ept->flags & EPT_FLAG_IN;
unsigned n;
n = readl(USB_ENDPTCTRL(ept->num));
if (in && (n & CTRL_TXS))
return 1;
else if (n & CTRL_RXS)
return 1;
return 0;
}
void usb_ept_fifo_flush(struct usb_endpoint *ept)
{
flush_endpoint(ept);
}
EXPORT_SYMBOL(usb_ept_fifo_flush);
struct usb_function *usb_ept_get_function(struct usb_endpoint *ept)
{
return NULL;
}
EXPORT_SYMBOL(usb_ept_get_function);
void usb_free_endpoint_all_req(struct usb_endpoint *ep)
{
struct msm_request *temp;
struct msm_request *req;
if (!ep)
return;
req = ep->req;
while (req) {
temp = req->next;
req->busy = 0;
if (&req->req)
usb_ept_free_req(ep, &req->req);
req = temp;
}
}
EXPORT_SYMBOL(usb_free_endpoint_all_req);
int usb_function_unregister(struct usb_function *func)
{
struct usb_info *ui = the_usb_info;
int i;
struct usb_function_info *fi;
unsigned long flags;
if (!func)
return -EINVAL;
fi = usb_find_function(func->name);
if (!fi)
return -EINVAL;
if (ui->running) {
disable_irq(ui->irq);
spin_lock_irqsave(&ui->lock, flags);
ui->running = 0;
ui->online = 0;
ui->bound = 0;
spin_unlock_irqrestore(&ui->lock, flags);
usb_uninit(ui);
/* we should come out of lpm to access registers */
if (ui->in_lpm) {
if (PHY_TYPE(ui->phy_info) == USB_PHY_EXTERNAL) {
disable_irq(ui->gpio_irq[0]);
disable_irq(ui->gpio_irq[1]);
}
usb_lpm_exit(ui);
if (cancel_work_sync(&ui->li.wakeup_phy))
usb_lpm_wakeup_phy(NULL);
ui->in_lpm = 0;
}
/* disable usb and session valid interrupts */
writel(0, USB_USBINTR);
writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC);
/* stop the controller */
usb_disable_pullup(ui);
msleep(100);
enable_irq(ui->irq);
}
pr_info("%s: func->name = %s\n", __func__, func->name);
ui->composition = NULL;
if (func->configure)
func->configure(0, func->context);
if (func->unbind)
func->unbind(func->context);
list_del(&fi->list);
for (i = 0; i < ui->num_funcs; i++)
if (fi == ui->func[i])
ui->func[i] = NULL;
kfree(fi);
return 0;
}
EXPORT_SYMBOL(usb_function_unregister);
MODULE_LICENSE("GPL");