| /* |
| * Driver for HighSpeed USB Client Controller in MSM7K |
| * |
| * Copyright (C) 2008 Google, Inc. |
| * Author: Mike Lockwood <lockwood@android.com> |
| * 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/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 |
| |
| typedef void (*completion_func)(struct usb_ep *ep, struct usb_request *req); |
| |
| 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 */ |
| completion_func gadget_complete; |
| |
| struct usb_info *ui; |
| struct msm_request *next; |
| |
| unsigned busy:1; |
| unsigned live:1; |
| unsigned alloced: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); |
| void (*hw_reset)(bool en); |
| |
| /* 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 *coreclk; |
| struct clk *pclk; |
| struct clk *otgclk; |
| struct clk *ebi1clk; |
| |
| 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; |
| } |
| |
| 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)); |
| } |
| |
| 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 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 0 |
| 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); |
| #endif |
| } |
| |
| 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 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) { |
| n = (n & (~CTRL_TXT_MASK)); |
| if (yes) { |
| n |= CTRL_TXE | CTRL_TXR; |
| } else { |
| n &= (~CTRL_TXE); |
| } |
| if (yes) { |
| switch (ep_type) { |
| case USB_ENDPOINT_XFER_BULK: |
| n |= CTRL_TXT_BULK; |
| break; |
| case USB_ENDPOINT_XFER_INT: |
| n |= CTRL_TXT_INT; |
| break; |
| case USB_ENDPOINT_XFER_ISOC: |
| n |= CTRL_TXT_ISOCH; |
| break; |
| default: |
| pr_err("%s: unsupported ep_type %d for %s\n", |
| __func__, ep_type, ept->ep.name); |
| break; |
| } |
| } |
| } else { |
| n = (n & (~CTRL_RXT_MASK)); |
| if (yes) { |
| n |= CTRL_RXE | CTRL_RXR; |
| } else { |
| n &= ~(CTRL_RXE); |
| } |
| if (yes) { |
| switch (ep_type) { |
| case USB_ENDPOINT_XFER_BULK: |
| n |= CTRL_RXT_BULK; |
| break; |
| case USB_ENDPOINT_XFER_INT: |
| n |= CTRL_RXT_INT; |
| break; |
| case USB_ENDPOINT_XFER_ISOC: |
| n |= CTRL_RXT_ISOCH; |
| break; |
| default: |
| pr_err("%s: unsupported ep_type %d for %s\n", |
| __func__, ep_type, ept->ep.name); |
| break; |
| } |
| } |
| } |
| writel(n, USB_ENDPTCTRL(ept->num)); |
| |
| #if 0 |
| 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 = NULL; |
| 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); |
| struct msm_request *r = to_msm_request(req); |
| completion_func gadget_complete = r->gadget_complete; |
| |
| if (gadget_complete) { |
| r->gadget_complete = NULL; |
| gadget_complete(ep, 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 = 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 = NULL; |
| 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); |
| |
| #if 0 |
| INFO("setup: type=%02x req=%02x val=%04x idx=%04x len=%04x\n", |
| ctl.bRequestType, ctl.bRequest, ctl.wValue, |
| ctl.wIndex, ctl.wLength); |
| #endif |
| |
| 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; |
| if (ui->online && ui->usb_connected) |
| ui->usb_connected(1); |
| } 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; |
| |
| #if 0 |
| 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 |
| |
| /* 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->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); |
| } |
| |
| #define FLUSH_WAIT_US 5 |
| #define FLUSH_TIMEOUT (2 * (USEC_PER_SEC / FLUSH_WAIT_US)) |
| static void flush_endpoint_hw(struct usb_info *ui, unsigned bits) |
| { |
| uint32_t unflushed = 0; |
| uint32_t stat = 0; |
| int cnt = 0; |
| |
| /* 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?!) |
| */ |
| while (cnt < FLUSH_TIMEOUT) { |
| writel(bits, USB_ENDPTFLUSH); |
| while (((unflushed = readl(USB_ENDPTFLUSH)) & bits) && |
| cnt < FLUSH_TIMEOUT) { |
| cnt++; |
| udelay(FLUSH_WAIT_US); |
| } |
| |
| stat = readl(USB_ENDPTSTAT); |
| if (cnt >= FLUSH_TIMEOUT) |
| goto err; |
| if (!(stat & bits)) |
| goto done; |
| cnt++; |
| udelay(FLUSH_WAIT_US); |
| } |
| |
| err: |
| pr_warning("%s: Could not complete flush! NOT GOOD! " |
| "stat: %x unflushed: %x bits: %x\n", __func__, |
| stat, unflushed, bits); |
| done: |
| return; |
| } |
| |
| 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); |
| } |
| 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) |
| { |
| #if defined(CONFIG_ARCH_QSD8X50) || defined(CONFIG_ARCH_MSM7X30) |
| /* 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 60MHz CLKOUT in serial or carkit mode */ |
| ulpi_write(ui, 0x08, 0x09); |
| |
| /* Enable PHY Low Power Suspend - Clock Disable (PLPSCD) */ |
| writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); |
| mdelay(1); |
| #else |
| /* 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); |
| /* clear the SuspendM bit -> suspend the PHY */ |
| ulpi_write(ui, 1 << 6, 0x06); |
| #endif |
| } |
| |
| /* If this function returns < 0, the phy reset failed and we cannot |
| * continue at this point. The only solution is to wait until the next |
| * cable disconnect/reconnect to bring the phy back */ |
| static int usb_phy_reset(struct usb_info *ui) |
| { |
| u32 val; |
| int ret; |
| int retries; |
| |
| if (!ui->phy_reset) |
| return 0; |
| |
| if (ui->hw_reset) |
| ui->hw_reset(1); |
| ui->phy_reset(); |
| if (ui->hw_reset) |
| ui->hw_reset(0); |
| |
| #if defined(CONFIG_ARCH_QSD8X50) |
| val = readl(USB_PORTSC) & ~PORTSC_PTS_MASK; |
| writel(val | PORTSC_PTS_ULPI, USB_PORTSC); |
| |
| /* XXX: only necessary for pre-45nm internal PHYs. */ |
| for (retries = 3; retries > 0; retries--) { |
| ret = ulpi_write(ui, ULPI_FUNC_SUSPENDM, ULPI_FUNC_CTRL_CLR); |
| if (!ret) |
| break; |
| ui->phy_reset(); |
| } |
| if (!retries) |
| return -1; |
| |
| /* this reset calibrates the phy, if the above write succeeded */ |
| ui->phy_reset(); |
| |
| /* XXX: pre-45nm internal phys have a known issue which can cause them |
| * to lockup on reset. If ULPI accesses fail, try resetting the phy |
| * again */ |
| for (retries = 3; retries > 0; retries--) { |
| ret = ulpi_read(ui, ULPI_DEBUG_REG); |
| if (ret != 0xffffffff) |
| break; |
| ui->phy_reset(); |
| } |
| if (!retries) |
| return -1; |
| #endif |
| pr_info("msm_hsusb_phy_reset: success\n"); |
| return 0; |
| } |
| |
| static void usb_reset(struct usb_info *ui) |
| { |
| unsigned long flags; |
| printk(KERN_INFO "hsusb: reset controller\n"); |
| |
| spin_lock_irqsave(&ui->lock, flags); |
| ui->running = 0; |
| spin_unlock_irqrestore(&ui->lock, flags); |
| |
| /* To prevent phantom packets being received by the usb core on |
| * some devices, put the controller into reset prior to |
| * resetting the phy. */ |
| writel(2, USB_USBCMD); |
| msleep(10); |
| |
| #if 0 |
| /* we should flush and shutdown cleanly if already running */ |
| writel(0xffffffff, USB_ENDPTFLUSH); |
| msleep(2); |
| #endif |
| |
| if (usb_phy_reset(ui) < 0) |
| pr_err("%s: Phy reset failed!\n", __func__); |
| |
| msleep(100); |
| |
| /* toggle non-driving mode after phy reset to ensure that |
| * we cause a disconnect event to the host */ |
| ulpi_write(ui, 0x18, 0x6); |
| msleep(1); |
| ulpi_write(ui, 0x8, 0x5); |
| msleep(1); |
| |
| /* RESET */ |
| writel(2, USB_USBCMD); |
| msleep(10); |
| |
| #ifdef CONFIG_ARCH_MSM7X00A |
| /* INCR4 BURST mode */ |
| writel(0x01, USB_SBUSCFG); |
| #else |
| /* bursts of unspecified length. */ |
| writel(0, USB_AHBBURST); |
| /* Use the AHB transactor */ |
| writel(0, USB_AHBMODE); |
| #endif |
| |
| /* 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(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); |
| if (ui->otgclk) |
| clk_put(ui->otgclk); |
| if (ui->coreclk) |
| clk_put(ui->coreclk); |
| if (ui->ebi1clk) |
| clk_put(ui->ebi1clk); |
| 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_set_rate(ui->ebi1clk, 128000000); |
| udelay(10); |
| if (ui->coreclk) |
| clk_enable(ui->coreclk); |
| clk_enable(ui->clk); |
| clk_enable(ui->pclk); |
| if (ui->otgclk) |
| clk_enable(ui->otgclk); |
| 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); |
| } |
| |
| usb_phy_reset(ui); |
| |
| /* power down phy, clock down usb */ |
| spin_lock_irqsave(&ui->lock, iflags); |
| usb_suspend_phy(ui); |
| clk_disable(ui->pclk); |
| clk_disable(ui->clk); |
| if (ui->otgclk) |
| clk_disable(ui->otgclk); |
| if (ui->coreclk) |
| clk_disable(ui->coreclk); |
| clk_set_rate(ui->ebi1clk, 0); |
| 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_set_rate(ui->ebi1clk, 128000000); |
| udelay(10); |
| if (ui->coreclk) |
| clk_enable(ui->coreclk); |
| clk_enable(ui->clk); |
| clk_enable(ui->pclk); |
| if (ui->otgclk) |
| clk_enable(ui->otgclk); |
| usb_reset(ui); |
| |
| /* detect shorted D+/D-, indicating AC power */ |
| msleep(10); |
| if ((readl(USB_PORTSC) & PORTSC_LS) == PORTSC_LS) |
| if (ui->usb_connected) |
| ui->usb_connected(2); |
| |
| 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. |
| */ |
| void msm_hsusb_set_vbus_state(int online) |
| { |
| unsigned long flags = 0; |
| struct usb_info *ui = the_usb_info; |
| |
| if (ui) |
| spin_lock_irqsave(&ui->lock, flags); |
| if (vbus != online) { |
| vbus = online; |
| if (ui) { |
| if (online) { |
| ui->flags |= USB_FLAG_VBUS_ONLINE; |
| } else { |
| ui->flags |= USB_FLAG_VBUS_OFFLINE; |
| } |
| schedule_work(&ui->work); |
| } |
| } |
| if (ui) |
| spin_unlock_irqrestore(&ui->lock, flags); |
| } |
| |
| #if defined(CONFIG_DEBUG_FS) && 0 |
| |
| void usb_function_reenumerate(void) |
| { |
| struct usb_info *ui = the_usb_info; |
| |
| /* disable and re-enable the D+ pullup */ |
| msm72k_pullup(&ui->gadget, false); |
| msleep(10); |
| msm72k_pullup(&ui->gadget, true); |
| } |
| |
| 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", 0220, dent, ui, &debug_reset_ops); |
| debugfs_create_file("cycle", 0220, 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; |
| |
| if (ep_type == USB_ENDPOINT_XFER_BULK) |
| _ep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); |
| else |
| _ep->maxpacket = le16_to_cpu(64); |
| 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; |
| |
| /* request should not be busy */ |
| BUG_ON(req->busy); |
| |
| if (req->alloced) |
| kfree(req->req.buf); |
| dma_pool_free(ui->pool, req->item, req->item_dma); |
| kfree(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; |
| else { |
| if (ui->ep0_dir == USB_DIR_OUT) { |
| ep = &ui->ep0out; |
| ep->ep.driver_data = ui->ep0in.ep.driver_data; |
| } |
| /* ep0_queue_ack_complete queue a receive for ACK before |
| ** calling req->complete |
| */ |
| r->gadget_complete = req->complete; |
| req->complete = ep0_queue_ack_complete; |
| } |
| } |
| 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); |
| } |
| /* remove from linked list */ |
| if (prev) |
| prev->next = cur->next; |
| else |
| ep->req = cur->next; |
| if (ep->last == cur) |
| ep->last = prev; |
| /* break from loop */ |
| cur = NULL; |
| } else { |
| prev = cur; |
| 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); |
| |
| u32 cmd = (8 << 16); |
| |
| /* disable/enable D+ pullup */ |
| if (is_active) { |
| pr_info("msm_hsusb: enable pullup\n"); |
| writel(cmd | 1, USB_USBCMD); |
| } else { |
| pr_info("msm_hsusb: disable pullup\n"); |
| writel(cmd, USB_USBCMD); |
| |
| #if defined(CONFIG_ARCH_QSD8X50) || defined(CONFIG_ARCH_MSM7X30) |
| ulpi_write(ui, 0x48, 0x04); |
| #endif |
| } |
| |
| 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; |
| |
| spin_lock_init(&ui->lock); |
| 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)); |
| |
| ui->otgclk = clk_get(&pdev->dev, "usb_otg_clk"); |
| if (IS_ERR(ui->otgclk)) |
| ui->otgclk = NULL; |
| |
| ui->coreclk = clk_get(&pdev->dev, "usb_hs_core_clk"); |
| if (IS_ERR(ui->coreclk)) |
| ui->coreclk = NULL; |
| |
| ui->ebi1clk = clk_get(NULL, "ebi1_clk"); |
| if (IS_ERR(ui->ebi1clk)) |
| return usb_free(ui, PTR_ERR(ui->ebi1clk)); |
| |
| /* clear interrupts before requesting irq */ |
| if (ui->coreclk) |
| clk_enable(ui->coreclk); |
| clk_enable(ui->clk); |
| clk_enable(ui->pclk); |
| if (ui->otgclk) |
| clk_enable(ui->otgclk); |
| writel(0, USB_USBINTR); |
| writel(0, USB_OTGSC); |
| if (ui->coreclk) |
| clk_disable(ui->coreclk); |
| if (ui->otgclk) |
| clk_disable(ui->otgclk); |
| clk_disable(ui->pclk); |
| clk_disable(ui->clk); |
| |
| 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); |
| dev_set_name(&ui->gadget.dev, "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", }, |
| }; |
| |
| static int __init init(void) |
| { |
| 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"); |