Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/usb/function/msm_hsusb.c b/drivers/usb/function/msm_hsusb.c
new file mode 100644
index 0000000..eebd9d4
--- /dev/null
+++ b/drivers/usb/function/msm_hsusb.c
@@ -0,0 +1,3948 @@
+/* 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");
+