Hi I wanna propose the introduction of usb gadget for msm72k into https://www.codeaurora.org/gitweb/quic/kernel/?p=dwalker/linux-msm.git;a=tree;h=refs/heads/for-next;hb=for-next sadly the following patch seems to have some flaws. I finds the internal chip and creates a iface usb0, but doesnt get up and online. I do not get an usb0 popping up on hostside either. Someone an idea how to fix this code? best regards leviathan --- arch/arm/mach-msm/include/mach/msm_hsusb.h | 74 ++ arch/arm/mach-msm/include/mach/msm_hsusb_hw.h | 204 +++ drivers/usb/gadget/Kconfig | 17 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/msm72k_udc.c | 1738 +++++++++++++++++++++++++ 5 files changed, 2034 insertions(+), 0 deletions(-) create mode 100644 arch/arm/mach-msm/include/mach/msm_hsusb.h create mode 100644 arch/arm/mach-msm/include/mach/msm_hsusb_hw.h create mode 100644 drivers/usb/gadget/msm72k_udc.c diff --git a/arch/arm/mach-msm/include/mach/msm_hsusb.h b/arch/arm/mach-msm/include/mach/msm_hsusb.h new file mode 100644 index 0000000..2d49b0f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_hsusb.h @@ -0,0 +1,74 @@ +/* linux/include/asm-arm/arch-msm/hsusb.h + * + * Copyright (C) 2008 Google, Inc. + * Author: Brian Swetland <swetland@xxxxxxxxxx> + * + * 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. + * + */ + +#ifndef __ASM_ARCH_MSM_HSUSB_H +#define __ASM_ARCH_MSM_HSUSB_H + +#include <linux/types.h> + +/* platform device data for msm_hsusb driver */ + +#ifdef CONFIG_USB_FUNCTION +/* matches a product ID to a list of enabled functions */ +struct msm_hsusb_product { + /* product ID for usb_device_descriptor.idProduct */ + __u16 product_id; + + /* bit mask of enabled usb_functions, matching ordering + ** in msm_hsusb_platform_data.functions + */ + __u32 functions; +}; +#endif + +struct msm_hsusb_platform_data { + /* hard reset the ULPI PHY */ + void (*phy_reset)(void); + + /* for notification when USB is connected or disconnected */ + void (*usb_connected)(int); + + /* val, reg pairs terminated by -1 */ + int *phy_init_seq; + +#ifdef CONFIG_USB_FUNCTION + /* USB device descriptor fields */ + __u16 vendor_id; + + /* Default product ID. + ** This can be overridden dynamically based on the disabled + ** state of the functions using the product_table. + */ + __u16 product_id; + + __u16 version; + char *serial_number; + char *product_name; + char *manufacturer_name; + + /* list of function drivers to bind to this configuration */ + int num_functions; + char **functions; + + /* if num_products is zero, then the default value in product_id + ** is used for the configuration descriptor. + */ + int num_products; + struct msm_hsusb_product *products; +#endif +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_hsusb_hw.h b/arch/arm/mach-msm/include/mach/msm_hsusb_hw.h new file mode 100644 index 0000000..8042b31 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_hsusb_hw.h @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland <swetland@xxxxxxxxxx> + * + * 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. + * + */ + +#ifndef __LINUX_USB_GADGET_MSM72K_UDC_H__ +#define __LINUX_USB_GADGET_MSM72K_UDC_H__ + +/*-------------------------------------------------------------------------*/ + +#define xprintk(level, fmt, args...) \ + printk(level "%s: " fmt , driver_name , ## args) + +#ifdef DEBUG +#undef DEBUG +#define DEBUG(fmt, args...) \ + xprintk(KERN_DEBUG , fmt , ## args) +#else +#define DEBUG(fmt,args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE +#define VDEBUG DEBUG +#else +#define VDEBUG(fmt,args...) \ + do { } while (0) +#endif /* VERBOSE */ + +#define ERROR(fmt,args...) \ + xprintk(KERN_ERR , fmt , ## args) +#define INFO(fmt,args...) \ + xprintk(KERN_INFO , fmt , ## args) + +/*-------------------------------------------------------------------------*/ + + +#define USB_ID (MSM_USB_BASE + 0x0000) +#define USB_HWGENERAL (MSM_USB_BASE + 0x0004) +#define USB_HWHOST (MSM_USB_BASE + 0x0008) +#define USB_HWDEVICE (MSM_USB_BASE + 0x000C) +#define USB_HWTXBUF (MSM_USB_BASE + 0x0010) +#define USB_HWRXBUF (MSM_USB_BASE + 0x0014) +#define USB_SBUSCFG (MSM_USB_BASE + 0x0090) + +#define USB_CAPLENGTH (MSM_USB_BASE + 0x0100) /* 8 bit */ +#define USB_HCIVERSION (MSM_USB_BASE + 0x0102) /* 16 bit */ +#define USB_HCSPARAMS (MSM_USB_BASE + 0x0104) +#define USB_HCCPARAMS (MSM_USB_BASE + 0x0108) +#define USB_DCIVERSION (MSM_USB_BASE + 0x0120) /* 16 bit */ +#define USB_USBCMD (MSM_USB_BASE + 0x0140) +#define USB_USBSTS (MSM_USB_BASE + 0x0144) +#define USB_USBINTR (MSM_USB_BASE + 0x0148) +#define USB_FRINDEX (MSM_USB_BASE + 0x014C) +#define USB_DEVICEADDR (MSM_USB_BASE + 0x0154) +#define USB_ENDPOINTLISTADDR (MSM_USB_BASE + 0x0158) +#define USB_BURSTSIZE (MSM_USB_BASE + 0x0160) +#define USB_TXFILLTUNING (MSM_USB_BASE + 0x0164) +#define USB_ULPI_VIEWPORT (MSM_USB_BASE + 0x0170) +#define USB_ENDPTNAK (MSM_USB_BASE + 0x0178) +#define USB_ENDPTNAKEN (MSM_USB_BASE + 0x017C) +#define USB_PORTSC (MSM_USB_BASE + 0x0184) +#define USB_OTGSC (MSM_USB_BASE + 0x01A4) +#define USB_USBMODE (MSM_USB_BASE + 0x01A8) +#define USB_ENDPTSETUPSTAT (MSM_USB_BASE + 0x01AC) +#define USB_ENDPTPRIME (MSM_USB_BASE + 0x01B0) +#define USB_ENDPTFLUSH (MSM_USB_BASE + 0x01B4) +#define USB_ENDPTSTAT (MSM_USB_BASE + 0x01B8) +#define USB_ENDPTCOMPLETE (MSM_USB_BASE + 0x01BC) +#define USB_ENDPTCTRL(n) (MSM_USB_BASE + 0x01C0 + (4 * (n))) + + +#define USBCMD_RESET 2 +#define USBCMD_ATTACH 1 +#define USBCMD_ATDTW (1 << 14) + +#define USBMODE_DEVICE 2 +#define USBMODE_HOST 3 + +struct ept_queue_head { + unsigned config; + unsigned active; /* read-only */ + + unsigned next; + unsigned info; + unsigned page0; + unsigned page1; + unsigned page2; + unsigned page3; + unsigned page4; + unsigned reserved_0; + + unsigned char setup_data[8]; + + unsigned reserved_1; + unsigned reserved_2; + unsigned reserved_3; + unsigned reserved_4; +}; + +#define CONFIG_MAX_PKT(n) ((n) << 16) +#define CONFIG_ZLT (1 << 29) /* stop on zero-len xfer */ +#define CONFIG_IOS (1 << 15) /* IRQ on setup */ + +struct ept_queue_item { + unsigned next; + unsigned info; + unsigned page0; + unsigned page1; + unsigned page2; + unsigned page3; + unsigned page4; + unsigned reserved; +}; + +#define TERMINATE 1 + +#define INFO_BYTES(n) ((n) << 16) +#define INFO_IOC (1 << 15) +#define INFO_ACTIVE (1 << 7) +#define INFO_HALTED (1 << 6) +#define INFO_BUFFER_ERROR (1 << 5) +#define INFO_TXN_ERROR (1 << 3) + + +#define STS_NAKI (1 << 16) /* */ +#define STS_SLI (1 << 8) /* R/WC - suspend state entered */ +#define STS_SRI (1 << 7) /* R/WC - SOF recv'd */ +#define STS_URI (1 << 6) /* R/WC - RESET recv'd - write to clear */ +#define STS_FRI (1 << 3) /* R/WC - Frame List Rollover */ +#define STS_PCI (1 << 2) /* R/WC - Port Change Detect */ +#define STS_UEI (1 << 1) /* R/WC - USB Error */ +#define STS_UI (1 << 0) /* R/WC - USB Transaction Complete */ + + +/* bits used in all the endpoint status registers */ +#define EPT_TX(n) (1 << ((n) + 16)) +#define EPT_RX(n) (1 << (n)) + + +#define CTRL_TXE (1 << 23) +#define CTRL_TXR (1 << 22) +#define CTRL_TXI (1 << 21) +#define CTRL_TXD (1 << 17) +#define CTRL_TXS (1 << 16) +#define CTRL_RXE (1 << 7) +#define CTRL_RXR (1 << 6) +#define CTRL_RXI (1 << 5) +#define CTRL_RXD (1 << 1) +#define CTRL_RXS (1 << 0) + +#define CTRL_TXT_MASK (3 << 18) +#define CTRL_TXT_CTRL (0 << 18) +#define CTRL_TXT_ISOCH (1 << 18) +#define CTRL_TXT_BULK (2 << 18) +#define CTRL_TXT_INT (3 << 18) +#define CTRL_TXT_EP_TYPE_SHIFT 18 + +#define CTRL_RXT_MASK (3 << 2) +#define CTRL_RXT_CTRL (0 << 2) +#define CTRL_RXT_ISOCH (1 << 2) +#define CTRL_RXT_BULK (2 << 2) +#define CTRL_RXT_INT (3 << 2) +#define CTRL_RXT_EP_TYPE_SHIFT 2 + +#define ULPI_WAKEUP (1 << 31) +#define ULPI_RUN (1 << 30) +#define ULPI_WRITE (1 << 29) +#define ULPI_READ (0 << 29) +#define ULPI_STATE_NORMAL (1 << 27) +#define ULPI_ADDR(n) (((n) & 255) << 16) +#define ULPI_DATA(n) ((n) & 255) +#define ULPI_DATA_READ(n) (((n) >> 8) & 255) + +/* USB_PORTSC bits for determining port speed */ +#define PORTSC_PSPD_FS (0 << 26) +#define PORTSC_PSPD_LS (1 << 26) +#define PORTSC_PSPD_HS (2 << 26) +#define PORTSC_PSPD_MASK (3 << 26) +/* suspend and remote wakeup */ +#define PORTSC_FPR (1 << 6) +#define PORTSC_SUSP (1 << 7) + +/* test mode support */ +#define J_TEST (0x0100) +#define K_TEST (0x0200) +#define SE0_NAK_TEST (0x0300) +#define TST_PKT_TEST (0x0400) +#define PORTSC_PTC (0xf << 16) +#define PORTSC_PTC_J_STATE (0x01 << 16) +#define PORTSC_PTC_K_STATE (0x02 << 16) +#define PORTSC_PTC_SE0_NAK (0x03 << 16) +#define PORTSC_PTC_TST_PKT (0x04 << 16) +#endif /* __LINUX_USB_GADGET_MSM72K_UDC_H__ */ diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 591ae9f..18d28c2 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -509,6 +509,23 @@ config USB_LANGWELL # # LAST -- dummy/emulated controller # +config USB_GADGET_MSM_72K + boolean "MSM 72K Device Controller" + depends on ARCH_MSM + select USB_GADGET_SELECTED + select USB_GADGET_DUALSPEED + help + USB gadget driver for Qualcomm MSM 72K architecture. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "msm72k" and force all + gadget drivers to also be dynamically linked. + +config USB_MSM_72K + tristate + depends on USB_GADGET_MSM_72K + default USB_GADGET + select USB_GADGET_SELECTED config USB_GADGET_DUMMY_HCD boolean "Dummy HCD (DEVELOPMENT)" diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 9bcde11..48ea27a 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o obj-$(CONFIG_USB_LANGWELL) += langwell_udc.o +obj-$(CONFIG_USB_MSM_72K) += msm72k_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/msm72k_udc.c b/drivers/usb/gadget/msm72k_udc.c new file mode 100644 index 0000000..1630cf8 --- /dev/null +++ b/drivers/usb/gadget/msm72k_udc.c @@ -0,0 +1,1738 @@ +/* + * Driver for HighSpeed USB Client Controller in MSM7K + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@xxxxxxxxxxx> + * Brian Swetland <swetland@xxxxxxxxxx> + * + * 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/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/io.h> + +#include <asm/mach-types.h> + +#include <mach/board.h> +#include <mach/msm_hsusb.h> +#include <linux/device.h> +#include <mach/msm_hsusb_hw.h> + +static const char driver_name[] = "msm72k_udc"; + +/* #define DEBUG */ +/* #define VERBOSE */ + +#define MSM_USB_BASE ((unsigned) ui->addr) + +#define DRIVER_DESC "MSM 72K USB Peripheral Controller" + +#define EPT_FLAG_IN 0x0001 + +#define SETUP_BUF_SIZE 4096 + + +static const char *const ep_name[] = { + "ep0out", "ep1out", "ep2out", "ep3out", + "ep4out", "ep5out", "ep6out", "ep7out", + "ep8out", "ep9out", "ep10out", "ep11out", + "ep12out", "ep13out", "ep14out", "ep15out", + "ep0in", "ep1in", "ep2in", "ep3in", + "ep4in", "ep5in", "ep6in", "ep7in", + "ep8in", "ep9in", "ep10in", "ep11in", + "ep12in", "ep13in", "ep14in", "ep15in" +}; + +/* current state of VBUS */ +static int vbus; + +struct msm_request { + struct usb_request req; + + /* saved copy of req.complete */ + void (*gadget_complete)(struct usb_ep *ep, + 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; + dma_addr_t item_dma; + + struct ept_queue_item *item; +}; + +#define to_msm_request(r) container_of(r, struct msm_request, req) +#define to_msm_endpoint(r) container_of(r, struct msm_endpoint, ep) + +struct msm_endpoint { + struct usb_ep ep; + struct usb_info *ui; + struct msm_request *req; /* head of pending requests */ + struct msm_request *last; + unsigned flags; + + /* bit number (0-31) in various status registers + ** as well as the index into the usb_info's array + ** of all endpoints + */ + unsigned char bit; + unsigned char num; + + /* pointers to DMA transfer list area */ + /* these are allocated from the usb_info dma space */ + struct ept_queue_head *head; +}; + +static void usb_do_work(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 + +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 platform_device *pdev; + int irq; + void *addr; + + unsigned state; + unsigned flags; + + unsigned online:1; + unsigned running:1; + + 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; + + /* endpoints are ordered based on their status bits, + ** so they are OUT0, OUT1, ... OUT15, IN0, IN1, ... IN15 + */ + struct msm_endpoint ept[32]; + + int *phy_init_seq; + void (*phy_reset)(void); + + /* for notification when USB is connected or disconnected */ + void (*usb_connected)(int); + + struct work_struct work; + unsigned phy_status; + unsigned phy_fail_count; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + +#define ep0out ept[0] +#define ep0in ept[16] + + struct clk *clk; + struct clk *pclk; + + unsigned int ep0_dir; + u16 test_mode; + + u8 remote_wakeup; +}; + +static const struct usb_ep_ops msm72k_ep_ops; + + +static int msm72k_pullup(struct usb_gadget *_gadget, int is_active); +static int msm72k_set_halt(struct usb_ep *_ep, int value); +static void flush_endpoint(struct msm_endpoint *ept); + +static int usb_ep_get_stall(struct msm_endpoint *ept) +{ + unsigned int n; + struct usb_info *ui = ept->ui; + + n = readl(USB_ENDPTCTRL(ept->num)); + if (ept->flags & EPT_FLAG_IN) + return (CTRL_TXS & n) ? 1 : 0; + else + return (CTRL_RXS & n) ? 1 : 0; +} + +#if 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) { + ERROR("ulpi_read: timeout %08x\n", readl(USB_ULPI_VIEWPORT)); + return 0xffffffff; + } + return ULPI_DATA_READ(readl(USB_ULPI_VIEWPORT)); +} +#endif + +static void 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) + ERROR("ulpi_write: timeout\n"); +} + +static void ulpi_init(struct usb_info *ui) +{ + int *seq = ui->phy_init_seq; + + if (!seq) + return; + + while (seq[0] >= 0) { + INFO("ulpi: write 0x%02x to 0x%02x\n", seq[0], seq[1]); + ulpi_write(ui, seq[0], seq[1]); + seq += 2; + } +} + +static void init_endpoints(struct usb_info *ui) +{ + unsigned n; + + for (n = 0; n < 32; n++) { + struct msm_endpoint *ept = ui->ept + n; + + ept->ui = ui; + ept->bit = n; + ept->num = n & 15; + ept->ep.name = ep_name[n]; + ept->ep.ops = &msm72k_ep_ops; + + 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; + } + + } +} + +static void config_ept(struct msm_endpoint *ept) +{ + unsigned cfg = CONFIG_MAX_PKT(ept->ep.maxpacket) | CONFIG_ZLT; + + if (ept->bit == 0) + /* ep0 out needs interrupt-on-setup */ + cfg |= CONFIG_IOS; + + ept->head->config = cfg; + ept->head->next = TERMINATE; + + if (ept->ep.maxpacket) + INFO("ept #%d %s max:%d head:%p bit:%d\n", + ept->num, (ept->flags & EPT_FLAG_IN) ? "in" : "out", + ept->ep.maxpacket, ept->head, ept->bit); +} + +static void configure_endpoints(struct usb_info *ui) +{ + unsigned n; + + for (n = 0; n < 32; n++) + config_ept(ui->ept + n); +} + +struct usb_request *usb_ept_alloc_req(struct msm_endpoint *ept, + unsigned bufsize, gfp_t gfp_flags) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + goto fail1; + + req->item = dma_pool_alloc(ui->pool, gfp_flags, &req->item_dma); + if (!req->item) + goto fail2; + + if (bufsize) { + req->req.buf = kmalloc(bufsize, gfp_flags); + 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 0; +} + +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); +} + + +static void usb_ept_enable(struct msm_endpoint *ept, int yes, + unsigned char ep_type) +{ + struct usb_info *ui = ept->ui; + int in = ept->flags & EPT_FLAG_IN; + unsigned n; + + n = readl(USB_ENDPTCTRL(ept->num)); + + if (in) { + if (yes) { + n = (n & (~CTRL_TXT_MASK)) | + (ep_type << CTRL_TXT_EP_TYPE_SHIFT); + n |= CTRL_TXE | CTRL_TXR; + } else + n &= (~CTRL_TXE); + } else { + if (yes) { + n = (n & (~CTRL_RXT_MASK)) | + (ep_type << CTRL_RXT_EP_TYPE_SHIFT); + n |= CTRL_RXE | CTRL_RXR; + } else + n &= ~(CTRL_RXE); + } + writel(n, USB_ENDPTCTRL(ept->num)); + +#if 1 + INFO("ept %d %s %s\n", + ept->num, in ? "in" : "out", yes ? "enabled" : "disabled"); +#endif +} + +static void usb_ept_start(struct msm_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; + + /* start the endpoint */ + writel(1 << ept->bit, USB_ENDPTPRIME); + + /* mark this chain of requests as live */ + while (req) { + req->live = 1; + req = req->next; + } +} + +int usb_ept_queue_xfer(struct msm_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; + + spin_lock_irqsave(&ui->lock, flags); + + if (req->busy) { + req->req.status = -EBUSY; + spin_unlock_irqrestore(&ui->lock, flags); + INFO("usb_ept_queue_xfer() tried to queue busy request\n"); + return -EBUSY; + } + + if (!ui->online && (ept->num != 0)) { + req->req.status = -ESHUTDOWN; + spin_unlock_irqrestore(&ui->lock, flags); + INFO("usb_ept_queue_xfer() called while offline\n"); + return -ESHUTDOWN; + } + + 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; +} + +/* --- endpoint 0 handling --- */ + +static void ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct msm_request *r = to_msm_request(req); + struct msm_endpoint *ept = to_msm_endpoint(ep); + struct usb_info *ui = ept->ui; + + req->complete = r->gadget_complete; + r->gadget_complete = 0; + if (req->complete) + req->complete(&ui->ep0in.ep, req); +} + +static void ep0_queue_ack_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct msm_endpoint *ept = to_msm_endpoint(ep); + + /* 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 = ep0_complete; + if (ui->ep0_dir == USB_DIR_IN) + usb_ept_queue_xfer(&ui->ep0out, req); + else + usb_ept_queue_xfer(&ui->ep0in, req); + } else + ep0_complete(ep, req); +} + +static void ep0_setup_ack_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct msm_endpoint *ept = to_msm_endpoint(ep); + struct usb_info *ui = ept->ui; + unsigned int temp; + + if (!ui->test_mode) + return; + + switch (ui->test_mode) { + case J_TEST: + pr_info("usb electrical test mode: (J)\n"); + temp = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(temp | PORTSC_PTC_J_STATE, USB_PORTSC); + break; + + case K_TEST: + pr_info("usb electrical test mode: (K)\n"); + temp = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(temp | PORTSC_PTC_K_STATE, USB_PORTSC); + break; + + case SE0_NAK_TEST: + pr_info("usb electrical test mode: (SE0-NAK)\n"); + temp = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(temp | PORTSC_PTC_SE0_NAK, USB_PORTSC); + break; + + case TST_PKT_TEST: + pr_info("usb electrical test mode: (TEST_PKT)\n"); + temp = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(temp | PORTSC_PTC_TST_PKT, USB_PORTSC); + break; + } +} + +static void ep0_setup_ack(struct usb_info *ui) +{ + struct usb_request *req = ui->setup_req; + req->length = 0; + req->complete = ep0_setup_ack_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_send(struct usb_info *ui, unsigned length) +{ + struct usb_request *req = ui->setup_req; + struct msm_request *r = to_msm_request(req); + struct msm_endpoint *ept = &ui->ep0in; + + req->length = length; + req->complete = ep0_queue_ack_complete; + r->gadget_complete = 0; + usb_ept_queue_xfer(ept, req); +} + +static void handle_setup(struct usb_info *ui) +{ + struct usb_ctrlrequest ctl; + struct usb_request *req = ui->setup_req; + int ret; + + memcpy(&ctl, ui->ep0out.head->setup_data, sizeof(ctl)); + writel(EPT_RX(0), USB_ENDPTSETUPSTAT); + + if (ctl.bRequestType & USB_DIR_IN) + ui->ep0_dir = USB_DIR_IN; + else + ui->ep0_dir = USB_DIR_OUT; + + /* any pending ep0 transactions must be canceled */ + flush_endpoint(&ui->ep0out); + flush_endpoint(&ui->ep0in); + + INFO("setup: type=%02x req=%02x val=%04x idx=%04x len=%04x\n", + ctl.bRequestType, ctl.bRequest, ctl.wValue, + ctl.wIndex, ctl.wLength); + + if ((ctl.bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) == + (USB_DIR_IN | USB_TYPE_STANDARD)) { + if (ctl.bRequest == USB_REQ_GET_STATUS) { + if (ctl.wLength != 2) + goto stall; + switch (ctl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_ENDPOINT: + { + struct msm_endpoint *ept; + unsigned num = + ctl.wIndex & USB_ENDPOINT_NUMBER_MASK; + u16 temp = 0; + + if (num == 0) { + memset(req->buf, 0, 2); + break; + } + if (ctl.wIndex & USB_ENDPOINT_DIR_MASK) + num += 16; + ept = &ui->ep0out + num; + temp = usb_ep_get_stall(ept); + temp = temp << USB_ENDPOINT_HALT; + memcpy(req->buf, &temp, 2); + break; + } + case USB_RECIP_DEVICE: + { + u16 temp = 0; + + temp = 1 << USB_DEVICE_SELF_POWERED; + temp |= (ui->remote_wakeup << + USB_DEVICE_REMOTE_WAKEUP); + memcpy(req->buf, &temp, 2); + break; + } + case USB_RECIP_INTERFACE: + memset(req->buf, 0, 2); + break; + default: + goto stall; + } + ep0_setup_send(ui, 2); + return; + } + } + if (ctl.bRequestType == + (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT)) { + if ((ctl.bRequest == USB_REQ_CLEAR_FEATURE) || + (ctl.bRequest == USB_REQ_SET_FEATURE)) { + if ((ctl.wValue == 0) && (ctl.wLength == 0)) { + unsigned num = ctl.wIndex & 0x0f; + + if (num != 0) { + struct msm_endpoint *ept; + + if (ctl.wIndex & 0x80) + num += 16; + ept = &ui->ep0out + num; + + if (ctl.bRequest == USB_REQ_SET_FEATURE) + msm72k_set_halt(&ept->ep, 1); + else + msm72k_set_halt(&ept->ep, 0); + } + goto ack; + } + } + } + if (ctl.bRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD)) { + if (ctl.bRequest == USB_REQ_SET_CONFIGURATION) + ui->online = !!ctl.wValue; + else if (ctl.bRequest == USB_REQ_SET_ADDRESS) { + /* write address delayed (will take effect + ** after the next IN txn) + */ + writel((ctl.wValue << 25) | (1 << 24), USB_DEVICEADDR); + goto ack; + } else if (ctl.bRequest == USB_REQ_SET_FEATURE) { + switch (ctl.wValue) { + case USB_DEVICE_TEST_MODE: + switch (ctl.wIndex) { + case J_TEST: + case K_TEST: + case SE0_NAK_TEST: + case TST_PKT_TEST: + ui->test_mode = ctl.wIndex; + goto ack; + } + goto stall; + case USB_DEVICE_REMOTE_WAKEUP: + ui->remote_wakeup = 1; + goto ack; + } + } else if ((ctl.bRequest == USB_REQ_CLEAR_FEATURE) && + (ctl.wValue == USB_DEVICE_REMOTE_WAKEUP)) { + ui->remote_wakeup = 0; + goto ack; + } + } + + /* delegate if we get here */ + if (ui->driver) { + ret = ui->driver->setup(&ui->gadget, &ctl); + if (ret >= 0) + return; + } + +stall: + /* stall ep0 on error */ + ep0_setup_stall(ui); + return; + +ack: + ep0_setup_ack(ui); +} + +static void handle_endpoint(struct usb_info *ui, unsigned bit) +{ + struct msm_endpoint *ept = ui->ept + bit; + struct msm_request *req; + unsigned long flags; + unsigned info; + + /* + 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); + */ + + /* expire all requests that are no longer active */ + spin_lock_irqsave(&ui->lock, flags); + while ((req = ept->req)) { + 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; + INFO("msm72k_udc: 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->ep, &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?!) + */ + do { + writel(bits, USB_ENDPTFLUSH); + while (readl(USB_ENDPTFLUSH) & bits) + udelay(100); + } while (readl(USB_ENDPTSTAT) & bits); +} + +static void flush_endpoint_sw(struct msm_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req; + unsigned long flags; + + /* inactive endpoints have nothing to do here */ + if (ept->ep.maxpacket == 0) + 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) { + req->busy = 0; + req->live = 0; + req->req.status = -ECONNRESET; + req->req.actual = 0; + if (req->req.complete) { + spin_unlock_irqrestore(&ui->lock, flags); + req->req.complete(&ept->ep, &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 msm_endpoint *ept) +{ + 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); +} + + +static irqreturn_t usb_interrupt(int irq, void *data) +{ + struct usb_info *ui = data; + unsigned n; + + n = readl(USB_USBSTS); + writel(n, USB_USBSTS); + + /* somehow we got an IRQ while in the reset sequence: ignore it */ + if (ui->running == 0) + return IRQ_HANDLED; + + if (n & STS_PCI) { + switch (readl(USB_PORTSC) & PORTSC_PSPD_MASK) { + case PORTSC_PSPD_FS: + INFO("msm72k_udc: portchange USB_SPEED_FULL\n"); + ui->gadget.speed = USB_SPEED_FULL; + break; + case PORTSC_PSPD_LS: + INFO("msm72k_udc: portchange USB_SPEED_LOW\n"); + ui->gadget.speed = USB_SPEED_LOW; + break; + case PORTSC_PSPD_HS: + INFO("msm72k_udc: portchange USB_SPEED_HIGH\n"); + ui->gadget.speed = USB_SPEED_HIGH; + break; + } + } + + if (n & STS_URI) { + INFO("msm72k_udc: reset\n"); + + 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, + * XXX: so deconfigure on reset for the time being + */ + if (ui->driver) { + printk(KERN_INFO "usb: notify offline\n"); + ui->driver->disconnect(&ui->gadget); + } + } + } + + if (n & STS_SLI) + INFO("msm72k_udc: suspend\n"); + + 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)); + } + } + return IRQ_HANDLED; +} + +static void usb_prepare(struct usb_info *ui) +{ + spin_lock_init(&ui->lock); + + 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->next_ifc_num = 0; + + init_endpoints(ui); + + ui->ep0in.ep.maxpacket = 64; + ui->ep0out.ep.maxpacket = 64; + + ui->setup_req = + usb_ept_alloc_req(&ui->ep0in, SETUP_BUF_SIZE, GFP_KERNEL); + + INIT_WORK(&ui->work, usb_do_work); +} + +static void usb_suspend_phy(struct usb_info *ui) +{ + /* clear VBusValid and SessionEnd rising interrupts */ + ulpi_write(ui, (1 << 1) | (1 << 3), 0x0f); + /* clear VBusValid and SessionEnd falling interrupts */ + ulpi_write(ui, (1 << 1) | (1 << 3), 0x12); + /* disable interface protect circuit to drop current consumption */ + ulpi_write(ui, (1 << 7), 0x08); +} + +static void usb_reset(struct usb_info *ui) +{ + unsigned long flags; + unsigned otgsc; + + INFO("msm72k_udc: reset controller\n"); + + 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 + + otgsc = readl(USB_OTGSC); + + /* RESET */ + writel(2, USB_USBCMD); + msleep(10); + + if (ui->phy_reset) + ui->phy_reset(); + + /* INCR4 BURST mode */ + writel(0x01, USB_SBUSCFG); + + /* select DEVICE mode */ + writel(0x12, USB_USBMODE); + msleep(1); + + /* select ULPI phy */ + writel(0x80000000, USB_PORTSC); + + ulpi_init(ui); + + writel(ui->dma, USB_ENDPOINTLISTADDR); + + configure_endpoints(ui); + + /* marking us offline will cause ept queue attempts to fail */ + ui->online = 0; + + /* terminate any pending transactions */ + flush_all_endpoints(ui); + + if (ui->driver) { + printk(KERN_INFO "usb: notify offline\n"); + ui->driver->disconnect(&ui->gadget); + } + + /* enable interrupts */ + writel(otgsc, USB_OTGSC); + writel(STS_URI | STS_SLI | STS_UI | STS_PCI, USB_USBINTR); + + /* go to RUN mode (D+ pullup enable) */ + msm72k_pullup(&ui->gadget, 1); + + spin_lock_irqsave(&ui->lock, flags); + ui->running = 1; + spin_unlock_irqrestore(&ui->lock, flags); +} + +static void usb_start(struct usb_info *ui) +{ + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + ui->flags |= USB_FLAG_START; + schedule_work(&ui->work); + spin_unlock_irqrestore(&ui->lock, flags); +} + +static struct usb_info *the_usb_info; + +static int usb_free(struct usb_info *ui, int ret) +{ + INFO("usb_free(%d)\n", ret); + + if (ui->irq) + free_irq(ui->irq, 0); + if (ui->pool) + dma_pool_destroy(ui->pool); + if (ui->dma) + dma_free_coherent(&ui->pdev->dev, 4096, ui->buf, ui->dma); + if (ui->addr) + iounmap(ui->addr); + if (ui->clk) + clk_put(ui->clk); + if (ui->pclk) + clk_put(ui->pclk); + kfree(ui); + return ret; +} + +static void usb_do_work_check_vbus(struct usb_info *ui) +{ + unsigned long iflags; + + spin_lock_irqsave(&ui->lock, iflags); + if (vbus) + ui->flags |= USB_FLAG_VBUS_ONLINE; + else + ui->flags |= USB_FLAG_VBUS_OFFLINE; + spin_unlock_irqrestore(&ui->lock, iflags); +} + +static void usb_do_work(struct work_struct *w) +{ + struct usb_info *ui = container_of(w, struct usb_info, work); + unsigned long iflags; + unsigned flags, _vbus; + + for (;;) { + spin_lock_irqsave(&ui->lock, iflags); + flags = ui->flags; + ui->flags = 0; + _vbus = vbus; + 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_START) { + pr_info("msm72k_udc: IDLE -> ONLINE\n"); + clk_enable(ui->clk); + clk_enable(ui->pclk); + usb_reset(ui); + + ui->state = USB_STATE_ONLINE; + usb_do_work_check_vbus(ui); + } + break; + 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) { + pr_info("msm72k_udc: ONLINE -> OFFLINE\n"); + + /* synchronize with irq context */ + spin_lock_irqsave(&ui->lock, iflags); + ui->running = 0; + ui->online = 0; + msm72k_pullup(&ui->gadget, 0); + spin_unlock_irqrestore(&ui->lock, iflags); + + if (ui->usb_connected) + ui->usb_connected(0); + + /* terminate any transactions, etc */ + flush_all_endpoints(ui); + + if (ui->driver) { + printk(KERN_INFO "usb: notify offline\n"); + ui->driver->disconnect(&ui->gadget); + } + + /* power down phy, clock down usb */ + spin_lock_irqsave(&ui->lock, iflags); + usb_suspend_phy(ui); + clk_disable(ui->pclk); + clk_disable(ui->clk); + spin_unlock_irqrestore(&ui->lock, iflags); + + ui->state = USB_STATE_OFFLINE; + usb_do_work_check_vbus(ui); + break; + } + if (flags & USB_FLAG_RESET) { + pr_info("msm72k_udc: ONLINE -> RESET\n"); + usb_reset(ui); + pr_info("msm72k_udc: RESET -> ONLINE\n"); + break; + } + break; + 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) && _vbus) { + pr_info("msm72k_udc: OFFLINE -> ONLINE\n"); + clk_enable(ui->clk); + clk_enable(ui->pclk); + usb_reset(ui); + + if (ui->usb_connected) + ui->usb_connected(1); + + ui->state = USB_STATE_ONLINE; + usb_do_work_check_vbus(ui); + } + break; + } + } +} + +/* FIXME - the callers of this function should use a gadget API instead. + * This is called from htc_battery.c and board-halibut.c + * WARNING - this can get called before this driver is initialized. + */ +#if !defined(MODULE) +void msm_hsusb_set_vbus_state(int online) +#else +static void msm_hsusb_set_vbus_state_new(int online) +#endif +{ + unsigned long flags; + struct usb_info *ui = the_usb_info; + + if (ui) { + spin_lock_irqsave(&ui->lock, flags); + if (vbus != online) { + vbus = online; + if (online) + ui->flags |= USB_FLAG_VBUS_ONLINE; + else + ui->flags |= USB_FLAG_VBUS_OFFLINE; + schedule_work(&ui->work); + } + spin_unlock_irqrestore(&ui->lock, flags); + } else { + printk(KERN_ERR "msm_hsusb_set_vbus_state called before driver initialized\n"); + vbus = online; + } +} + + +#if defined(CONFIG_DEBUG_FS) + +void usb_function_reenumerate(void) +{ + struct usb_info *ui = the_usb_info; + + /* disable and re-enable the D+ pullup */ + INFO("msm72k_udc: disable pullup\n"); + writel(0x00080000, USB_USBCMD); + + msleep(10); + + INFO("msm72k_udc: enable pullup\n"); + writel(0x00080001, USB_USBCMD); +} + +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 msm_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->ep.maxpacket == 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; + schedule_work(&ui->work); + 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 void usb_debugfs_init(struct usb_info *ui) +{ + struct dentry *dent; + dent = debugfs_create_dir("usb", 0); + if (IS_ERR(dent)) + return; + + debugfs_create_file("status", 0444, dent, ui, &debug_stat_ops); + debugfs_create_file("reset", 0222, dent, ui, &debug_reset_ops); + debugfs_create_file("cycle", 0222, dent, ui, &debug_cycle_ops); +} +#else +static void usb_debugfs_init(struct usb_info *ui) {} +#endif + +static int +msm72k_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct msm_endpoint *ept = to_msm_endpoint(_ep); + unsigned char ep_type = + desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + _ep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); + config_ept(ept); + usb_ept_enable(ept, 1, ep_type); + return 0; +} + +static int msm72k_disable(struct usb_ep *_ep) +{ + struct msm_endpoint *ept = to_msm_endpoint(_ep); + + usb_ept_enable(ept, 0, 0); + return 0; +} + +static struct usb_request * +msm72k_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + return usb_ept_alloc_req(to_msm_endpoint(_ep), 0, gfp_flags); +} + +static void +msm72k_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct msm_request *req = to_msm_request(_req); + struct msm_endpoint *ept = to_msm_endpoint(_ep); + struct usb_info *ui = ept->ui; + unsigned long flags; + int dead = 0; + + 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) + do_free_req(ui, req); +} + +static int +msm72k_queue(struct usb_ep *_ep, struct usb_request *req, gfp_t gfp_flags) +{ + struct msm_endpoint *ep = to_msm_endpoint(_ep); + struct usb_info *ui = ep->ui; + + if (ep == &ui->ep0in) { + struct msm_request *r = to_msm_request(req); + if (!req->length) + goto ep_queue_done; + r->gadget_complete = req->complete; + /* ep0_queue_ack_complete queue a receive for ACK before + ** calling req->complete + */ + req->complete = ep0_queue_ack_complete; + if (ui->ep0_dir == USB_DIR_OUT) + ep = &ui->ep0out; + } +ep_queue_done: + return usb_ept_queue_xfer(ep, req); +} + +static int msm72k_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct msm_endpoint *ep = to_msm_endpoint(_ep); + struct msm_request *req = to_msm_request(_req); + struct usb_info *ui = ep->ui; + + struct msm_request *cur, *prev; + unsigned long flags; + + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ui->lock, flags); + cur = ep->req; + prev = NULL; + + while (cur != 0) { + if (cur == req) { + req->busy = 0; + req->live = 0; + req->req.status = -ECONNRESET; + req->req.actual = 0; + if (req->req.complete) { + spin_unlock_irqrestore(&ui->lock, flags); + req->req.complete(&ep->ep, &req->req); + spin_lock_irqsave(&ui->lock, flags); + } + if (req->dead) + do_free_req(ui, req); + /* remove from linked list */ + if (prev) + prev->next = cur->next; + else + ep->req = cur->next; + prev = cur; + /* break from loop */ + cur = NULL; + } else + cur = cur->next; + } + spin_unlock_irqrestore(&ui->lock, flags); + + return 0; +} + +static int +msm72k_set_halt(struct usb_ep *_ep, int value) +{ + struct msm_endpoint *ept = to_msm_endpoint(_ep); + struct usb_info *ui = ept->ui; + unsigned int in = ept->flags & EPT_FLAG_IN; + unsigned int n; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + n = readl(USB_ENDPTCTRL(ept->num)); + + if (in) { + if (value) + n |= CTRL_TXS; + else { + n &= ~CTRL_TXS; + n |= CTRL_TXR; + } + } else { + if (value) + n |= CTRL_RXS; + else { + n &= ~CTRL_RXS; + n |= CTRL_RXR; + } + } + writel(n, USB_ENDPTCTRL(ept->num)); + spin_unlock_irqrestore(&ui->lock, flags); + + return 0; +} + +static int +msm72k_fifo_status(struct usb_ep *_ep) +{ + return -EOPNOTSUPP; +} + +static void +msm72k_fifo_flush(struct usb_ep *_ep) +{ + flush_endpoint(to_msm_endpoint(_ep)); +} + +static const struct usb_ep_ops msm72k_ep_ops = { + .enable = msm72k_enable, + .disable = msm72k_disable, + + .alloc_request = msm72k_alloc_request, + .free_request = msm72k_free_request, + + .queue = msm72k_queue, + .dequeue = msm72k_dequeue, + + .set_halt = msm72k_set_halt, + .fifo_status = msm72k_fifo_status, + .fifo_flush = msm72k_fifo_flush, +}; + +static int msm72k_get_frame(struct usb_gadget *_gadget) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + + /* frame number is in bits 13:3 */ + return (readl(USB_FRINDEX) >> 3) & 0x000007FF; +} + +/* VBUS reporting logically comes from a transceiver */ +static int msm72k_udc_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + msm_hsusb_set_vbus_state(is_active); + return 0; +} + +/* drivers may have software control over D+ pullup */ +static int msm72k_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + + if (is_active) { + if (vbus && ui->driver) + writel(0x00080001, USB_USBCMD); + } else + writel(0x00080000, USB_USBCMD); + + return 0; +} + +static int msm72k_wakeup(struct usb_gadget *_gadget) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + unsigned long flags; + + if (!ui->remote_wakeup) { + pr_err("%s: remote wakeup not supported\n", __func__); + return -ENOTSUPP; + } + + if (!ui->online) { + pr_err("%s: device is not configured\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&ui->lock, flags); + if ((readl(USB_PORTSC) & PORTSC_SUSP) == PORTSC_SUSP) { + pr_info("%s: enabling force resume\n", __func__); + writel(readl(USB_PORTSC) | PORTSC_FPR, USB_PORTSC); + } + spin_unlock_irqrestore(&ui->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops msm72k_ops = { + .get_frame = msm72k_get_frame, + .vbus_session = msm72k_udc_vbus_session, + .pullup = msm72k_pullup, + .wakeup = msm72k_wakeup, +}; + +static ssize_t usb_remote_wakeup(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_info *ui = the_usb_info; + + msm72k_wakeup(&ui->gadget); + + return count; +} +static DEVICE_ATTR(wakeup, S_IWUSR, 0, usb_remote_wakeup); + +static int msm72k_probe(struct platform_device *pdev) +{ + struct resource *res; + struct usb_info *ui; + int irq; + int ret; + + INFO("msm72k_probe\n"); + ui = kzalloc(sizeof(struct usb_info), GFP_KERNEL); + if (!ui) + return -ENOMEM; + + ui->pdev = pdev; + + if (pdev->dev.platform_data) { + struct msm_hsusb_platform_data *pdata = pdev->dev.platform_data; + ui->phy_reset = pdata->phy_reset; + ui->phy_init_seq = pdata->phy_init_seq; + ui->usb_connected = pdata->usb_connected; + } + + irq = platform_get_irq(pdev, 0); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res || (irq < 0)) + return usb_free(ui, -ENODEV); + + ui->addr = ioremap(res->start, 4096); + if (!ui->addr) + return usb_free(ui, -ENOMEM); + + ui->buf = dma_alloc_coherent(&pdev->dev, 4096, &ui->dma, GFP_KERNEL); + if (!ui->buf) + return usb_free(ui, -ENOMEM); + + ui->pool = dma_pool_create("msm72k_udc", NULL, 32, 32, 0); + if (!ui->pool) + return usb_free(ui, -ENOMEM); + + INFO("msm72k_probe() io=%p, irq=%d, dma=%p(%x)\n", + ui->addr, irq, ui->buf, ui->dma); + + ui->clk = clk_get(&pdev->dev, "usb_hs_clk"); + if (IS_ERR(ui->clk)) + return usb_free(ui, PTR_ERR(ui->clk)); + + ui->pclk = clk_get(&pdev->dev, "usb_hs_pclk"); + if (IS_ERR(ui->pclk)) + return usb_free(ui, PTR_ERR(ui->pclk)); + + ret = request_irq(irq, usb_interrupt, 0, pdev->name, ui); + if (ret) + return usb_free(ui, ret); + enable_irq_wake(irq); + ui->irq = irq; + + ui->gadget.ops = &msm72k_ops; + ui->gadget.is_dualspeed = 1; + device_initialize(&ui->gadget.dev); + ui->gadget.dev.init_name="gadget"; + ui->gadget.dev.parent = &pdev->dev; + ui->gadget.dev.dma_mask = pdev->dev.dma_mask; + + the_usb_info = ui; + + usb_debugfs_init(ui); + + usb_prepare(ui); + + return 0; +} + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct usb_info *ui = the_usb_info; + int retval, n; + + if (!driver + || driver->speed < USB_SPEED_FULL + || !driver->bind + || !driver->disconnect + || !driver->setup) + return -EINVAL; + if (!ui) + return -ENODEV; + if (ui->driver) + return -EBUSY; + + /* first hook up the driver ... */ + ui->driver = driver; + ui->gadget.dev.driver = &driver->driver; + ui->gadget.name = driver_name; + INIT_LIST_HEAD(&ui->gadget.ep_list); + ui->gadget.ep0 = &ui->ep0in.ep; + INIT_LIST_HEAD(&ui->gadget.ep0->ep_list); + ui->gadget.speed = USB_SPEED_UNKNOWN; + + for (n = 1; n < 16; n++) { + struct msm_endpoint *ept = ui->ept + n; + list_add_tail(&ept->ep.ep_list, &ui->gadget.ep_list); + ept->ep.maxpacket = 512; + } + for (n = 17; n < 32; n++) { + struct msm_endpoint *ept = ui->ept + n; + list_add_tail(&ept->ep.ep_list, &ui->gadget.ep_list); + ept->ep.maxpacket = 512; + } + + retval = device_add(&ui->gadget.dev); + if (retval) + goto fail; + + retval = driver->bind(&ui->gadget); + if (retval) { + INFO("bind to driver %s --> error %d\n", + driver->driver.name, retval); + device_del(&ui->gadget.dev); + goto fail; + } + + /* create sysfs node for remote wakeup */ + retval = device_create_file(&ui->gadget.dev, &dev_attr_wakeup); + if (retval != 0) + INFO("failed to create sysfs entry: (wakeup) error: (%d)\n", + retval); + INFO("msm72k_udc: registered gadget driver '%s'\n", + driver->driver.name); + usb_start(ui); + + return 0; + +fail: + ui->driver = NULL; + ui->gadget.dev.driver = NULL; + return retval; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usb_info *dev = the_usb_info; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver || !driver->unbind) + return -EINVAL; + + device_remove_file(&dev->gadget.dev, &dev_attr_wakeup); + driver->unbind(&dev->gadget); + dev->gadget.dev.driver = NULL; + dev->driver = NULL; + + device_del(&dev->gadget.dev); + + VDEBUG("unregistered gadget driver '%s'\n", driver->driver.name); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + + +static struct platform_driver usb_driver = { + .probe = msm72k_probe, + .driver = { .name = "msm_hsusb", }, +}; + +#if defined(MODULE) +extern void (*msm_hsusb_set_vbus_state)(int online); +#endif + +static int __init init(void) +{ + #if defined(MODULE) + msm_hsusb_set_vbus_state = &msm_hsusb_set_vbus_state_new; + #endif + return platform_driver_register(&usb_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + platform_driver_unregister(&usb_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Mike Lockwood, Brian Swetland"); +MODULE_LICENSE("GPL"); -- 1.6.4.4 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html