2572 lines
60 KiB
C
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(¬ify_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(¬ify_sem);
|
|
}
|
|
|
|
int usb_register_notifier(struct t_usb_status_notifier *notifier)
|
|
{
|
|
if (!notifier || !notifier->name || !notifier->func)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(¬ify_sem);
|
|
list_add(¬ifier->notifier_link,
|
|
&g_lh_usb_notifier_list);
|
|
mutex_unlock(¬ify_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");
|