| /* 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"); |
| |