android_kernel_cmhtcleo/drivers/usb/gadget/msm72k_udc.c
2010-08-27 11:19:57 +02:00

2572 lines
60 KiB
C

/*
* 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/irq.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/switch.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>
#ifdef CONFIG_ARCH_MSM7X30
#include <mach/rpc_hsusb.h>
#endif
#ifdef CONFIG_USB_ACCESSORY_DETECT_BY_ADC
#include <mach/htc_headset_mgr.h>
#endif
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"
};
static struct usb_info *the_usb_info;
/* current state of VBUS */
static int vbus;
static int use_mfg_serialno;
static char mfg_df_serialno[16];
#ifdef CONFIG_USB_ACCESSORY_DETECT
#ifdef CONFIG_USB_ACCESSORY_DETECT_BY_ADC
extern int htc_get_usb_accessory_adc_level(uint32_t *buffer);
#endif
static struct switch_dev dock_switch = {
.name = "dock",
};
#define DOCK_STATE_UNDOCKED 0
#define DOCK_STATE_DESK (1 << 0)
#define DOCK_STATE_CAR (1 << 1)
#endif
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);
static void check_charger(struct work_struct *w);
#ifdef CONFIG_USB_ACCESSORY_DETECT
static void accessory_detect_work(struct work_struct *w);
#endif
extern int android_switch_function(unsigned func);
extern int android_show_function(char *buf);
extern void android_set_serialno(char *serialno);
#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
enum usb_connect_type {
CONNECT_TYPE_NONE = 0,
CONNECT_TYPE_USB,
CONNECT_TYPE_AC,
CONNECT_TYPE_UNKNOWN,
};
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);
void (*usb_uart_switch)(int);
/* for notification when USB is connected or disconnected */
void (*usb_connected)(int);
struct workqueue_struct *usb_wq;
struct work_struct work;
struct delayed_work chg_work;
struct work_struct detect_work;
struct work_struct notifier_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;
enum usb_connect_type connect_type;
u8 in_lpm;
/* for accessory detection */
u8 accessory_detect;
u8 mfg_usb_carkit_enable;
int idpin_irq;
int usb_id_pin_gpio;
void (*config_usb_id_gpios)(bool output_enable);
/* 0: none, 1: carkit, 2: usb headset */
u8 accessory_type;
};
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 DEFINE_MUTEX(notify_sem);
static void send_usb_connect_notify(struct work_struct *w)
{
static struct t_usb_status_notifier *notifier;
struct usb_info *ui = container_of(w, struct usb_info,
notifier_work);
if (!ui)
return;
printk(KERN_INFO "usb: send connect type %d\n", ui->connect_type);
mutex_lock(&notify_sem);
list_for_each_entry(notifier,
&g_lh_usb_notifier_list,
notifier_link) {
if (notifier->func != NULL) {
/* Notify other drivers about connect type. */
/* use slow charging for unknown type*/
if (ui->connect_type == CONNECT_TYPE_UNKNOWN)
notifier->func(CONNECT_TYPE_USB);
else
notifier->func(ui->connect_type);
}
}
mutex_unlock(&notify_sem);
}
int usb_register_notifier(struct t_usb_status_notifier *notifier)
{
if (!notifier || !notifier->name || !notifier->func)
return -EINVAL;
mutex_lock(&notify_sem);
list_add(&notifier->notifier_link,
&g_lh_usb_notifier_list);
mutex_unlock(&notify_sem);
return 0;
}
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 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) {
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 = 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_request *r = to_msm_request(_req);
struct msm_endpoint *ept = to_msm_endpoint(ep);
struct usb_info *ui = ept->ui;
struct usb_request *req = ui->setup_req;
/* queue up the receive of the ACK response from the host */
if (_req->status == 0 && _req->actual == _req->length) {
req->length = 0;
if (ui->ep0_dir == USB_DIR_IN)
usb_ept_queue_xfer(&ui->ep0out, req);
else
usb_ept_queue_xfer(&ui->ep0in, req);
_req->complete = r->gadget_complete;
r->gadget_complete = 0;
if (_req->complete)
_req->complete(&ui->ep0in.ep, _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);
#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;
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->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);
}
#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);
}
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("usb: portchange USB_SPEED_FULL\n");
ui->gadget.speed = USB_SPEED_FULL;
break;
case PORTSC_PSPD_LS:
INFO("usb: portchange USB_SPEED_LOW\n");
ui->gadget.speed = USB_SPEED_LOW;
break;
case PORTSC_PSPD_HS:
INFO("usb: portchange USB_SPEED_HIGH\n");
ui->gadget.speed = USB_SPEED_HIGH;
break;
}
}
if (n & STS_URI) {
INFO("usb: 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 (ui->connect_type != CONNECT_TYPE_USB) {
ui->connect_type = CONNECT_TYPE_USB;
queue_work(ui->usb_wq, &ui->notifier_work);
}
}
if (n & STS_SLI)
INFO("usb: 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;
}
int usb_get_connect_type(void)
{
if (!the_usb_info)
return 0;
return the_usb_info->connect_type;
}
EXPORT_SYMBOL(usb_get_connect_type);
void msm_hsusb_request_reset(void)
{
struct usb_info *ui = the_usb_info;
unsigned long flags;
if (!ui)
return;
spin_lock_irqsave(&ui->lock, flags);
ui->flags |= USB_FLAG_RESET;
queue_work(ui->usb_wq, &ui->work);
spin_unlock_irqrestore(&ui->lock, flags);
}
static ssize_t show_usb_cable_connect(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned length;
if (!the_usb_info)
return 0;
length = sprintf(buf, "%d\n",
(the_usb_info->connect_type == CONNECT_TYPE_USB)?1:0);
return length;
}
static DEVICE_ATTR(usb_cable_connect, 0444, show_usb_cable_connect, NULL);
static ssize_t show_usb_function_switch(struct device *dev,
struct device_attribute *attr, char *buf)
{
return android_show_function(buf);
}
static ssize_t store_usb_function_switch(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned u;
ssize_t ret;
u = simple_strtoul(buf, NULL, 10);
ret = android_switch_function(u);
if (ret == 0)
return count;
else
return 0;
}
static DEVICE_ATTR(usb_function_switch, 0666,
show_usb_function_switch, store_usb_function_switch);
static ssize_t show_usb_serial_number(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned length;
struct msm_hsusb_platform_data *pdata = dev->platform_data;
length = sprintf(buf, "%s", pdata->serial_number);
return length;
}
static ssize_t store_usb_serial_number(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct msm_hsusb_platform_data *pdata = dev->platform_data;
if (buf[0] == '0' || buf[0] == '1') {
memset(mfg_df_serialno, 0x0, sizeof(mfg_df_serialno));
if (buf[0] == '0') {
strncpy(mfg_df_serialno, "000000000000",
strlen("000000000000"));
use_mfg_serialno = 1;
android_set_serialno(mfg_df_serialno);
} else {
strncpy(mfg_df_serialno, pdata->serial_number,
strlen(pdata->serial_number));
use_mfg_serialno = 0;
android_set_serialno(pdata->serial_number);
}
/* reset_device */
msm_hsusb_request_reset();
}
return count;
}
static DEVICE_ATTR(usb_serial_number, 0644,
show_usb_serial_number, store_usb_serial_number);
static ssize_t show_dummy_usb_serial_number(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned length;
struct msm_hsusb_platform_data *pdata = dev->platform_data;
if (use_mfg_serialno)
length = sprintf(buf, "%s", mfg_df_serialno); /* dummy */
else
length = sprintf(buf, "%s", pdata->serial_number); /* Real */
return length;
}
static ssize_t store_dummy_usb_serial_number(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int data_buff_size = (sizeof(mfg_df_serialno) > strlen(buf))?
strlen(buf):sizeof(mfg_df_serialno);
int loop_i;
/* avoid overflow, mfg_df_serialno[16] always is 0x0 */
if (data_buff_size == 16)
data_buff_size--;
for (loop_i = 0; loop_i < data_buff_size; loop_i++) {
if (buf[loop_i] >= 0x30 && buf[loop_i] <= 0x39) /* 0-9 */
continue;
else if (buf[loop_i] >= 0x41 && buf[loop_i] <= 0x5A) /* A-Z */
continue;
if (buf[loop_i] == 0x0A) /* Line Feed */
continue;
else {
printk(KERN_WARNING "%s(): get invaild char (0x%2.2X)\n",
__func__, buf[loop_i]);
return -EINVAL;
}
}
use_mfg_serialno = 1;
memset(mfg_df_serialno, 0x0, sizeof(mfg_df_serialno));
strncpy(mfg_df_serialno, buf, data_buff_size);
android_set_serialno(mfg_df_serialno);
/*device_reset */
msm_hsusb_request_reset();
return count;
}
static DEVICE_ATTR(dummy_usb_serial_number, 0644,
show_dummy_usb_serial_number, store_dummy_usb_serial_number);
static ssize_t show_USB_ID_status(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct usb_info *ui = the_usb_info;
int value = 1;
unsigned length;
if (!ui)
return 0;
if (ui->usb_id_pin_gpio != 0) {
value = gpio_get_value(ui->usb_id_pin_gpio);
printk(KERN_INFO "usb: id pin status %d\n", value);
}
length = sprintf(buf, "%d", value);
return length;
}
static DEVICE_ATTR(USB_ID_status, 0444,
show_USB_ID_status, NULL);
static ssize_t show_usb_phy_setting(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_info *ui = the_usb_info;
unsigned length = 0;
int i;
for (i = 0; i <= 0x14; i++)
length += sprintf(buf + length, "0x%x = 0x%x\n", i, ulpi_read(ui, i));
for (i = 0x30; i <= 0x37; i++)
length += sprintf(buf + length, "0x%x = 0x%x\n", i, ulpi_read(ui, i));
return length;
}
static ssize_t store_usb_phy_setting(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct usb_info *ui = the_usb_info;
char *token[10];
unsigned reg;
unsigned value;
int i;
printk(KERN_INFO "%s\n", buf);
for (i = 0; i < 2; i++)
token[i] = strsep((char **)&buf, " ");
reg = simple_strtoul(token[0], NULL, 16);
value = simple_strtoul(token[1], NULL, 16);
printk(KERN_INFO "Set 0x%x = 0x%x\n", reg, value);
ulpi_write(ui, value, reg);
return 0;
}
static DEVICE_ATTR(usb_phy_setting, 0666,
show_usb_phy_setting, store_usb_phy_setting);
#ifdef CONFIG_USB_ACCESSORY_DETECT
static ssize_t show_mfg_carkit_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned length;
struct usb_info *ui = the_usb_info;
length = sprintf(buf, "%d", ui->mfg_usb_carkit_enable);
printk(KERN_INFO "%s: %d\n", __func__,
ui->mfg_usb_carkit_enable);
return length;
}
static ssize_t store_mfg_carkit_enable(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb_info *ui = the_usb_info;
unsigned char uc;
if (buf[0] != '0' && buf[0] != '1') {
printk(KERN_ERR "Can't enable/disable carkit\n");
return -EINVAL;
}
uc = buf[0] - '0';
printk(KERN_INFO "%s: %d\n", __func__, uc);
ui->mfg_usb_carkit_enable = uc;
if (uc == 1 && ui->accessory_type == 1 &&
board_mfg_mode() == 1) {
switch_set_state(&dock_switch, DOCK_STATE_CAR);
printk(KERN_INFO "carkit: set state %d\n", DOCK_STATE_CAR);
}
return count;
}
static DEVICE_ATTR(usb_mfg_carkit_enable, 0644,
show_mfg_carkit_enable, store_mfg_carkit_enable);
static ssize_t dock_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_info *ui = the_usb_info;
if (ui->accessory_type == 1)
return sprintf(buf, "online\n");
else
return sprintf(buf, "offline\n");
}
static DEVICE_ATTR(status, S_IRUGO | S_IWUSR, dock_status_show, NULL);
#endif
static void usb_prepare(struct usb_info *ui)
{
int ret;
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);
ui->usb_wq = create_singlethread_workqueue("msm_hsusb");
if (ui->usb_wq == 0) {
printk(KERN_ERR "usb: fail to create workqueue\n");
return;
}
INIT_WORK(&ui->work, usb_do_work);
#ifdef CONFIG_USB_ACCESSORY_DETECT
INIT_WORK(&ui->detect_work, accessory_detect_work);
#endif
INIT_WORK(&ui->notifier_work, send_usb_connect_notify);
INIT_DELAYED_WORK(&ui->chg_work, check_charger);
ret = device_create_file(&ui->pdev->dev,
&dev_attr_usb_cable_connect);
if (ret != 0)
printk(KERN_WARNING "dev_attr_usb_cable_connect failed\n");
ret = device_create_file(&ui->pdev->dev,
&dev_attr_usb_function_switch);
if (ret != 0)
printk(KERN_WARNING "dev_attr_usb_function_switch failed\n");
ret = device_create_file(&ui->pdev->dev,
&dev_attr_usb_serial_number);
if (ret != 0)
printk(KERN_WARNING "dev_attr_usb_serial_number failed\n");
ret = device_create_file(&ui->pdev->dev,
&dev_attr_dummy_usb_serial_number);
if (ret != 0)
printk(KERN_WARNING "dev_attr_dummy_usb_serial_number failed\n");
ret = device_create_file(&ui->pdev->dev,
&dev_attr_USB_ID_status);
if (ret != 0)
printk(KERN_WARNING "dev_attr_USB_ID_status failed\n");
ret = device_create_file(&ui->pdev->dev,
&dev_attr_usb_phy_setting);
if (ret != 0)
printk(KERN_WARNING "dev_attr_usb_phy_setting failed\n");
#ifdef CONFIG_USB_ACCESSORY_DETECT
ret = device_create_file(&ui->pdev->dev,
&dev_attr_usb_mfg_carkit_enable);
if (ret != 0)
printk(KERN_WARNING "dev_attr_usb_mfg_carkit_enable failed\n");
#endif
}
static int usb_wakeup_phy(struct usb_info *ui)
{
int i;
/*writel(readl(USB_USBCMD) & ~ULPI_STP_CTRL, USB_USBCMD);*/
/* some circuits automatically clear PHCD bit */
for (i = 0; i < 5 && (readl(USB_PORTSC) & PORTSC_PHCD); i++) {
writel(readl(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC);
mdelay(1);
}
if ((readl(USB_PORTSC) & PORTSC_PHCD)) {
pr_err("%s: cannot clear phcd bit\n", __func__);
return -1;
}
return 0;
}
static void usb_suspend_phy(struct usb_info *ui)
{
printk(KERN_INFO "%s\n", __func__);
#ifdef CONFIG_ARCH_MSM7X00A
/* disable unused interrupt */
ulpi_write(ui, 0x01, 0x0d);
ulpi_write(ui, 0x01, 0x10);
/* 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);
#else
#ifdef CONFIG_ARCH_MSM7X30
ulpi_write(ui, 0x0, 0x0D);
ulpi_write(ui, 0x0, 0x10);
#endif
/* 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);
ulpi_read(ui, 0x14); /* clear PHY interrupt latch register*/
ulpi_write(ui, 0x08, 0x09);/* turn off PLL on integrated phy */
/* set phy to be in lpm */
writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC);
mdelay(1);
if (!(readl(USB_PORTSC) & PORTSC_PHCD))
printk(KERN_INFO "%s: unable to set lpm\n", __func__);
#endif
}
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);
/* disable usb interrupts */
writel(0, USB_USBINTR);
/* wait for a while after enable usb clk*/
msleep(5);
/* 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 (ui->phy_reset)
ui->phy_reset();
msleep(100);
/* 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;
queue_work(ui->usb_wq, &ui->work);
spin_unlock_irqrestore(&ui->lock, flags);
}
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 defined(CONFIG_USB_BYPASS_VBUS_NOTIFY)
ui->flags |= USB_FLAG_VBUS_ONLINE;
pr_info("usb: fake vbus\n");
#else
if (vbus)
ui->flags |= USB_FLAG_VBUS_ONLINE;
else
ui->flags |= USB_FLAG_VBUS_OFFLINE;
#endif
spin_unlock_irqrestore(&ui->lock, iflags);
}
static void usb_lpm_enter(struct usb_info *ui)
{
unsigned long iflags;
if (ui->in_lpm)
return;
printk(KERN_INFO "usb: lpm enter\n");
spin_lock_irqsave(&ui->lock, iflags);
usb_suspend_phy(ui);
if (ui->otgclk)
clk_disable(ui->otgclk);
clk_disable(ui->pclk);
clk_disable(ui->clk);
if (ui->coreclk)
clk_disable(ui->coreclk);
clk_set_rate(ui->ebi1clk, 0);
ui->in_lpm = 1;
spin_unlock_irqrestore(&ui->lock, iflags);
}
static void usb_lpm_exit(struct usb_info *ui)
{
if (!ui->in_lpm)
return;
printk(KERN_INFO "usb: lpm exit\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_wakeup_phy(ui);
ui->in_lpm = 0;
}
#ifdef CONFIG_USB_ACCESSORY_DETECT
static void carkit_detect(struct usb_info *ui)
{
unsigned n;
int value;
unsigned in_lpm;
msleep(100);
value = gpio_get_value(ui->usb_id_pin_gpio);
printk(KERN_INFO "usb: usb ID pin = %d\n", value);
in_lpm = ui->in_lpm;
if (value == 0) {
if (in_lpm)
usb_lpm_exit(ui);
n = readl(USB_OTGSC);
/* ID pull-up register */
writel(n | OTGSC_IDPU, USB_OTGSC);
msleep(100);
n = readl(USB_OTGSC);
if (n & OTGSC_ID) {
printk(KERN_INFO "usb: carkit inserted\n");
if ((board_mfg_mode() == 0) || (board_mfg_mode() == 1 &&
ui->mfg_usb_carkit_enable == 1)) {
switch_set_state(&dock_switch, DOCK_STATE_CAR);
printk(KERN_INFO "carkit: set state %d\n", DOCK_STATE_CAR);
}
ui->accessory_type = 1;
} else
ui->accessory_type = 0;
if (in_lpm)
usb_lpm_enter(ui);
} else {
if (ui->accessory_type == 1)
printk(KERN_INFO "usb: carkit removed\n");
switch_set_state(&dock_switch, DOCK_STATE_UNDOCKED);
printk(KERN_INFO "carkit: set state %d\n", DOCK_STATE_UNDOCKED);
ui->accessory_type = 0;
}
}
#ifdef CONFIG_USB_ACCESSORY_DETECT_BY_ADC
static void accessory_detect_by_adc(struct usb_info *ui)
{
int value;
msleep(100);
value = gpio_get_value(ui->usb_id_pin_gpio);
printk(KERN_INFO "usb: usb ID pin = %d\n", value);
if (value == 0) {
uint32_t adc_value = 0xffffffff;
htc_get_usb_accessory_adc_level(&adc_value);
printk(KERN_INFO "usb: accessory adc = 0x%x\n", adc_value);
if (adc_value >= 0x2112 && adc_value <= 0x3D53) {
printk(KERN_INFO "usb: headset inserted\n");
ui->accessory_type = 2;
headset_ext_detect(USB_HEADSET);
} else if (adc_value >= 0x88A && adc_value <= 0x1E38) {
printk(KERN_INFO "usb: carkit inserted\n");
ui->accessory_type = 1;
if ((board_mfg_mode() == 0) || (board_mfg_mode() == 1 &&
ui->mfg_usb_carkit_enable == 1)) {
switch_set_state(&dock_switch, DOCK_STATE_CAR);
printk(KERN_INFO "carkit: set state %d\n", DOCK_STATE_CAR);
}
} else
ui->accessory_type = 0;
} else {
if (ui->accessory_type == 2) {
printk(KERN_INFO "usb: headset removed\n");
headset_ext_detect(NO_DEVICE);
} else if (ui->accessory_type == 1) {
printk(KERN_INFO "usb: carkit removed\n");
switch_set_state(&dock_switch, DOCK_STATE_UNDOCKED);
}
ui->accessory_type = 0;
}
}
#endif
static void accessory_detect_work(struct work_struct *w)
{
struct usb_info *ui = container_of(w, struct usb_info, detect_work);
int value;
if (!ui->accessory_detect)
return;
if (ui->accessory_detect == 1)
carkit_detect(ui);
#ifdef CONFIG_USB_ACCESSORY_DETECT_BY_ADC
else if (ui->accessory_detect == 2)
accessory_detect_by_adc(ui);
#endif
value = gpio_get_value(ui->usb_id_pin_gpio);
if (value == 0)
set_irq_type(ui->idpin_irq, IRQF_TRIGGER_HIGH);
else
set_irq_type(ui->idpin_irq, IRQF_TRIGGER_LOW);
enable_irq(ui->idpin_irq);
}
static irqreturn_t usbid_interrupt(int irq, void *data)
{
struct usb_info *ui = data;
disable_irq_nosync(ui->idpin_irq);
printk(KERN_INFO "usb: id interrupt\n");
queue_work(ui->usb_wq, &ui->detect_work);
return IRQ_HANDLED;
}
static void accessory_detect_init(struct usb_info *ui)
{
int ret;
printk(KERN_INFO "%s: id pin %d\n", __func__,
ui->usb_id_pin_gpio);
if (ui->usb_id_pin_gpio == 0)
return;
ui->idpin_irq = gpio_to_irq(ui->usb_id_pin_gpio);
ret = request_irq(ui->idpin_irq, usbid_interrupt,
IRQF_TRIGGER_LOW,
"car_kit_irq", ui);
if (ret < 0) {
printk(KERN_ERR "%s: request_irq failed\n", __func__);
return;
}
ret = set_irq_wake(ui->idpin_irq, 1);
if (ret < 0) {
printk(KERN_ERR "%s: set_irq_wake failed\n", __func__);
goto err;
}
if (switch_dev_register(&dock_switch) < 0) {
printk(KERN_ERR "usb: fail to register dock switch!\n");
goto err;
}
ret = device_create_file(dock_switch.dev, &dev_attr_status);
if (ret != 0)
printk(KERN_WARNING "dev_attr_status failed\n");
return;
err:
free_irq(ui->idpin_irq, 0);
}
#endif
#define DELAY_FOR_CHECK_CHG msecs_to_jiffies(300)
static void charger_detect(struct usb_info *ui)
{
if (!vbus)
return;
msleep(10);
/* detect shorted D+/D-, indicating AC power */
if ((readl(USB_PORTSC) & PORTSC_LS) != PORTSC_LS) {
printk(KERN_INFO "usb: not AC charger\n");
ui->connect_type = CONNECT_TYPE_UNKNOWN;
queue_delayed_work(ui->usb_wq, &ui->chg_work,
DELAY_FOR_CHECK_CHG);
} else {
printk(KERN_INFO "usb: AC charger\n");
ui->connect_type = CONNECT_TYPE_AC;
queue_work(ui->usb_wq, &ui->notifier_work);
writel(0x00080000, USB_USBCMD);
msleep(10);
usb_lpm_enter(ui);
}
}
static void check_charger(struct work_struct *w)
{
struct usb_info *ui = container_of(w, struct usb_info, chg_work.work);
/* unknown charger */
if (vbus && ui->connect_type == CONNECT_TYPE_UNKNOWN)
queue_work(ui->usb_wq, &ui->notifier_work);
}
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("hsusb: IDLE -> ONLINE\n");
usb_lpm_exit(ui);
usb_reset(ui);
charger_detect(ui);
ui->state = USB_STATE_ONLINE;
#ifdef CONFIG_USB_ACCESSORY_DETECT
if (ui->accessory_detect)
accessory_detect_init(ui);
#endif
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("hsusb: ONLINE -> OFFLINE\n");
/* synchronize with irq context */
spin_lock_irqsave(&ui->lock, iflags);
ui->running = 0;
ui->online = 0;
writel(0x00080000, USB_USBCMD);
spin_unlock_irqrestore(&ui->lock, iflags);
if (ui->connect_type != CONNECT_TYPE_NONE) {
ui->connect_type = CONNECT_TYPE_NONE;
queue_work(ui->usb_wq, &ui->notifier_work);
}
if (ui->in_lpm) {
usb_lpm_exit(ui);
msleep(5);
}
/* terminate any transactions, etc */
flush_all_endpoints(ui);
if (ui->driver) {
printk(KERN_INFO "usb: notify offline\n");
ui->driver->disconnect(&ui->gadget);
}
if (ui->phy_reset)
ui->phy_reset();
/* power down phy, clock down usb */
usb_lpm_enter(ui);
ui->state = USB_STATE_OFFLINE;
usb_do_work_check_vbus(ui);
break;
}
if (flags & USB_FLAG_RESET) {
pr_info("hsusb: ONLINE -> RESET\n");
if (ui->connect_type == CONNECT_TYPE_AC) {
pr_info("hsusb: RESET -> ONLINE\n");
break;
}
spin_lock_irqsave(&ui->lock, iflags);
ui->online = 0;
msm72k_pullup(&ui->gadget, 0);
spin_unlock_irqrestore(&ui->lock, iflags);
usb_reset(ui);
pr_info("hsusb: 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("hsusb: OFFLINE -> ONLINE\n");
usb_lpm_exit(ui);
usb_reset(ui);
charger_detect(ui);
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;
printk(KERN_INFO "%s: %d\n", __func__, online);
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;
}
/* online->switch to USB, offline->switch to uart */
if (ui->usb_uart_switch)
ui->usb_uart_switch(!online);
queue_work(ui->usb_wq, &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;
queue_work(ui->usb_wq, &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;
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;
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);
}
if (req->dead)
do_free_req(ui, req);
/* 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);
#ifndef CONFIG_ARCH_MSM7X00A
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;
ui->usb_uart_switch = pdata->usb_uart_switch;
ui->accessory_detect = pdata->accessory_detect;
printk(KERN_INFO "usb: accessory detect %d\n",
ui->accessory_detect);
ui->usb_id_pin_gpio = pdata->usb_id_pin_gpio;
printk(KERN_INFO "usb: id_pin_gpio %d\n",
pdata->usb_id_pin_gpio);
if (pdata->config_usb_id_gpios)
ui->config_usb_id_gpios = pdata->config_usb_id_gpios;
}
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);
#ifdef CONFIG_ARCH_MSM7X30
msm_hsusb_rpc_connect();
#endif
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);
ui->in_lpm = 1;
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);
/* initialize mfg serial number */
if (board_mfg_mode() == 1)
use_mfg_serialno = 1;
else
use_mfg_serialno = 0;
strncpy(mfg_df_serialno, "000000000000", strlen("000000000000"));
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");