Commit 0f114671 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

USB SL811HS host controller driver.

Added the driver to the 2.5 tree.  The original code for 2.4 was
written by Pei Liu <pbl@cypress.com> but cleaned up a bit and 
ported to 2.5 by me.  Any problems in the driver is probably due to
my messing with it.

This driver is for the SL811HS USB host controller chip from
Cypress and is typically contained on a ARM based embedded
system.
parent 96b37d50
......@@ -12,6 +12,7 @@ subdir-$(CONFIG_USB) += core
subdir-$(CONFIG_USB_EHCI_HCD) += host
subdir-$(CONFIG_USB_OHCI_HCD) += host
subdir-$(CONFIG_USB_OHCI) += host
subdir-$(CONFIG_USB_SL811HS) += host
subdir-$(CONFIG_USB_UHCI_ALT) += host
subdir-$(CONFIG_USB_UHCI_HCD_ALT) += host
subdir-$(CONFIG_USB_UHCI_HCD) += host
......
......@@ -89,3 +89,13 @@ CONFIG_USB_OHCI
inserted in and removed from the running kernel whenever you want).
The module will be called usb-ohci.o. If you want to compile it
as a module, say M here and read <file:Documentation/modules.txt>.
CONFIG_USB_SL811HS
Say Y here if you have a SL811HS USB host controller in your system.
If you do not know what this is, please say N.
This code is also available as a module ( = code which can be
inserted in and removed from the running kernel whenever you want).
The module will be called hc_sl811.o. If you want to compile it
as a module, say M here and read <file:Documentation/modules.txt>.
......@@ -19,3 +19,6 @@ fi
# define_bool CONFIG_USB_UHCI_ALT n
#fi
#dep_tristate ' OHCI (Compaq, iMacs, OPTi, SiS, ALi, ...) support' CONFIG_USB_OHCI $CONFIG_USB
if [ "$CONFIG_ARM" = "y" ]; then
dep_tristate ' SL811HS support' CONFIG_USB_SL811HS $CONFIG_USB
fi
......@@ -13,5 +13,6 @@ obj-$(CONFIG_USB_UHCI_HCD_ALT) += uhci-hcd.o
obj-$(CONFIG_USB_UHCI) += usb-uhci.o
obj-$(CONFIG_USB_UHCI_ALT) += uhci.o
obj-$(CONFIG_USB_OHCI) += usb-ohci.o
obj-$(CONFIG_USB_SL811HS) += hc_sl811.o
include $(TOPDIR)/Rules.make
/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*
* simple generic USB HCD frontend Version 0.9.5 (10/28/2001)
* for embedded HCs (SL811HS)
*
* USB URB handling, hci_ hcs_
* URB queueing, qu_
* Transfer scheduling, sh_
*
*
*-------------------------------------------------------------------------*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*-------------------------------------------------------------------------*/
/* main lock for urb access */
static spinlock_t usb_urb_lock = SPIN_LOCK_UNLOCKED;
/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/* URB HCD API function layer
* * * */
/***************************************************************************
* Function Name : hcs_urb_queue
*
* This function initializes the urb status and length before queueing the
* urb.
*
* Input: hci = data structure for the host controller
* urb = USB request block data structure
*
* Return: 0
**************************************************************************/
static inline int hcs_urb_queue (hci_t * hci, struct urb * urb)
{
int i;
DBGFUNC ("enter hcs_urb_queue\n");
if (usb_pipeisoc (urb->pipe)) {
DBGVERBOSE ("hcs_urb_queue: isoc pipe\n");
for (i = 0; i < urb->number_of_packets; i++) {
urb->iso_frame_desc[i].actual_length = 0;
urb->iso_frame_desc[i].status = -EXDEV;
}
/* urb->next hack : 1 .. resub, 0 .. single shot */
/* urb->interval = urb->next ? 1 : 0; */
}
urb->status = -EINPROGRESS;
urb->actual_length = 0;
urb->error_count = 0;
if (usb_pipecontrol (urb->pipe))
hc_flush_data_cache (hci, urb->setup_packet, 8);
if (usb_pipeout (urb->pipe))
hc_flush_data_cache (hci, urb->transfer_buffer,
urb->transfer_buffer_length);
qu_queue_urb (hci, urb);
return 0;
}
/***************************************************************************
* Function Name : hcs_return_urb
*
* This function the return path of URB back to the USB core. It calls the
* the urb complete function if exist, and also handles the resubmition of
* interrupt URBs.
*
* Input: hci = data structure for the host controller
* urb = USB request block data structure
* resub_ok = resubmit flag: 1 = submit urb again, 0 = not submit
*
* Return: 0
**************************************************************************/
static int hcs_return_urb (hci_t * hci, struct urb * urb, int resub_ok)
{
struct usb_device *dev = urb->dev;
int resubmit = 0;
DBGFUNC ("enter hcs_return_urb, urb pointer = 0x%x, "
"transferbuffer point = 0x%x, "
" setup packet pointer = 0x%x, context pointer = 0x%x \n",
(__u32 *) urb, (__u32 *) urb->transfer_buffer,
(__u32 *) urb->setup_packet, (__u32 *) urb->context);
if (urb_debug)
urb_print (urb, "RET", usb_pipeout (urb->pipe));
resubmit = urb->interval && resub_ok;
urb->dev = urb->hcpriv = NULL;
if (urb->complete) {
urb->complete (urb); /* call complete */
}
if (resubmit) {
/* requeue the URB */
urb->dev = dev;
hcs_urb_queue (hci, urb);
} else {
usb_put_urb(urb);
}
return 0;
}
/***************************************************************************
* Function Name : hci_submit_urb
*
* This function is called by the USB core API when an URB is available to
* process. This function does the following
*
* 1) Check the validity of the URB
* 2) Parse the device number from the URB
* 3) Pass the URB to the root hub routine if its intended for the hub, else
* queue the urb for the attached device.
*
* Input: urb = USB request block data structure
*
* Return: 0 if success or error code
**************************************************************************/
static int hci_submit_urb (struct urb * urb, int mem_flags)
{
hci_t *hci;
unsigned int pipe = urb->pipe;
unsigned long flags;
int ret;
DBGFUNC ("enter hci_submit_urb, pipe = 0x%x\n", urb->pipe);
if (!urb->dev || !urb->dev->bus || urb->hcpriv)
return -EINVAL;
if (usb_endpoint_halted
(urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe))) {
printk ("hci_submit_urb: endpoint_halted\n");
return -EPIPE;
}
hci = (hci_t *) urb->dev->bus->hcpriv;
/* a request to the virtual root hub */
if (usb_pipedevice (pipe) == hci->rh.devnum) {
if (urb_debug > 1)
urb_print (urb, "SUB-RH", usb_pipein (pipe));
return rh_submit_urb (urb);
}
/* increment urb's reference count, we now control it. */
urb = usb_get_urb (urb);
/* queue the URB to its endpoint-queue */
spin_lock_irqsave (&usb_urb_lock, flags);
ret = hcs_urb_queue (hci, urb);
if (ret != 0) {
/* error on return */
DBGERR ("hci_submit_urb: return err, ret = 0x%x, urb->status = 0x%x\n",
ret, urb->status);
usb_put_urb (urb);
}
spin_unlock_irqrestore (&usb_urb_lock, flags);
return ret;
}
/***************************************************************************
* Function Name : hci_unlink_urb
*
* This function mark the URB to unlink
*
* Input: urb = USB request block data structure
*
* Return: 0 if success or error code
**************************************************************************/
static int hci_unlink_urb (struct urb * urb)
{
unsigned long flags;
hci_t *hci;
DECLARE_WAITQUEUE (wait, current);
void *comp = NULL;
DBGFUNC ("enter hci_unlink_urb\n");
if (!urb) /* just to be sure */
return -EINVAL;
if (!urb->dev || !urb->dev->bus)
return -ENODEV;
hci = (hci_t *) urb->dev->bus->hcpriv;
/* a request to the virtual root hub */
if (usb_pipedevice (urb->pipe) == hci->rh.devnum) {
return rh_unlink_urb (urb);
}
if (urb_debug)
urb_print (urb, "UNLINK", 1);
spin_lock_irqsave (&usb_urb_lock, flags);
if (!list_empty (&urb->urb_list) && urb->status == -EINPROGRESS) {
/* URB active? */
if (urb->transfer_flags & (USB_ASYNC_UNLINK | USB_TIMEOUT_KILLED)) {
/* asynchron with callback */
list_del (&urb->urb_list); /* relink the urb to the del list */
list_add (&urb->urb_list, &hci->del_list);
spin_unlock_irqrestore (&usb_urb_lock, flags);
} else {
/* synchron without callback */
add_wait_queue (&hci->waitq, &wait);
set_current_state (TASK_UNINTERRUPTIBLE);
comp = urb->complete;
urb->complete = NULL;
list_del (&urb->urb_list); /* relink the urb to the del list */
list_add (&urb->urb_list, &hci->del_list);
spin_unlock_irqrestore (&usb_urb_lock, flags);
schedule_timeout (HZ / 50);
if (!list_empty (&urb->urb_list))
list_del (&urb->urb_list);
urb->complete = comp;
urb->hcpriv = NULL;
remove_wait_queue (&hci->waitq, &wait);
}
} else {
/* hcd does not own URB but we keep the driver happy anyway */
spin_unlock_irqrestore (&usb_urb_lock, flags);
if (urb->complete && (urb->transfer_flags & USB_ASYNC_UNLINK)) {
urb->status = -ENOENT;
urb->actual_length = 0;
urb->complete (urb);
urb->status = 0;
} else {
urb->status = -ENOENT;
}
}
return 0;
}
/***************************************************************************
* Function Name : hci_alloc_dev
*
* This function allocates private data space for the usb device and
* initialize the endpoint descriptor heads.
*
* Input: usb_dev = pointer to the usb device
*
* Return: 0 if success or error code
**************************************************************************/
static int hci_alloc_dev (struct usb_device *usb_dev)
{
struct hci_device *dev;
int i;
DBGFUNC ("enter hci_alloc_dev\n");
dev = kmalloc (sizeof (*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
memset (dev, 0, sizeof (*dev));
for (i = 0; i < 32; i++) {
INIT_LIST_HEAD (&(dev->ed[i].urb_queue));
dev->ed[i].pipe_head = NULL;
}
usb_dev->hcpriv = dev;
DBGVERBOSE ("USB HC dev alloc %d bytes\n", sizeof (*dev));
return 0;
}
/***************************************************************************
* Function Name : hci_free_dev
*
* This function de-allocates private data space for the usb devic
*
* Input: usb_dev = pointer to the usb device
*
* Return: 0
**************************************************************************/
static int hci_free_dev (struct usb_device *usb_dev)
{
DBGFUNC ("enter hci_free_dev\n");
if (usb_dev->hcpriv)
kfree (usb_dev->hcpriv);
usb_dev->hcpriv = NULL;
return 0;
}
/***************************************************************************
* Function Name : hci_get_current_frame_number
*
* This function get the current USB frame number
*
* Input: usb_dev = pointer to the usb device
*
* Return: frame number
**************************************************************************/
static int hci_get_current_frame_number (struct usb_device *usb_dev)
{
hci_t *hci = usb_dev->bus->hcpriv;
DBGFUNC ("enter hci_get_current_frame_number, frame = 0x%x \r\n",
hci->frame_number);
return (hci->frame_number);
}
/***************************************************************************
* List of all io-functions
**************************************************************************/
static struct usb_operations hci_device_operations = {
allocate: hci_alloc_dev,
deallocate: hci_free_dev,
get_frame_number: hci_get_current_frame_number,
submit_urb: hci_submit_urb,
unlink_urb: hci_unlink_urb,
};
/***************************************************************************
* URB queueing:
*
* For each type of transfer (INTR, BULK, ISO, CTRL) there is a list of
* active URBs.
* (hci->intr_list, hci->bulk_list, hci->iso_list, hci->ctrl_list)
* For every endpoint the head URB of the queued URBs is linked to one of
* those lists.
*
* The rest of the queued URBs of an endpoint are linked into a
* private URB list for each endpoint. (hci_dev->ed [endpoint_io].urb_queue)
* hci_dev->ed [endpoint_io].pipe_head .. points to the head URB which is
* in one of the active URB lists.
*
* The index of an endpoint consists of its number and its direction.
*
* The state of an intr and iso URB is 0.
* For ctrl URBs the states are US_CTRL_SETUP, US_CTRL_DATA, US_CTRL_ACK
* Bulk URBs states are US_BULK and US_BULK0 (with 0-len packet)
*
**************************************************************************/
/***************************************************************************
* Function Name : qu_urb_timeout
*
* This function is called when the URB timeout. The function unlinks the
* URB.
*
* Input: lurb: URB
*
* Return: none
**************************************************************************/
#ifdef HC_URB_TIMEOUT
static void qu_urb_timeout (unsigned long lurb)
{
struct urb *urb = (struct urb *) lurb;
DBGFUNC ("enter qu_urb_timeout\n");
urb->transfer_flags |= USB_TIMEOUT_KILLED;
hci_unlink_urb (urb);
}
#endif
/***************************************************************************
* Function Name : qu_pipeindex
*
* This function gets the index of the pipe.
*
* Input: pipe: the urb pipe
*
* Return: index
**************************************************************************/
static inline int qu_pipeindex (__u32 pipe)
{
DBGFUNC ("enter qu_pipeindex\n");
return (usb_pipeendpoint (pipe) << 1) | (usb_pipecontrol (pipe) ? 0 : usb_pipeout (pipe));
}
/***************************************************************************
* Function Name : qu_seturbstate
*
* This function set the state of the URB.
*
* control pipe: 3 states -- Setup, data, status
* interrupt and bulk pipe: 1 state -- data
*
* Input: urb = USB request block data structure
* state = the urb state
*
* Return: none
**************************************************************************/
static inline void qu_seturbstate (struct urb * urb, int state)
{
DBGFUNC ("enter qu_seturbstate\n");
urb->pipe &= ~0x1f;
urb->pipe |= state & 0x1f;
}
/***************************************************************************
* Function Name : qu_urbstate
*
* This function get the current state of the URB.
*
* Input: urb = USB request block data structure
*
* Return: none
**************************************************************************/
static inline int qu_urbstate (struct urb * urb)
{
DBGFUNC ("enter qu_urbstate\n");
return urb->pipe & 0x1f;
}
/***************************************************************************
* Function Name : qu_queue_active_urb
*
* This function adds the urb to the appropriate active urb list and set
* the urb state.
*
* There are four active lists: isochoronous list, interrupt list,
* control list, and bulk list.
*
* Input: hci = data structure for the host controller
* urb = USB request block data structure
* ed = endpoint descriptor
*
* Return: none
**************************************************************************/
static inline void qu_queue_active_urb (hci_t * hci, struct urb * urb, epd_t * ed)
{
int urb_state = 0;
DBGFUNC ("enter qu_queue_active_urb\n");
switch (usb_pipetype (urb->pipe)) {
case PIPE_CONTROL:
list_add (&urb->urb_list, &hci->ctrl_list);
urb_state = US_CTRL_SETUP;
break;
case PIPE_BULK:
list_add (&urb->urb_list, &hci->bulk_list);
if ((urb->transfer_flags & USB_ZERO_PACKET)
&& urb->transfer_buffer_length > 0
&&
((urb->transfer_buffer_length %
usb_maxpacket (urb->dev, urb->pipe,
usb_pipeout (urb->pipe))) == 0)) {
urb_state = US_BULK0;
}
break;
case PIPE_INTERRUPT:
urb->start_frame = hci->frame_number;
list_add (&urb->urb_list, &hci->intr_list);
break;
case PIPE_ISOCHRONOUS:
list_add (&urb->urb_list, &hci->iso_list);
break;
}
#ifdef HC_URB_TIMEOUT
if (urb->timeout) {
ed->timeout.data = (unsigned long) urb;
ed->timeout.expires = urb->timeout + jiffies;
ed->timeout.function = qu_urb_timeout;
add_timer (&ed->timeout);
}
#endif
qu_seturbstate (urb, urb_state);
}
/***************************************************************************
* Function Name : qu_queue_urb
*
* This function adds the urb to the endpoint descriptor list
*
* Input: hci = data structure for the host controller
* urb = USB request block data structure
*
* Return: none
**************************************************************************/
static int qu_queue_urb (hci_t * hci, struct urb * urb)
{
struct hci_device *hci_dev = usb_to_hci (urb->dev);
epd_t *ed = &hci_dev->ed[qu_pipeindex (urb->pipe)];
DBGFUNC ("Enter qu_queue_urb\n");
/* for ISOC transfers calculate start frame index */
if (usb_pipeisoc (urb->pipe) && urb->transfer_flags & USB_ISO_ASAP) {
urb->start_frame = ((ed->pipe_head) ? (ed->last_iso + 1) : hci_get_current_frame_number (urb-> dev) + 1) & 0xffff;
}
if (ed->pipe_head) {
__list_add (&urb->urb_list, ed->urb_queue.prev,
&(ed->urb_queue));
} else {
ed->pipe_head = urb;
qu_queue_active_urb (hci, urb, ed);
if (++hci->active_urbs == 1)
hc_start_int (hci);
}
return 0;
}
/***************************************************************************
* Function Name : qu_next_urb
*
* This function removes the URB from the queue and add the next URB to
* active list.
*
* Input: hci = data structure for the host controller
* urb = USB request block data structure
* resub_ok = resubmit flag
*
* Return: pointer to the next urb
**************************************************************************/
static struct urb *qu_next_urb (hci_t * hci, struct urb * urb, int resub_ok)
{
struct hci_device *hci_dev = usb_to_hci (urb->dev);
epd_t *ed = &hci_dev->ed[qu_pipeindex (urb->pipe)];
DBGFUNC ("enter qu_next_urb\n");
list_del (&urb->urb_list);
INIT_LIST_HEAD (&urb->urb_list);
if (ed->pipe_head == urb) {
#ifdef HC_URB_TIMEOUT
if (urb->timeout)
del_timer (&ed->timeout);
#endif
if (!--hci->active_urbs)
hc_stop_int (hci);
if (!list_empty (&ed->urb_queue)) {
urb = list_entry (ed->urb_queue.next, struct urb, urb_list);
list_del (&urb->urb_list);
INIT_LIST_HEAD (&urb->urb_list);
ed->pipe_head = urb;
qu_queue_active_urb (hci, urb, ed);
} else {
ed->pipe_head = NULL;
urb = NULL;
}
}
return urb;
}
/***************************************************************************
* Function Name : qu_return_urb
*
* This function is part of the return path.
*
* Input: hci = data structure for the host controller
* urb = USB request block data structure
* resub_ok = resubmit flag
*
* Return: pointer to the next urb
**************************************************************************/
static struct urb *qu_return_urb (hci_t * hci, struct urb * urb, int resub_ok)
{
struct urb *next_urb;
DBGFUNC ("enter qu_return_rub\n");
next_urb = qu_next_urb (hci, urb, resub_ok);
hcs_return_urb (hci, urb, resub_ok);
return next_urb;
}
/***************************************************************************
* Function Name : sh_scan_iso_urb_list
*
* This function goes throught the isochronous urb list and schedule the
* the transfer.
*
* Note: This function has not tested yet
*
* Input: hci = data structure for the host controller
* list_lh = pointer to the isochronous list
* frame_number = the frame number
*
* Return: 0 = unsuccessful; 1 = successful
**************************************************************************/
static int sh_scan_iso_urb_list (hci_t * hci, struct list_head *list_lh,
int frame_number)
{
struct list_head *lh = list_lh->next;
struct urb *urb;
DBGFUNC ("enter sh_scan_iso_urb_list\n");
hci->td_array->len = 0;
while (lh != list_lh) {
urb = list_entry (lh, struct urb, urb_list);
lh = lh->next;
if (((frame_number - urb->start_frame) & 0x7ff) <
urb->number_of_packets) {
if (!sh_add_packet (hci, urb)) {
return 0;
} else {
if (((frame_number -
urb->start_frame) & 0x7ff) > 0x400) {
if (qu_urbstate (urb) > 0)
urb = qu_return_urb (hci, urb, 1);
else
urb = qu_next_urb (hci, urb, 1);
if (lh == list_lh && urb)
lh = &urb->urb_list;
}
}
}
}
return 1;
}
/***************************************************************************
* Function Name : sh_scan_urb_list
*
* This function goes through the urb list and schedule the
* the transaction.
*
* Input: hci = data structure for the host controller
* list_lh = pointer to the isochronous list
*
* Return: 0 = unsuccessful; 1 = successful
**************************************************************************/
static int sh_scan_urb_list (hci_t * hci, struct list_head *list_lh)
{
struct list_head *lh = NULL;
struct urb *urb;
if (list_lh == NULL) {
DBGERR ("sh_scan_urb_list: error, list_lh == NULL\n");
}
DBGFUNC ("enter sh_scan_urb_list: frame# \n");
list_for_each (lh, list_lh) {
urb = list_entry (lh, struct urb, urb_list);
if (urb == NULL)
return 1;
if (!usb_pipeint (urb->pipe)
|| (((hci->frame_number - urb->start_frame)
& 0x7ff) >= urb->interval)) {
DBGVERBOSE ("sh_scan_urb_list !INT: %d fr_no: %d int: %d pint: %d\n",
urb->start_frame, hci->frame_number, urb->interval,
usb_pipeint (urb->pipe));
if (!sh_add_packet (hci, urb)) {
return 0;
} else {
DBGVERBOSE ("INT: start: %d fr_no: %d int: %d pint: %d\n",
urb->start_frame, hci->frame_number,
urb->interval, usb_pipeint (urb->pipe));
urb->start_frame = hci->frame_number;
return 0;
}
}
}
return 1;
}
/***************************************************************************
* Function Name : sh_shedule_trans
*
* This function schedule the USB transaction.
* This function will process the endpoint in the following order:
* interrupt, control, and bulk.
*
* Input: hci = data structure for the host controller
* isSOF = flag indicate if Start Of Frame has occurred
*
* Return: 0
**************************************************************************/
static int sh_schedule_trans (hci_t * hci, int isSOF)
{
int units_left = 1;
struct list_head *lh;
if (hci == NULL) {
DBGERR ("sh_schedule_trans: hci == NULL\n");
return 0;
}
if (hci->td_array == NULL) {
DBGERR ("sh_schedule_trans: hci->td_array == NULL\n");
return 0;
}
if (hci->td_array->len != 0) {
DBGERR ("ERROR: schedule, hci->td_array->len = 0x%x, s/b: 0\n",
hci->td_array->len);
}
/* schedule the next available interrupt transfer or the next
* stage of the interrupt transfer */
if (hci->td_array->len == 0 && !list_empty (&hci->intr_list)) {
units_left = sh_scan_urb_list (hci, &hci->intr_list);
}
/* schedule the next available control transfer or the next
* stage of the control transfer */
if (hci->td_array->len == 0 && !list_empty (&hci->ctrl_list) && units_left > 0) {
units_left = sh_scan_urb_list (hci, &hci->ctrl_list);
}
/* schedule the next available bulk transfer or the next
* stage of the bulk transfer */
if (hci->td_array->len == 0 && !list_empty (&hci->bulk_list) && units_left > 0) {
sh_scan_urb_list (hci, &hci->bulk_list);
/* be fair to each BULK URB (move list head around)
* only when the new SOF happens */
lh = hci->bulk_list.next;
list_del (&hci->bulk_list);
list_add (&hci->bulk_list, lh);
}
return 0;
}
/***************************************************************************
* Function Name : sh_add_packet
*
* This function forms the packet and transmit the packet. This function
* will handle all endpoint type: isochoronus, interrupt, control, and
* bulk.
*
* Input: hci = data structure for the host controller
* urb = USB request block data structure
*
* Return: 0 = unsucessful; 1 = successful
**************************************************************************/
static int sh_add_packet (hci_t * hci, struct urb * urb)
{
__u8 *data = NULL;
int len = 0;
int toggle = 0;
int maxps = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe));
int endpoint = usb_pipeendpoint (urb->pipe);
int address = usb_pipedevice (urb->pipe);
int slow = (((urb->pipe) >> 26) & 1);
int out = usb_pipeout (urb->pipe);
int pid = 0;
int ret;
int i = 0;
int iso = 0;
DBGFUNC ("enter sh_add_packet\n");
if (maxps == 0)
maxps = 8;
/* calculate len, toggle bit and add the transaction */
switch (usb_pipetype (urb->pipe)) {
case PIPE_ISOCHRONOUS:
pid = out ? PID_OUT : PID_IN;
iso = 1;
i = hci->frame_number - urb->start_frame;
data = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
len = urb->iso_frame_desc[i].length;
break;
case PIPE_BULK: /* BULK and BULK0 */
case PIPE_INTERRUPT:
pid = out ? PID_OUT : PID_IN;
len = urb->transfer_buffer_length - urb->actual_length;
data = urb->transfer_buffer + urb->actual_length;
toggle = usb_gettoggle (urb->dev, endpoint, out);
break;
case PIPE_CONTROL:
switch (qu_urbstate (urb)) {
case US_CTRL_SETUP:
len = 8;
pid = PID_SETUP;
data = urb->setup_packet;
toggle = 0;
break;
case US_CTRL_DATA:
if (!hci->last_packet_nak) {
/* The last packet received is not a nak:
* reset the nak count
*/
hci->nakCnt = 0;
}
if (urb->transfer_buffer_length != 0) {
pid = out ? PID_OUT : PID_IN;
len = urb->transfer_buffer_length - urb->actual_length;
data = urb->transfer_buffer + urb->actual_length;
toggle = (urb->actual_length & maxps) ? 0 : 1;
usb_settoggle (urb->dev,
usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe), toggle);
break;
} else {
/* correct state and fall through */
qu_seturbstate (urb, US_CTRL_ACK);
}
case US_CTRL_ACK:
len = 0;
/* reply in opposite direction */
pid = !out ? PID_OUT : PID_IN;
toggle = 1;
usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe), toggle);
break;
}
}
ret =
hc_add_trans (hci, len, data, toggle, maxps, slow, endpoint,
address, pid, iso, qu_urbstate (urb));
DBGVERBOSE ("transfer_pa: addr:%d ep:%d pid:%x tog:%x iso:%x sl:%x "
"max:%d\n len:%d ret:%d data:%p left:%d\n",
address, endpoint, pid, toggle, iso, slow,
maxps, len, ret, data, hci->hp.units_left);
if (ret >= 0) {
hci->td_array->td[hci->td_array->len].urb = urb;
hci->td_array->td[hci->td_array->len].len = ret;
hci->td_array->td[hci->td_array->len].iso_index = i;
hci->td_array->len++;
hci->active_trans = 1;
return 1;
}
return 0;
}
/***************************************************************************
* Function Name : cc_to_error
*
* This function maps the SL811HS hardware error code to the linux USB error
* code.
*
* Input: cc = hardware error code
*
* Return: USB error code
**************************************************************************/
static int cc_to_error (int cc)
{
int errCode = 0;
if (cc & SL11H_STATMASK_ERROR) {
errCode |= -EILSEQ;
} else if (cc & SL11H_STATMASK_OVF) {
errCode |= -EOVERFLOW;
} else if (cc & SL11H_STATMASK_STALL) {
errCode |= -EPIPE;
}
return errCode;
}
/***************************************************************************
* Function Name : sh_done_list
*
* This function process the packet when it has done finish transfer.
*
* 1) It handles hardware error
* 2) It updates the URB state
* 3) If the USB transaction is complete, it start the return stack path.
*
* Input: hci = data structure for the host controller
* isExcessNak = flag tells if there excess NAK condition occurred
*
* Return: urb_state or -1 if the transaction has complete
**************************************************************************/
static int sh_done_list (hci_t * hci, int *isExcessNak)
{
int actbytes = 0;
int active = 0;
void *data = NULL;
int cc;
int maxps;
int toggle;
struct urb *urb;
int urb_state = 0;
int ret = 1; /* -1 parse abbort, 1 parse ok, 0 last element */
int trans = 0;
int len;
int iso_index = 0;
int out;
int pid = 0;
int debugLen = 0;
*isExcessNak = 0;
DBGFUNC ("enter sh_done_list: td_array->len = 0x%x\n",
hci->td_array->len);
debugLen = hci->td_array->len;
if (debugLen > 1)
DBGERR ("sh_done_list: td_array->len = 0x%x > 1\n",
hci->td_array->len);
for (trans = 0; ret && trans < hci->td_array->len && trans < MAX_TRANS;
trans++) {
urb = hci->td_array->td[trans].urb;
len = hci->td_array->td[trans].len;
out = usb_pipeout (urb->pipe);
if (usb_pipeisoc (urb->pipe)) {
iso_index = hci->td_array->td[trans].iso_index;
data = urb->transfer_buffer + urb->iso_frame_desc[iso_index].offset;
toggle = 0;
} else {
data = urb->transfer_buffer + urb->actual_length;
toggle = usb_gettoggle (urb->dev,
usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe));
}
urb_state = qu_urbstate (urb);
pid = out ? PID_OUT : PID_IN;
ret = hc_parse_trans (hci, &actbytes, data, &cc, &toggle, len,
pid, urb_state);
maxps = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe));
if (maxps == 0)
maxps = 8;
active = (urb_state != US_CTRL_SETUP) && (actbytes && !(actbytes & (maxps - 1)));
/* If the transfer is not bulk in, then it is necessary to get all
* data specify by the urb->transfer_len.
*/
if (!(usb_pipebulk (urb->pipe) && usb_pipein (urb->pipe)))
active = active && (urb->transfer_buffer_length != urb->actual_length + actbytes);
if (urb->transfer_buffer_length == urb->actual_length + actbytes)
active = 0;
if ((cc &
(SL11H_STATMASK_ERROR | SL11H_STATMASK_TMOUT |
SL11H_STATMASK_OVF | SL11H_STATMASK_STALL))
&& !(cc & SL11H_STATMASK_NAK)) {
if (++urb->error_count > 3) {
DBGERR ("done_list: excessive error: errcount = 0x%x, cc = 0x%x\n",
urb->error_count, cc);
urb_state = 0;
active = 0;
} else {
DBGERR ("done_list: packet err, cc = 0x%x, "
" urb->length = 0x%x, actual_len = 0x%x,"
" urb_state =0x%x\n",
cc, urb->transfer_buffer_length,
urb->actual_length, urb_state);
// if (cc & SL11H_STATMASK_STALL) {
/* The USB function is STALLED on a control pipe (0),
* then it needs to send the SETUP command again to
* clear the STALL condition
*/
// if (usb_pipeendpoint (urb->pipe) == 0) {
// urb_state = 2;
// active = 0;
// }
// } else
active = 1;
}
} else {
if (cc & SL11H_STATMASK_NAK) {
if (hci->nakCnt < 0x10000) {
hci->nakCnt++;
hci->last_packet_nak = 1;
active = 1;
*isExcessNak = 0;
} else {
DBGERR ("done_list: nak count exceed limit\n");
active = 0;
*isExcessNak = 1;
hci->nakCnt = 0;
}
} else {
hci->nakCnt = 0;
hci->last_packet_nak = 0;
}
if (urb_state != US_CTRL_SETUP) {
/* no error */
urb->actual_length += actbytes;
usb_settoggle (urb->dev,
usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe), toggle);
}
if (usb_pipeisoc (urb->pipe)) {
urb->iso_frame_desc[iso_index].actual_length = actbytes;
urb->iso_frame_desc[iso_index].status = cc_to_error (cc);
active = (iso_index < urb->number_of_packets);
}
}
if (!active) {
if (!urb_state) {
urb->status = cc_to_error (cc);
if (urb->status) {
DBGERR ("error on received packet: urb->status = 0x%x\n",
urb->status);
}
hci->td_array->len = 0;
qu_return_urb (hci, urb, 1);
return -1;
} else {
/* We do not want to decrement the urb_state if exceeded nak,
* because we need to finish the data stage of the control
* packet
*/
if (!(*isExcessNak))
urb_state--;
qu_seturbstate (urb, urb_state);
}
}
}
if (urb_state < 0)
DBGERR ("ERROR: done_list, urb_state = %d, suppose > 0\n",
urb_state);
if (debugLen != hci->td_array->len) {
DBGERR ("ERROR: done_list, debugLen!= td_array->len,"
"debugLen = 0x%x, hci->td_array->len = 0x%x\n",
debugLen, hci->td_array->len);
}
hci->td_array->len = 0;
return urb_state;
}
/*-------------------------------------------------------------------------*/
/* list of all controllers using this driver
* */
static LIST_HEAD (hci_hcd_list);
/* URB states (urb_state) */
/* isoc, interrupt single state */
/* bulk transfer main state and 0-length packet */
#define US_BULK 0
#define US_BULK0 1
/* three setup states */
#define US_CTRL_SETUP 2
#define US_CTRL_DATA 1
#define US_CTRL_ACK 0
/*-------------------------------------------------------------------------*/
/* HC private part of a device descriptor
* */
#define NUM_EDS 32
typedef struct epd {
struct urb *pipe_head;
struct list_head urb_queue;
// int urb_state;
struct timer_list timeout;
int last_iso; /* timestamp of last queued ISOC transfer */
} epd_t;
struct hci_device {
epd_t ed[NUM_EDS];
};
/*-------------------------------------------------------------------------*/
/* Virtual Root HUB
*/
#define usb_to_hci(usb) ((struct hci_device *)(usb)->hcpriv)
struct virt_root_hub {
int devnum; /* Address of Root Hub endpoint */
void *urb; /* interrupt URB of root hub */
int send; /* active flag */
int interval; /* intervall of roothub interrupt transfers */
struct timer_list rh_int_timer; /* intervall timer for rh interrupt EP */
};
#if 1
/* USB HUB CONSTANTS (not OHCI-specific; see hub.h and USB spec) */
/* destination of request */
#define RH_INTERFACE 0x01
#define RH_ENDPOINT 0x02
#define RH_OTHER 0x03
#define RH_CLASS 0x20
#define RH_VENDOR 0x40
/* Requests: bRequest << 8 | bmRequestType */
#define RH_GET_STATUS 0x0080
#define RH_CLEAR_FEATURE 0x0100
#define RH_SET_FEATURE 0x0300
#define RH_SET_ADDRESS 0x0500
#define RH_GET_DESCRIPTOR 0x0680
#define RH_SET_DESCRIPTOR 0x0700
#define RH_GET_CONFIGURATION 0x0880
#define RH_SET_CONFIGURATION 0x0900
#define RH_GET_STATE 0x0280
#define RH_GET_INTERFACE 0x0A80
#define RH_SET_INTERFACE 0x0B00
#define RH_SYNC_FRAME 0x0C80
/* Our Vendor Specific Request */
#define RH_SET_EP 0x2000
/* Hub port features */
#define RH_PORT_CONNECTION 0x00
#define RH_PORT_ENABLE 0x01
#define RH_PORT_SUSPEND 0x02
#define RH_PORT_OVER_CURRENT 0x03
#define RH_PORT_RESET 0x04
#define RH_PORT_POWER 0x08
#define RH_PORT_LOW_SPEED 0x09
#define RH_C_PORT_CONNECTION 0x10
#define RH_C_PORT_ENABLE 0x11
#define RH_C_PORT_SUSPEND 0x12
#define RH_C_PORT_OVER_CURRENT 0x13
#define RH_C_PORT_RESET 0x14
/* Hub features */
#define RH_C_HUB_LOCAL_POWER 0x00
#define RH_C_HUB_OVER_CURRENT 0x01
#define RH_DEVICE_REMOTE_WAKEUP 0x00
#define RH_ENDPOINT_STALL 0x01
#endif
/*-------------------------------------------------------------------------*/
/* struct for each HC
*/
#define MAX_TRANS 32
typedef struct td {
struct urb *urb;
__u16 len;
__u16 iso_index;
} td_t;
typedef struct td_array {
int len;
td_t td[MAX_TRANS];
} td_array_t;
typedef struct hci {
struct virt_root_hub rh; /* roothub */
wait_queue_head_t waitq; /* deletion of URBs and devices needs a waitqueue */
int active; /* HC is operating */
struct list_head ctrl_list; /* set of ctrl endpoints */
struct list_head bulk_list; /* set of bulk endpoints */
struct list_head iso_list; /* set of isoc endpoints */
struct list_head intr_list; /* ordered (tree) set of int endpoints */
struct list_head del_list; /* set of entpoints to be deleted */
td_array_t *td_array;
td_array_t a_td_array;
td_array_t i_td_array[2];
struct list_head hci_hcd_list; /* list of all hci_hcd */
struct usb_bus *bus; /* our bus */
// int trans; /* number of transactions pending */
int active_urbs;
int active_trans;
int frame_number; /* frame number */
hcipriv_t hp; /* individual part of hc type */
int nakCnt;
int last_packet_nak;
} hci_t;
/*-------------------------------------------------------------------------*/
/* condition (error) CC codes and mapping OHCI like
*/
#define TD_CC_NOERROR 0x00
#define TD_CC_CRC 0x01
#define TD_CC_BITSTUFFING 0x02
#define TD_CC_DATATOGGLEM 0x03
#define TD_CC_STALL 0x04
#define TD_DEVNOTRESP 0x05
#define TD_PIDCHECKFAIL 0x06
#define TD_UNEXPECTEDPID 0x07
#define TD_DATAOVERRUN 0x08
#define TD_DATAUNDERRUN 0x09
#define TD_BUFFEROVERRUN 0x0C
#define TD_BUFFERUNDERRUN 0x0D
#define TD_NOTACCESSED 0x0F
/* urb interface functions */
static int hci_get_current_frame_number (struct usb_device *usb_dev);
static int hci_unlink_urb (struct urb * urb);
static int qu_queue_urb (hci_t * hci, struct urb * urb);
/* root hub */
static int rh_init_int_timer (struct urb * urb);
static int rh_submit_urb (struct urb * urb);
static int rh_unlink_urb (struct urb * urb);
/* schedule functions */
static int sh_add_packet (hci_t * hci, struct urb * urb);
/* hc specific functions */
static inline void hc_flush_data_cache (hci_t * hci, void *data, int len);
static inline int hc_parse_trans (hci_t * hci, int *actbytes, __u8 * data,
int *cc, int *toggle, int length, int pid,
int urb_state);
static inline int hc_add_trans (hci_t * hci, int len, void *data, int toggle,
int maxps, int slow, int endpoint, int address,
int pid, int format, int urb_state);
static void hc_start_int (hci_t * hci);
static void hc_stop_int (hci_t * hci);
static void SL811Write (hci_t * hci, char offset, char data);
/* debug| print the main components of an URB
* small: 0) header + data packets 1) just header */
static void urb_print (struct urb * urb, char *str, int small)
{
unsigned int pipe = urb->pipe;
int i, len;
if (!urb->dev || !urb->dev->bus) {
dbg ("%s URB: no dev", str);
return;
}
printk ("%s URB:[%4x] dev:%2d,ep:%2d-%c,type:%s,flags:%4x,len:%d/%d,stat:%d(%x)\n",
str, hci_get_current_frame_number (urb->dev),
usb_pipedevice (pipe), usb_pipeendpoint (pipe),
usb_pipeout (pipe) ? 'O' : 'I',
usb_pipetype (pipe) < 2 ? (usb_pipeint (pipe) ? "INTR" : "ISOC")
: (usb_pipecontrol (pipe) ? "CTRL" : "BULK"), urb->transfer_flags,
urb->actual_length, urb->transfer_buffer_length, urb->status,
urb->status);
if (!small) {
if (usb_pipecontrol (pipe)) {
printk (__FILE__ ": cmd(8):");
for (i = 0; i < 8; i++)
printk (" %02x", ((__u8 *) urb->setup_packet)[i]);
printk ("\n");
}
if (urb->transfer_buffer_length > 0 && urb->transfer_buffer) {
printk (__FILE__ ": data(%d/%d):", urb->actual_length,
urb->transfer_buffer_length);
len = usb_pipeout (pipe) ? urb-> transfer_buffer_length : urb->actual_length;
for (i = 0; i < 2096 && i < len; i++)
printk (" %02x", ((__u8 *) urb->transfer_buffer)[i]);
printk ("%s stat:%d\n", i < len ? "..." : "",
urb->status);
}
}
}
/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*
* SL811HS USB HCD for Linux Version 0.1 (10/28/2001)
*
* requires (includes) hc_simple.[hc] simple generic HCD frontend
*
* COPYRIGHT(C) 2001 by CYPRESS SEMICONDUCTOR INC.
*
*-------------------------------------------------------------------------*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*-------------------------------------------------------------------------*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/malloc.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <linux/list.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <linux/usb.h>
#include "../core/hcd.h"
#undef HC_URB_TIMEOUT
#undef HC_SWITCH_INT
#undef HC_ENABLE_ISOC
#define SL811_DEBUG_ERR
#ifdef SL811_DEBUG_ERR
#define DBGERR(fmt, args...) printk(fmt,## args)
#else
#define DBGERR(fmt, args...)
#endif
#ifdef SL811_DEBUG
#define DBG(fmt, args...) printk(fmt,## args)
#else
#define DBG(fmt, args...)
#endif
#ifdef SL811_DEBUG_FUNC
#define DBGFUNC(fmt, args...) printk(fmt,## args)
#else
#define DBGFUNC(fmt, args...)
#endif
#ifdef SL811_DEBUG_DATA
#define DBGDATAR(fmt, args...) printk(fmt,## args)
#define DBGDATAW(fmt, args...) printk(fmt,## args)
#else
#define DBGDATAR(fmt, args...)
#define DBGDATAW(fmt, args...)
#endif
#ifdef SL811_DEBUG_VERBOSE
#define DBGVERBOSE(fmt, args...) printk(fmt,## args)
#else
#define DBGVERBOSE(fmt, args...)
#endif
#define TRUE 1
#define FALSE 0
#define HC_SWITCH_INT
#include "hc_sl811.h"
#include "hc_simple.h"
static int urb_debug = 0;
#include "hc_simple.c"
#include "hc_sl811_rh.c"
/* The base_addr, data_reg_addr, and irq number are board specific.
* The current values are design to run on the Accelent SA1110 IDP
* NOTE: values need to modify for different development boards
*/
static int base_addr = 0xd3800000;
static int data_reg_addr = 0xd3810000;
static int irq = 34;
/* forware declaration */
int SL11StartXaction (hci_t * hci, __u8 addr, __u8 epaddr, int pid, int len,
int toggle, int slow, int urb_state);
static int sofWaitCnt = 0;
MODULE_PARM (urb_debug, "i");
MODULE_PARM_DESC (urb_debug, "debug urb messages, default is 0 (no)");
MODULE_PARM (base_addr, "i");
MODULE_PARM_DESC (base_addr, "sl811 base address 0xd3800000");
MODULE_PARM (data_reg_addr, "i");
MODULE_PARM_DESC (data_reg_addr, "sl811 data register address 0xd3810000");
MODULE_PARM (irq, "i");
MODULE_PARM_DESC (irq, "IRQ 34 (default)");
static int hc_reset (hci_t * hci);
/***************************************************************************
* Function Name : SL811Read
*
* Read a byte of data from the SL811H/SL11H
*
* Input: hci = data structure for the host controller
* offset = address of SL811/SL11H register or memory
*
* Return: data
**************************************************************************/
char SL811Read (hci_t * hci, char offset)
{
hcipriv_t *hp = &hci->hp;
char data;
writeb (offset, hp->hcport);
wmb ();
data = readb (hp->hcport2);
rmb ();
return (data);
}
/***************************************************************************
* Function Name : SL811Write
*
* Write a byte of data to the SL811H/SL11H
*
* Input: hci = data structure for the host controller
* offset = address of SL811/SL11H register or memory
* data = the data going to write to SL811H
*
* Return: none
**************************************************************************/
void SL811Write (hci_t * hci, char offset, char data)
{
hcipriv_t *hp = &hci->hp;
writeb (offset, hp->hcport);
writeb (data, hp->hcport2);
wmb ();
}
/***************************************************************************
* Function Name : SL811BufRead
*
* Read consecutive bytes of data from the SL811H/SL11H buffer
*
* Input: hci = data structure for the host controller
* offset = SL811/SL11H register offset
* buf = the buffer where the data will store
* size = number of bytes to read
*
* Return: none
**************************************************************************/
void SL811BufRead (hci_t * hci, short offset, char *buf, short size)
{
hcipriv_t *hp = &hci->hp;
if (size <= 0)
return;
writeb ((char) offset, hp->hcport);
wmb ();
DBGDATAR ("SL811BufRead: offset = 0x%x, data = ", offset);
while (size--) {
*buf++ = (char) readb (hp->hcport2);
DBGDATAR ("0x%x ", *(buf - 1));
rmb ();
}
DBGDATAR ("\n");
}
/***************************************************************************
* Function Name : SL811BufWrite
*
* Write consecutive bytes of data to the SL811H/SL11H buffer
*
* Input: hci = data structure for the host controller
* offset = SL811/SL11H register offset
* buf = the data buffer
* size = number of bytes to write
*
* Return: none
**************************************************************************/
void SL811BufWrite (hci_t * hci, short offset, char *buf, short size)
{
hcipriv_t *hp = &hci->hp;
if (size <= 0)
return;
writeb ((char) offset, hp->hcport);
wmb ();
DBGDATAW ("SL811BufWrite: offset = 0x%x, data = ", offset);
while (size--) {
DBGDATAW ("0x%x ", *buf);
writeb (*buf, hp->hcport2);
wmb ();
buf++;
}
DBGDATAW ("\n");
}
/***************************************************************************
* Function Name : regTest
*
* This routine test the Read/Write functionality of SL811HS registers
*
* 1) Store original register value into a buffer
* 2) Write to registers with a RAMP pattern. (10, 11, 12, ..., 255)
* 3) Read from register
* 4) Compare the written value with the read value and make sure they are
* equivalent
* 5) Restore the original register value
*
* Input: hci = data structure for the host controller
*
*
* Return: TRUE = passed; FALSE = failed
**************************************************************************/
int regTest (hci_t * hci)
{
int i, data, result = TRUE;
char buf[256];
DBGFUNC ("Enter regTest\n");
for (i = 0x10; i < 256; i++) {
/* save the original buffer */
buf[i] = (char) SL811Read (hci, i);
/* Write the new data to the buffer */
SL811Write (hci, i, i);
}
/* compare the written data */
for (i = 0x10; i < 256; i++) {
data = SL811Read (hci, i);
if (data != i) {
DBGERR ("Pattern test failed!! value = 0x%x, s/b 0x%x\n",
data, i);
result = FALSE;
}
}
/* restore the data */
for (i = 0x10; i < 256; i++) {
SL811Write (hci, i, buf[i]);
}
return (result);
}
/***************************************************************************
* Function Name : regShow
*
* Display all SL811HS register values
*
* Input: hci = data structure for the host controller
*
* Return: none
**************************************************************************/
void regShow (hci_t * hci)
{
int i;
for (i = 0; i < 256; i++) {
printk ("offset %d: 0x%x\n", i, SL811Read (hci, i));
}
}
/************************************************************************
* Function Name : USBReset
*
* This function resets SL811HS controller and detects the speed of
* the connecting device
*
* Input: hci = data structure for the host controller
*
* Return: 0 = no device attached; 1 = USB device attached
*
***********************************************************************/
static int USBReset (hci_t * hci)
{
int status;
hcipriv_t *hp = &hci->hp;
DBGFUNC ("enter USBReset\n");
SL811Write (hci, SL11H_CTLREG2, 0xae);
// setup master and full speed
SL811Write (hci, SL11H_CTLREG1, 0x08); // reset USB
mdelay (20); // 20ms
SL811Write (hci, SL11H_CTLREG1, 0); // remove SE0
for (status = 0; status < 100; status++)
SL811Write (hci, SL11H_INTSTATREG, 0xff); // clear all interrupt bits
status = SL811Read (hci, SL11H_INTSTATREG);
if (status & 0x40) // Check if device is removed
{
DBG ("USBReset: Device removed\n");
SL811Write (hci, SL11H_INTENBLREG,
SL11H_INTMASK_XFERDONE | SL11H_INTMASK_SOFINTR |
SL11H_INTMASK_INSRMV);
hp->RHportStatus->portStatus &=
~(PORT_CONNECT_STAT | PORT_ENABLE_STAT);
return 0;
}
SL811Write (hci, SL11H_BUFLNTHREG_B, 0); //zero lenth
SL811Write (hci, SL11H_PIDEPREG_B, 0x50); //send SOF to EP0
SL811Write (hci, SL11H_DEVADDRREG_B, 0x01); //address0
SL811Write (hci, SL11H_SOFLOWREG, 0xe0);
if (!(status & 0x80)) {
/* slow speed device connect directly to root-hub */
DBG ("USBReset: low speed Device attached\n");
SL811Write (hci, SL11H_CTLREG1, 0x8);
mdelay (20);
SL811Write (hci, SL11H_SOFTMRREG, 0xee);
SL811Write (hci, SL11H_CTLREG1, 0x21);
/* start the SOF or EOP */
SL811Write (hci, SL11H_HOSTCTLREG_B, 0x01);
hp->RHportStatus->portStatus |=
(PORT_CONNECT_STAT | PORT_LOW_SPEED_DEV_ATTACH_STAT);
/* clear all interrupt bits */
for (status = 0; status < 20; status++)
SL811Write (hci, SL11H_INTSTATREG, 0xff);
} else {
/* full speed device connect directly to root hub */
DBG ("USBReset: full speed Device attached\n");
SL811Write (hci, SL11H_CTLREG1, 0x8);
mdelay (20);
SL811Write (hci, SL11H_SOFTMRREG, 0xae);
SL811Write (hci, SL11H_CTLREG1, 0x01);
/* start the SOF or EOP */
SL811Write (hci, SL11H_HOSTCTLREG_B, 0x01);
hp->RHportStatus->portStatus |= (PORT_CONNECT_STAT);
hp->RHportStatus->portStatus &= ~PORT_LOW_SPEED_DEV_ATTACH_STAT;
/* clear all interrupt bits */
SL811Write (hci, SL11H_INTSTATREG, 0xff);
}
/* enable all interrupts */
SL811Write (hci, SL11H_INTENBLREG,
SL11H_INTMASK_XFERDONE | SL11H_INTMASK_SOFINTR |
SL11H_INTMASK_INSRMV);
return 1;
}
/*-------------------------------------------------------------------------*/
/* tl functions */
static inline void hc_mark_last_trans (hci_t * hci)
{
hcipriv_t *hp = &hci->hp;
__u8 *ptd = hp->tl;
dbg ("enter hc_mark_last_trans\n");
if (ptd == NULL) {
printk ("hc_mark_last_trans: ptd = null\n");
return;
}
if (hp->xferPktLen > 0)
*(ptd + hp->tl_last) |= (1 << 3);
}
static inline void hc_flush_data_cache (hci_t * hci, void *data, int len)
{
}
/************************************************************************
* Function Name : hc_add_trans
*
* This function sets up the SL811HS register and transmit the USB packets.
*
* 1) Determine if enough time within the current frame to send the packet
* 2) Load the data into the SL811HS register
* 3) Set the appropriate command to the register and trigger the transmit
*
* Input: hci = data structure for the host controller
* len = data length
* data = transmitting data
* toggle = USB toggle bit, either 0 or 1
* maxps = maximum packet size for this endpoint
* slow = speed of the device
* endpoint = endpoint number
* address = USB address of the device
* pid = packet ID
* format =
* urb_state = the current stage of USB transaction
*
* Return: 0 = no time left to schedule the transfer
* 1 = success
*
***********************************************************************/
static inline int hc_add_trans (hci_t * hci, int len, void *data, int toggle,
int maxps, int slow, int endpoint, int address,
int pid, int format, int urb_state)
{
hcipriv_t *hp = &hci->hp;
__u16 speed;
int ii, jj, kk;
DBGFUNC ("enter hc_addr_trans: len =0x%x, toggle:0x%x, endpoing:0x%x,"
" addr:0x%x, pid:0x%x,format:0x%x\n", len, toggle, endpoint,
i address, pid, format);
if (len > maxps) {
len = maxps;
}
speed = hp->RHportStatus->portStatus;
if (speed & PORT_LOW_SPEED_DEV_ATTACH_STAT) {
// ii = (8*7*8 + 6*3) * len + 800;
ii = 8 * 8 * len + 1024;
} else {
if (slow) {
// ii = (8*7*8 + 6*3) * len + 800;
ii = 8 * 8 * len + 2048;
} else
// ii = (8*7 + 6*3)*len + 110;
ii = 8 * len + 256;
}
ii += 2 * 10 * len;
jj = SL811Read (hci, SL11H_SOFTMRREG);
kk = (jj & 0xFF) * 64 - ii;
if (kk < 0) {
DBGVERBOSE
("hc_add_trans: no bandwidth for schedule, ii = 0x%x,"
"jj = 0x%x, len =0x%x, active_trans = 0x%x\n", ii, jj, len,
hci->active_trans);
return (-1);
}
if (pid != PID_IN) {
/* Load data into hc */
SL811BufWrite (hci, SL11H_DATA_START, (__u8 *) data, len);
}
/* transmit */
SL11StartXaction (hci, (__u8) address, (__u8) endpoint, (__u8) pid, len,
toggle, slow, urb_state);
return len;
}
/************************************************************************
* Function Name : hc_parse_trans
*
* This function checks the status of the transmitted or received packet
* and copy the data from the SL811HS register into a buffer.
*
* 1) Check the status of the packet
* 2) If successful, and IN packet then copy the data from the SL811HS register
* into a buffer
*
* Input: hci = data structure for the host controller
* actbytes = pointer to actual number of bytes
* data = data buffer
* cc = packet status
* length = the urb transmit length
* pid = packet ID
* urb_state = the current stage of USB transaction
*
* Return: 0
***********************************************************************/
static inline int hc_parse_trans (hci_t * hci, int *actbytes, __u8 * data,
int *cc, int *toggle, int length, int pid,
int urb_state)
{
__u8 addr;
__u8 len;
DBGFUNC ("enter hc_parse_trans\n");
/* get packet status; convert ack rcvd to ack-not-rcvd */
*cc = (int) SL811Read (hci, SL11H_PKTSTATREG);
if (*cc &
(SL11H_STATMASK_ERROR | SL11H_STATMASK_TMOUT | SL11H_STATMASK_OVF |
SL11H_STATMASK_NAK | SL11H_STATMASK_STALL)) {
if (*cc & SL11H_STATMASK_OVF)
DBGERR ("parse trans: error recv ack, cc = 0x%x, TX_BASE_Len = "
"0x%x, TX_count=0x%x\n", *cc,
SL811Read (hci, SL11H_BUFLNTHREG),
SL811Read (hci, SL11H_XFERCNTREG));
} else {
DBGVERBOSE ("parse trans: recv ack, cc = 0x%x, len = 0x%x, \n",
*cc, length);
/* Successful data */
if ((pid == PID_IN) && (urb_state != US_CTRL_SETUP)) {
/* Find the base address */
addr = SL811Read (hci, SL11H_BUFADDRREG);
/* Find the Transmit Length */
len = SL811Read (hci, SL11H_BUFLNTHREG);
/* The actual data length = xmit length reg - xfer count reg */
*actbytes = len - SL811Read (hci, SL11H_XFERCNTREG);
if ((data != NULL) && (*actbytes > 0)) {
SL811BufRead (hci, addr, data, *actbytes);
} else if ((data == NULL) && (*actbytes <= 0)) {
DBGERR ("hc_parse_trans: data = NULL or actbyte = 0x%x\n",
*actbytes);
return 0;
}
} else if (pid == PID_OUT) {
*actbytes = length;
} else {
// printk ("ERR:parse_trans, pid != IN or OUT, pid = 0x%x\n", pid);
}
*toggle = !*toggle;
}
return 0;
}
/************************************************************************
* Function Name : hc_start_int
*
* This function enables SL811HS interrupts
*
* Input: hci = data structure for the host controller
*
* Return: none
***********************************************************************/
static void hc_start_int (hci_t * hci)
{
#ifdef HC_SWITCH_INT
int mask =
SL11H_INTMASK_XFERDONE | SL11H_INTMASK_SOFINTR |
SL11H_INTMASK_INSRMV | SL11H_INTMASK_USBRESET;
SL811Write (hci, IntEna, mask);
#endif
}
/************************************************************************
* Function Name : hc_stop_int
*
* This function disables SL811HS interrupts
*
* Input: hci = data structure for the host controller
*
* Return: none
***********************************************************************/
static void hc_stop_int (hci_t * hci)
{
#ifdef HC_SWITCH_INT
SL811Write (hci, SL11H_INTSTATREG, 0xff);
// SL811Write(hci, SL11H_INTENBLREG, SL11H_INTMASK_INSRMV);
#endif
}
/************************************************************************
* Function Name : handleInsRmvIntr
*
* This function handles the insertion or removal of device on SL811HS.
* It resets the controller and updates the port status
*
* Input: hci = data structure for the host controller
*
* Return: none
***********************************************************************/
void handleInsRmvIntr (hci_t * hci)
{
hcipriv_t *hp = &hci->hp;
USBReset (hci);
/* Changes in connection status */
hp->RHportStatus->portChange |= PORT_CONNECT_CHANGE;
/* Port Enable or Disable */
if (hp->RHportStatus->portStatus & PORT_CONNECT_STAT) {
/* device is connected to the port:
* 1) Enable port
* 2) Resume ??
*/
// hp->RHportStatus->portChange |= PORT_ENABLE_CHANGE;
/* Over Current is not supported by the SL811 HW ?? */
/* How about the Port Power ?? */
} else {
/* Device has disconnect:
* 1) Disable port
*/
hp->RHportStatus->portStatus &= ~(PORT_ENABLE_STAT);
hp->RHportStatus->portChange |= PORT_ENABLE_CHANGE;
}
}
/*****************************************************************
*
* Function Name: SL11StartXaction
*
* This functions load the registers with appropriate value and
* transmit the packet.
*
* Input: hci = data structure for the host controller
* addr = USB address of the device
* epaddr = endpoint number
* pid = packet ID
* len = data length
* toggle = USB toggle bit, either 0 or 1
* slow = speed of the device
* urb_state = the current stage of USB transaction
*
* Return: 0 = error; 1 = successful
*
*****************************************************************/
int SL11StartXaction (hci_t * hci, __u8 addr, __u8 epaddr, int pid, int len,
int toggle, int slow, int urb_state)
{
hcipriv_t *hp = &hci->hp;
__u8 cmd = 0;
__u8 setup_data[4];
__u16 speed;
speed = hp->RHportStatus->portStatus;
if (!(speed & PORT_LOW_SPEED_DEV_ATTACH_STAT) && slow) {
cmd |= SL11H_HCTLMASK_PREAMBLE;
}
switch (pid) {
case PID_SETUP:
cmd &= SL11H_HCTLMASK_PREAMBLE;
cmd |=
(SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENBLEP |
SL11H_HCTLMASK_WRITE);
break;
case PID_OUT:
cmd &= (SL11H_HCTLMASK_SEQ | SL11H_HCTLMASK_PREAMBLE);
cmd |=
(SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENBLEP |
SL11H_HCTLMASK_WRITE);
if (toggle) {
cmd |= SL11H_HCTLMASK_SEQ;
}
break;
case PID_IN:
cmd &= (SL11H_HCTLMASK_SEQ | SL11H_HCTLMASK_PREAMBLE);
cmd |= (SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENBLEP);
break;
default:
DBGERR ("ERR: SL11StartXaction: unknow pid = 0x%x\n", pid);
return 0;
}
setup_data[0] = SL11H_DATA_START;
setup_data[1] = len;
setup_data[2] = (((pid & 0x0F) << 4) | (epaddr & 0xF));
setup_data[3] = addr & 0x7F;
SL811BufWrite (hci, SL11H_BUFADDRREG, (__u8 *) & setup_data[0], 4);
SL811Write (hci, SL11H_HOSTCTLREG, cmd);
#if 0
/* The SL811 has a hardware flaw when hub devices sends out
* SE0 between packets. It has been found in a TI chipset and
* cypress hub chipset. It causes the SL811 to hang
* The workaround is to re-issue the preample again.
*/
if ((cmd & SL11H_HCTLMASK_PREAMBLE)) {
SL811Write (hci, SL11H_PIDEPREG_B, 0xc0);
SL811Write (hci, SL11H_HOSTCTLREG_B, 0x1); // send the premable
}
#endif
return 1;
}
/*****************************************************************
*
* Function Name: hc_interrupt
*
* Interrupt service routine.
*
* 1) determine the causes of interrupt
* 2) clears all interrupts
* 3) calls appropriate function to service the interrupt
*
* Input: irq = interrupt line associated with the controller
* hci = data structure for the host controller
* r = holds the snapshot of the processor's context before
* the processor entered interrupt code. (not used here)
*
* Return value : None.
*
*****************************************************************/
static void hc_interrupt (int irq, void *__hci, struct pt_regs *r)
{
char ii;
hci_t *hci = __hci;
int isExcessNak = 0;
int urb_state = 0;
char tmpIrq = 0;
/* Get value from interrupt status register */
ii = SL811Read (hci, SL11H_INTSTATREG);
if (ii & SL11H_INTMASK_INSRMV) {
/* Device insertion or removal detected for the USB port */
SL811Write (hci, SL11H_INTENBLREG, 0);
SL811Write (hci, SL11H_CTLREG1, 0);
mdelay (100); // wait for device stable
handleInsRmvIntr (hci);
return;
}
/* Clear all interrupts */
SL811Write (hci, SL11H_INTSTATREG, 0xff);
if (ii & SL11H_INTMASK_XFERDONE) {
/* USB Done interrupt occurred */
urb_state = sh_done_list (hci, &isExcessNak);
#ifdef WARNING
if (hci->td_array->len > 0)
printk ("WARNING: IRQ, td_array->len = 0x%x, s/b:0\n",
hci->td_array->len);
#endif
if (hci->td_array->len == 0 && !isExcessNak
&& !(ii & SL11H_INTMASK_SOFINTR) && (urb_state == 0)) {
if (urb_state == 0) {
/* All urb_state has not been finished yet!
* continue with the current urb transaction
*/
if (hci->last_packet_nak == 0) {
if (!usb_pipecontrol
(hci->td_array->td[0].urb->pipe))
sh_add_packet (hci, hci->td_array-> td[0].urb);
}
} else {
/* The last transaction has completed:
* schedule the next transaction
*/
sh_schedule_trans (hci, 0);
}
}
SL811Write (hci, SL11H_INTSTATREG, 0xff);
return;
}
if (ii & SL11H_INTMASK_SOFINTR) {
hci->frame_number = (hci->frame_number + 1) % 2048;
if (hci->td_array->len == 0)
sh_schedule_trans (hci, 1);
else {
if (sofWaitCnt++ > 100) {
/* The last transaction has not completed.
* Need to retire the current td, and let
* it transmit again later on.
* (THIS NEEDS TO BE WORK ON MORE, IT SHOULD NEVER
* GET TO THIS POINT)
*/
DBGERR ("SOF interrupt: td_array->len = 0x%x, s/b: 0\n",
hci->td_array->len);
urb_print (hci->td_array->td[hci->td_array->len - 1].urb,
"INTERRUPT", 0);
sh_done_list (hci, &isExcessNak);
SL811Write (hci, SL11H_INTSTATREG, 0xff);
hci->td_array->len = 0;
sofWaitCnt = 0;
}
}
tmpIrq = SL811Read (hci, SL11H_INTSTATREG) & SL811Read (hci, SL11H_INTENBLREG);
if (tmpIrq) {
DBG ("IRQ occurred while service SOF: irq = 0x%x\n",
tmpIrq);
/* If we receive a DONE IRQ after schedule, need to
* handle DONE IRQ again
*/
if (tmpIrq & SL11H_INTMASK_XFERDONE) {
DBGERR ("IRQ occurred while service SOF: irq = 0x%x\n",
tmpIrq);
urb_state = sh_done_list (hci, &isExcessNak);
}
SL811Write (hci, SL11H_INTSTATREG, 0xff);
}
} else {
DBG ("SL811 ISR: unknown, int = 0x%x \n", ii);
}
SL811Write (hci, SL11H_INTSTATREG, 0xff);
return;
}
/*****************************************************************
*
* Function Name: hc_reset
*
* This function does register test and resets the SL811HS
* controller.
*
* Input: hci = data structure for the host controller
*
* Return value : 0
*
*****************************************************************/
static int hc_reset (hci_t * hci)
{
int attachFlag = 0;
DBGFUNC ("Enter hc_reset\n");
regTest (hci);
attachFlag = USBReset (hci);
if (attachFlag) {
setPortChange (hci, PORT_CONNECT_CHANGE);
}
return (0);
}
/*****************************************************************
*
* Function Name: hc_alloc_trans_buffer
*
* This function allocates all transfer buffer
*
* Input: hci = data structure for the host controller
*
* Return value : 0
*
*****************************************************************/
static int hc_alloc_trans_buffer (hci_t * hci)
{
hcipriv_t *hp = &hci->hp;
int maxlen;
hp->itl0_len = 0;
hp->itl1_len = 0;
hp->atl_len = 0;
hp->itl_buffer_len = 1024;
hp->atl_buffer_len = 4096 - 2 * hp->itl_buffer_len; /* 2048 */
maxlen = (hp->itl_buffer_len > hp->atl_buffer_len) ? hp->itl_buffer_len : hp->atl_buffer_len;
hp->tl = kmalloc (maxlen, GFP_KERNEL);
if (!hp->tl)
return -ENOMEM;
memset (hp->tl, 0, maxlen);
return 0;
}
/*****************************************************************
*
* Function Name: getPortStatusAndChange
*
* This function gets the ports status from SL811 and format it
* to a USB request format
*
* Input: hci = data structure for the host controller
*
* Return value : port status and change
*
*****************************************************************/
static __u32 getPortStatusAndChange (hci_t * hci)
{
hcipriv_t *hp = &hci->hp;
__u32 portstatus;
DBGFUNC ("enter getPorStatusAndChange\n");
portstatus = hp->RHportStatus->portChange << 16 | hp->RHportStatus->portStatus;
return (portstatus);
}
/*****************************************************************
*
* Function Name: setPortChange
*
* This function set the bit position of portChange.
*
* Input: hci = data structure for the host controller
* bitPos = the bit position
*
* Return value : none
*
*****************************************************************/
static void setPortChange (hci_t * hci, __u16 bitPos)
{
hcipriv_t *hp = &hci->hp;
switch (bitPos) {
case PORT_CONNECT_STAT:
hp->RHportStatus->portChange |= bitPos;
break;
case PORT_ENABLE_STAT:
hp->RHportStatus->portChange |= bitPos;
break;
case PORT_RESET_STAT:
hp->RHportStatus->portChange |= bitPos;
break;
case PORT_POWER_STAT:
hp->RHportStatus->portChange |= bitPos;
break;
case PORT_SUSPEND_STAT:
hp->RHportStatus->portChange |= bitPos;
break;
case PORT_OVER_CURRENT_STAT:
hp->RHportStatus->portChange |= bitPos;
break;
}
}
/*****************************************************************
*
* Function Name: clrPortChange
*
* This function clear the bit position of portChange.
*
* Input: hci = data structure for the host controller
* bitPos = the bit position
*
* Return value : none
*
*****************************************************************/
static void clrPortChange (hci_t * hci, __u16 bitPos)
{
hcipriv_t *hp = &hci->hp;
switch (bitPos) {
case PORT_CONNECT_CHANGE:
hp->RHportStatus->portChange &= ~bitPos;
break;
case PORT_ENABLE_CHANGE:
hp->RHportStatus->portChange &= ~bitPos;
break;
case PORT_RESET_CHANGE:
hp->RHportStatus->portChange &= ~bitPos;
break;
case PORT_SUSPEND_CHANGE:
hp->RHportStatus->portChange &= ~bitPos;
break;
case PORT_OVER_CURRENT_CHANGE:
hp->RHportStatus->portChange &= ~bitPos;
break;
}
}
/*****************************************************************
*
* Function Name: clrPortStatus
*
* This function clear the bit position of portStatus.
*
* Input: hci = data structure for the host controller
* bitPos = the bit position
*
* Return value : none
*
*****************************************************************/
static void clrPortStatus (hci_t * hci, __u16 bitPos)
{
hcipriv_t *hp = &hci->hp;
switch (bitPos) {
case PORT_ENABLE_STAT:
hp->RHportStatus->portStatus &= ~bitPos;
break;
case PORT_RESET_STAT:
hp->RHportStatus->portStatus &= ~bitPos;
break;
case PORT_POWER_STAT:
hp->RHportStatus->portStatus &= ~bitPos;
break;
case PORT_SUSPEND_STAT:
hp->RHportStatus->portStatus &= ~bitPos;
break;
}
}
/*****************************************************************
*
* Function Name: setPortStatus
*
* This function set the bit position of portStatus.
*
* Input: hci = data structure for the host controller
* bitPos = the bit position
*
* Return value : none
*
*****************************************************************/
static void setPortStatus (hci_t * hci, __u16 bitPos)
{
hcipriv_t *hp = &hci->hp;
switch (bitPos) {
case PORT_ENABLE_STAT:
hp->RHportStatus->portStatus |= bitPos;
break;
case PORT_RESET_STAT:
hp->RHportStatus->portStatus |= bitPos;
break;
case PORT_POWER_STAT:
hp->RHportStatus->portStatus |= bitPos;
break;
case PORT_SUSPEND_STAT:
hp->RHportStatus->portStatus |= bitPos;
break;
}
}
/*****************************************************************
*
* Function Name: hc_start
*
* This function starts the root hub functionality.
*
* Input: hci = data structure for the host controller
*
* Return value : 0
*
*****************************************************************/
static int hc_start (hci_t * hci)
{
DBGFUNC ("Enter hc_start\n");
rh_connect_rh (hci);
return 0;
}
/*****************************************************************
*
* Function Name: hc_alloc_hci
*
* This function allocates all data structure and store in the
* private data structure.
*
* Input: hci = data structure for the host controller
*
* Return value : 0
*
*****************************************************************/
static hci_t *__devinit hc_alloc_hci (void)
{
hci_t *hci;
hcipriv_t *hp;
portstat_t *ps;
struct usb_bus *bus;
DBGFUNC ("Enter hc_alloc_hci\n");
hci = (hci_t *) kmalloc (sizeof (hci_t), GFP_KERNEL);
if (!hci)
return NULL;
memset (hci, 0, sizeof (hci_t));
hp = &hci->hp;
hp->irq = -1;
hp->hcport = -1;
/* setup root hub port status */
ps = (portstat_t *) kmalloc (sizeof (portstat_t), GFP_KERNEL);
if (!ps)
return NULL;
ps->portStatus = PORT_STAT_DEFAULT;
ps->portChange = PORT_CHANGE_DEFAULT;
hp->RHportStatus = ps;
hci->nakCnt = 0;
hci->last_packet_nak = 0;
hci->a_td_array.len = 0;
hci->i_td_array[0].len = 0;
hci->i_td_array[1].len = 0;
hci->td_array = &hci->a_td_array;
hci->active_urbs = 0;
hci->active_trans = 0;
INIT_LIST_HEAD (&hci->hci_hcd_list);
list_add (&hci->hci_hcd_list, &hci_hcd_list);
init_waitqueue_head (&hci->waitq);
INIT_LIST_HEAD (&hci->ctrl_list);
INIT_LIST_HEAD (&hci->bulk_list);
INIT_LIST_HEAD (&hci->iso_list);
INIT_LIST_HEAD (&hci->intr_list);
INIT_LIST_HEAD (&hci->del_list);
bus = usb_alloc_bus (&hci_device_operations);
if (!bus) {
kfree (hci);
return NULL;
}
hci->bus = bus;
bus->hcpriv = (void *) hci;
return hci;
}
/*****************************************************************
*
* Function Name: hc_release_hci
*
* This function De-allocate all resources
*
* Input: hci = data structure for the host controller
*
* Return value : 0
*
*****************************************************************/
static void hc_release_hci (hci_t * hci)
{
hcipriv_t *hp = &hci->hp;
DBGFUNC ("Enter hc_release_hci\n");
/* disconnect all devices */
if (hci->bus->root_hub)
usb_disconnect (&hci->bus->root_hub);
hc_reset (hci);
if (hp->tl)
kfree (hp->tl);
if (hp->hcport > 0) {
release_region (hp->hcport, 2);
hp->hcport = 0;
}
if (hp->irq >= 0) {
free_irq (hp->irq, hci);
hp->irq = -1;
}
usb_deregister_bus (hci->bus);
usb_free_bus (hci->bus);
list_del (&hci->hci_hcd_list);
INIT_LIST_HEAD (&hci->hci_hcd_list);
kfree (hci);
}
/*****************************************************************
*
* Function Name: init_irq
*
* This function is board specific. It sets up the interrupt to
* be an edge trigger and trigger on the rising edge
*
* Input: none
*
* Return value : none
*
*****************************************************************/
void init_irq (void)
{
GPDR &= ~(1 << 13);
set_GPIO_IRQ_edge (1 << 13, GPIO_RISING_EDGE);
}
/*****************************************************************
*
* Function Name: hc_found_hci
*
* This function request IO memory regions, request IRQ, and
* allocate all other resources.
*
* Input: addr = first IO address
* addr2 = second IO address
* irq = interrupt number
*
* Return: 0 = success or error condition
*
*****************************************************************/
static int __devinit hc_found_hci (int addr, int addr2, int irq)
{
hci_t *hci;
hcipriv_t *hp;
DBGFUNC ("Enter hc_found_hci\n");
hci = hc_alloc_hci ();
if (!hci) {
return -ENOMEM;
}
init_irq ();
hp = &hci->hp;
if (!request_region (addr, 256, "SL811 USB HOST")) {
DBGERR ("request address %d failed", addr);
hc_release_hci (hci);
return -EBUSY;
}
hp->hcport = addr;
if (!hp->hcport) {
DBGERR ("Error mapping SL811 Memory 0x%x", hp->hcport);
}
if (!request_region (addr2, 256, "SL811 USB HOST")) {
DBGERR ("request address %d failed", addr2);
hc_release_hci (hci);
return -EBUSY;
}
hp->hcport2 = addr2;
if (!hp->hcport2) {
DBGERR ("Error mapping SL811 Memory 0x%x", hp->hcport2);
}
if (hc_alloc_trans_buffer (hci)) {
hc_release_hci (hci);
return -ENOMEM;
}
usb_register_bus (hci->bus);
if (request_irq (irq, hc_interrupt, 0, "SL811", hci) != 0) {
DBGERR ("request interrupt %d failed", irq);
hc_release_hci (hci);
return -EBUSY;
}
hp->irq = irq;
printk (KERN_INFO __FILE__ ": USB SL811 at %x, addr2 = %x, IRQ %d\n",
addr, addr2, irq);
hc_reset (hci);
if (hc_start (hci) < 0) {
DBGERR ("can't start usb-%x", addr);
hc_release_hci (hci);
return -EBUSY;
}
return 0;
}
/*****************************************************************
*
* Function Name: hci_hcd_init
*
* This is an init function, and it is the first function being called
*
* Input: none
*
* Return: 0 = success or error condition
*
*****************************************************************/
static int __init hci_hcd_init (void)
{
int ret;
DBGFUNC ("Enter hci_hcd_init\n");
ret = hc_found_hci (base_addr, data_reg_addr, irq);
return ret;
}
/*****************************************************************
*
* Function Name: hci_hcd_cleanup
*
* This is a cleanup function, and it is called when module is
* unloaded.
*
* Input: none
*
* Return: none
*
*****************************************************************/
static void __exit hci_hcd_cleanup (void)
{
struct list_head *hci_l;
hci_t *hci;
DBGFUNC ("Enter hci_hcd_cleanup\n");
for (hci_l = hci_hcd_list.next; hci_l != &hci_hcd_list;) {
hci = list_entry (hci_l, hci_t, hci_hcd_list);
hci_l = hci_l->next;
hc_release_hci (hci);
}
}
module_init (hci_hcd_init);
module_exit (hci_hcd_cleanup);
MODULE_AUTHOR ("Pei Liu <pbl@cypress.com>");
MODULE_DESCRIPTION ("USB SL811HS Host Controller Driver");
/*
* SL811HS HCD (Host Controller Driver) for USB.
*
* COPYRIGHT (C) by CYPRESS SEMICONDUCTOR INC
*
*
*/
#define GET_FRAME_NUMBER(hci) READ_REG32 (hci, HcFmNumber)
/*
* Maximum number of root hub ports
*/
#define MAX_ROOT_PORTS 15 /* maximum OHCI root hub ports */
/* control and status registers */
#define HcRevision 0x00
#define HcControl 0x01
#define HcCommandStatus 0x02
#define HcInterruptStatus 0x03
#define HcInterruptEnable 0x04
#define HcInterruptDisable 0x05
#define HcFmInterval 0x0D
#define HcFmRemaining 0x0E
#define HcFmNumber 0x0F
#define HcLSThreshold 0x11
#define HcRhDescriptorA 0x12
#define HcRhDescriptorB 0x13
#define HcRhStatus 0x14
#define HcRhPortStatus 0x15
#define HcHardwareConfiguration 0x20
#define HcDMAConfiguration 0x21
#define HcTransferCounter 0x22
#define HcuPInterrupt 0x24
#define HcuPInterruptEnable 0x25
#define HcChipID 0x27
#define HcScratch 0x28
#define HcSoftwareReset 0x29
#define HcITLBufferLength 0x2A
#define HcATLBufferLength 0x2B
#define HcBufferStatus 0x2C
#define HcReadBackITL0Length 0x2D
#define HcReadBackITL1Length 0x2E
#define HcITLBufferPort 0x40
#define HcATLBufferPort 0x41
/* OHCI CONTROL AND STATUS REGISTER MASKS */
/*
* HcControl (control) register masks
*/
#define OHCI_CTRL_HCFS (3 << 6) /* BUS state mask */
#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */
#define OHCI_CTRL_RWE (1 << 10) /* remote wakeup enable */
/* pre-shifted values for HCFS */
#define OHCI_USB_RESET (0 << 6)
#define OHCI_USB_RESUME (1 << 6)
#define OHCI_USB_OPER (2 << 6)
#define OHCI_USB_SUSPEND (3 << 6)
/*
* HcCommandStatus (cmdstatus) register masks
*/
#define OHCI_HCR (1 << 0) /* host controller reset */
#define OHCI_SO (3 << 16) /* scheduling overrun count */
/*
* masks used with interrupt registers:
* HcInterruptStatus (intrstatus)
* HcInterruptEnable (intrenable)
* HcInterruptDisable (intrdisable)
*/
#define OHCI_INTR_SO (1 << 0) /* scheduling overrun */
#define OHCI_INTR_SF (1 << 2) /* start frame */
#define OHCI_INTR_RD (1 << 3) /* resume detect */
#define OHCI_INTR_UE (1 << 4) /* unrecoverable error */
#define OHCI_INTR_FNO (1 << 5) /* frame number overflow */
#define OHCI_INTR_RHSC (1 << 6) /* root hub status change */
#define OHCI_INTR_ATD (1 << 7) /* scheduling overrun */
#define OHCI_INTR_MIE (1 << 31) /* master interrupt enable */
/*
* HcHardwareConfiguration
*/
#define InterruptPinEnable (1 << 0)
#define InterruptPinTrigger (1 << 1)
#define InterruptOutputPolarity (1 << 2)
#define DataBusWidth16 (1 << 3)
#define DREQOutputPolarity (1 << 5)
#define DACKInputPolarity (1 << 6)
#define EOTInputPolarity (1 << 7)
#define DACKMode (1 << 8)
#define AnalogOCEnable (1 << 10)
#define SuspendClkNotStop (1 << 11)
#define DownstreamPort15KRSel (1 << 12)
/*
* HcDMAConfiguration
*/
#define DMAReadWriteSelect (1 << 0)
#define ITL_ATL_DataSelect (1 << 1)
#define DMACounterSelect (1 << 2)
#define DMAEnable (1 << 4)
#define BurstLen_1 0
#define BurstLen_4 (1 << 5)
#define BurstLen_8 (2 << 5)
/*
* HcuPInterrupt
*/
#define SOFITLInt (1 << 0)
#define ATLInt (1 << 1)
#define AllEOTInterrupt (1 << 2)
#define OPR_Reg (1 << 4)
#define HCSuspended (1 << 5)
#define ClkReady (1 << 6)
/*
* HcBufferStatus
*/
#define ITL0BufferFull (1 << 0)
#define ITL1BufferFull (1 << 1)
#define ATLBufferFull (1 << 2)
#define ITL0BufferDone (1 << 3)
#define ITL1BufferDone (1 << 4)
#define ATLBufferDone (1 << 5)
/* OHCI ROOT HUB REGISTER MASKS */
/* roothub.portstatus [i] bits */
#define RH_PS_CCS 0x00000001 /* current connect status */
#define RH_PS_PES 0x00000002 /* port enable status */
#define RH_PS_PSS 0x00000004 /* port suspend status */
#define RH_PS_POCI 0x00000008 /* port over current indicator */
#define RH_PS_PRS 0x00000010 /* port reset status */
#define RH_PS_PPS 0x00000100 /* port power status */
#define RH_PS_LSDA 0x00000200 /* low speed device attached */
#define RH_PS_CSC 0x00010000 /* connect status change */
#define RH_PS_PESC 0x00020000 /* port enable status change */
#define RH_PS_PSSC 0x00040000 /* port suspend status change */
#define RH_PS_OCIC 0x00080000 /* over current indicator change */
#define RH_PS_PRSC 0x00100000 /* port reset status change */
/* roothub.status bits */
#define RH_HS_LPS 0x00000001 /* local power status */
#define RH_HS_OCI 0x00000002 /* over current indicator */
#define RH_HS_DRWE 0x00008000 /* device remote wakeup enable */
#define RH_HS_LPSC 0x00010000 /* local power status change */
#define RH_HS_OCIC 0x00020000 /* over current indicator change */
#define RH_HS_CRWE 0x80000000 /* clear remote wakeup enable */
/* roothub.b masks */
#define RH_B_DR 0x0000ffff /* device removable flags */
#define RH_B_PPCM 0xffff0000 /* port power control mask */
/* roothub.a masks */
#define RH_A_NDP (0xff << 0) /* number of downstream ports */
#define RH_A_PSM (1 << 8) /* power switching mode */
#define RH_A_NPS (1 << 9) /* no power switching */
#define RH_A_DT (1 << 10) /* device type (mbz) */
#define RH_A_OCPM (1 << 11) /* over current protection mode */
#define RH_A_NOCP (1 << 12) /* no over current protection */
#define RH_A_POTPGT (0xff << 24) /* power on to power good time */
#define URB_DEL 1
#define PORT_STAT_DEFAULT 0x0100
#define PORT_CONNECT_STAT 0x1
#define PORT_ENABLE_STAT 0x2
#define PORT_SUSPEND_STAT 0x4
#define PORT_OVER_CURRENT_STAT 0x8
#define PORT_RESET_STAT 0x10
#define PORT_POWER_STAT 0x100
#define PORT_LOW_SPEED_DEV_ATTACH_STAT 0x200
#define PORT_CHANGE_DEFAULT 0x0
#define PORT_CONNECT_CHANGE 0x1
#define PORT_ENABLE_CHANGE 0x2
#define PORT_SUSPEND_CHANGE 0x4
#define PORT_OVER_CURRENT_CHANGE 0x8
#define PORT_RESET_CHANGE 0x10
/* Port Status Request info */
typedef struct portstat {
__u16 portChange;
__u16 portStatus;
} portstat_t;
typedef struct hcipriv {
int irq;
int disabled; /* e.g. got a UE, we're hung */
atomic_t resume_count; /* defending against multiple resumes */
struct ohci_regs *regs; /* OHCI controller's memory */
int hcport; /* I/O base address */
int hcport2; /* I/O data reg addr */
struct portstat *RHportStatus; /* root hub port status */
int intrstatus;
__u32 hc_control; /* copy of the hc control reg */
int frame;
__u8 *tl;
int xferPktLen;
int atl_len;
int atl_buffer_len;
int itl0_len;
int itl1_len;
int itl_buffer_len;
int itl_index;
int tl_last;
int units_left;
} hcipriv_t;
struct hci;
#define cClt 0 // Control
#define cISO 1 // ISO
#define cBULK 2 // BULK
#define cInt 3 // Interrupt
#define ISO_BIT 0x10
/*-------------------------------------------------------------------------
* EP0 use for configuration and Vendor Specific command interface
*------------------------------------------------------------------------*/
#define cMemStart 0x10
#define EP0Buf 0x40 /* SL11H/SL811H memory start at 0x40 */
#define EP0Len 0x40 /* Length of config buffer EP0Buf */
#define EP1Buf 0x60
#define EP1Len 0x40
/*-------------------------------------------------------------------------
* SL11H/SL811H memory from 80h-ffh use as ping-pong buffer.
*------------------------------------------------------------------------*/
#define uBufA 0x80 /* buffer A address for DATA0 */
#define uBufB 0xc0 /* buffer B address for DATA1 */
#define uXferLen 0x40 /* xfer length */
#define sMemSize 0xc0 /* Total SL11 memory size */
#define cMemEnd 256
/*-------------------------------------------------------------------------
* SL811H Register Control memory map
* --Note:
* --SL11H only has one control register set from 0x00-0x04
* --SL811H has two control register set from 0x00-0x04 and 0x08-0x0c
*------------------------------------------------------------------------*/
#define EP0Control 0x00
#define EP0Address 0x01
#define EP0XferLen 0x02
#define EP0Status 0x03
#define EP0Counter 0x04
#define EP1Control 0x08
#define EP1Address 0x09
#define EP1XferLen 0x0a
#define EP1Status 0x0b
#define EP1Counter 0x0c
#define CtrlReg 0x05
#define IntEna 0x06
// 0x07 is reserved
#define IntStatus 0x0d
#define cDATASet 0x0e
#define cSOFcnt 0x0f
#define IntMask 0x57 /* Reset|DMA|EP0|EP2|EP1 for IntEna */
#define HostMask 0x47 /* Host request command for IntStatus */
#define ReadMask 0xd7 /* Read mask interrupt for IntStatus */
/*-------------------------------------------------------------------------
* Standard Chapter 9 definition
*-------------------------------------------------------------------------
*/
#define GET_STATUS 0x00
#define CLEAR_FEATURE 0x01
#define SET_FEATURE 0x03
#define SET_ADDRESS 0x05
#define GET_DESCRIPTOR 0x06
#define SET_DESCRIPTOR 0x07
#define GET_CONFIG 0x08
#define SET_CONFIG 0x09
#define GET_INTERFACE 0x0a
#define SET_INTERFACE 0x0b
#define SYNCH_FRAME 0x0c
#define DEVICE 0x01
#define CONFIGURATION 0x02
#define STRING 0x03
#define INTERFACE 0x04
#define ENDPOINT 0x05
/*-------------------------------------------------------------------------
* SL11H/SL811H definition
*-------------------------------------------------------------------------
*/
#define DATA0_WR 0x07 // (Arm+Enable+tranmist to Host+DATA0)
#define DATA1_WR 0x47 // (Arm+Enable+tranmist to Host on DATA1)
#define ZDATA0_WR 0x05 // (Arm+Transaction Ignored+tranmist to Host+DATA0)
#define ZDATA1_WR 0x45 // (Arm+Transaction Ignored+tranmist to Host+DATA1)
#define DATA0_RD 0x03 // (Arm+Enable+received from Host+DATA0)
#define DATA1_RD 0x43 // (Arm+Enable+received from Host+DATA1)
#define PID_SETUP 0x2d // USB Specification 1.1 Standard Definition
#define PID_SOF 0xA5
#define PID_IN 0x69
#define PID_OUT 0xe1
#define MAX_RETRY 0xffff
#define TIMEOUT 5 /* 2 mseconds */
#define SL11H_HOSTCTLREG 0
#define SL11H_BUFADDRREG 1
#define SL11H_BUFLNTHREG 2
#define SL11H_PKTSTATREG 3 /* read */
#define SL11H_PIDEPREG 3 /* write */
#define SL11H_XFERCNTREG 4 /* read */
#define SL11H_DEVADDRREG 4 /* write */
#define SL11H_CTLREG1 5
#define SL11H_INTENBLREG 6
#define SL11H_HOSTCTLREG_B 8
#define SL11H_BUFADDRREG_B 9
#define SL11H_BUFLNTHREG_B 0x0A
#define SL11H_PKTSTATREG_B 0x0B /* read */
#define SL11H_PIDEPREG_B 0x0B /* write */
#define SL11H_XFERCNTREG_B 0x0C /* read */
#define SL11H_DEVADDRREG_B 0x0C /* write */
#define SL11H_INTSTATREG 0x0D /* write clears bitwise */
#define SL11H_HWREVREG 0x0E /* read */
#define SL11H_SOFLOWREG 0x0E /* write */
#define SL11H_SOFTMRREG 0x0F /* read */
#define SL11H_CTLREG2 0x0F /* write */
#define SL11H_DATA_START 0x10
/* Host control register bits (addr 0) */
#define SL11H_HCTLMASK_ARM 1
#define SL11H_HCTLMASK_ENBLEP 2
#define SL11H_HCTLMASK_WRITE 4
#define SL11H_HCTLMASK_ISOCH 0x10
#define SL11H_HCTLMASK_AFTERSOF 0x20
#define SL11H_HCTLMASK_SEQ 0x40
#define SL11H_HCTLMASK_PREAMBLE 0x80
/* Packet status register bits (addr 3) */
#define SL11H_STATMASK_ACK 1
#define SL11H_STATMASK_ERROR 2
#define SL11H_STATMASK_TMOUT 4
#define SL11H_STATMASK_SEQ 8
#define SL11H_STATMASK_SETUP 0x10
#define SL11H_STATMASK_OVF 0x20
#define SL11H_STATMASK_NAK 0x40
#define SL11H_STATMASK_STALL 0x80
/* Control register 1 bits (addr 5) */
#define SL11H_CTL1MASK_DSBLSOF 1
#define SL11H_CTL1MASK_NOTXEOF2 4
#define SL11H_CTL1MASK_DSTATE 0x18
#define SL11H_CTL1MASK_NSPD 0x20
#define SL11H_CTL1MASK_SUSPEND 0x40
#define SL11H_CTL1MASK_CLK12 0x80
#define SL11H_CTL1VAL_RESET 8
/* Interrut enable (addr 6) and interrupt status register bits (addr 0xD) */
#define SL11H_INTMASK_XFERDONE 1
#define SL11H_INTMASK_SOFINTR 0x10
#define SL11H_INTMASK_INSRMV 0x20
#define SL11H_INTMASK_USBRESET 0x40
#define SL11H_INTMASK_DSTATE 0x80 /* only in status reg */
/* HW rev and SOF lo register bits (addr 0xE) */
#define SL11H_HWRMASK_HWREV 0xF0
/* SOF counter and control reg 2 (addr 0xF) */
#define SL11H_CTL2MASK_SOFHI 0x3F
#define SL11H_CTL2MASK_DSWAP 0x40
#define SL11H_CTL2MASK_HOSTMODE 0xae
/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*
* SL811HS virtual root hub
*
* based on usb-ohci.c by R. Weissgaerber et al.
*-------------------------------------------------------------------------*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*-------------------------------------------------------------------------*/
#ifdef DEBUG
#undef DEBUG
#endif
static __u32 getPortStatusAndChange (hci_t * hci);
static void setPortStatus (hci_t * hci, __u16 bitPos);
static void setPortChange (hci_t * hci, __u16 bitPos);
static void clrPortStatus (hci_t * hci, __u16 bitPos);
static void clrPortChange (hci_t * hci, __u16 bitPos);
static int USBReset (hci_t * hci);
static int cc_to_error (int cc);
/*-------------------------------------------------------------------------*
* Virtual Root Hub
*-------------------------------------------------------------------------*/
/* Device descriptor */
static __u8 root_hub_dev_des[] = {
0x12, /* __u8 bLength; */
0x01, /* __u8 bDescriptorType; Device */
0x10, /* __u16 bcdUSB; v1.1 */
0x01,
0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */
0x00, /* __u8 bDeviceSubClass; */
0x00, /* __u8 bDeviceProtocol; */
0x08, /* __u8 bMaxPacketSize0; 8 Bytes */
0x00, /* __u16 idVendor; */
0x00,
0x00, /* __u16 idProduct; */
0x00,
0x00, /* __u16 bcdDevice; */
0x00,
0x00, /* __u8 iManufacturer; */
0x02, /* __u8 iProduct; */
0x01, /* __u8 iSerialNumber; */
0x01 /* __u8 bNumConfigurations; */
};
/* Configuration descriptor */
static __u8 root_hub_config_des[] = {
0x09, /* __u8 bLength; */
0x02, /* __u8 bDescriptorType; Configuration */
0x19, /* __u16 wTotalLength; */
0x00,
0x01, /* __u8 bNumInterfaces; */
0x01, /* __u8 bConfigurationValue; */
0x00, /* __u8 iConfiguration; */
0x40, /* __u8 bmAttributes;
Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup,
4..0: resvd */
0x00, /* __u8 MaxPower; */
/* interface */
0x09, /* __u8 if_bLength; */
0x04, /* __u8 if_bDescriptorType; Interface */
0x00, /* __u8 if_bInterfaceNumber; */
0x00, /* __u8 if_bAlternateSetting; */
0x01, /* __u8 if_bNumEndpoints; */
0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */
0x00, /* __u8 if_bInterfaceSubClass; */
0x00, /* __u8 if_bInterfaceProtocol; */
0x00, /* __u8 if_iInterface; */
/* endpoint */
0x07, /* __u8 ep_bLength; */
0x05, /* __u8 ep_bDescriptorType; Endpoint */
0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */
0x03, /* __u8 ep_bmAttributes; Interrupt */
0x02, /* __u16 ep_wMaxPacketSize; ((MAX_ROOT_PORTS + 1) / 8 */
0x00,
0xff /* __u8 ep_bInterval; 255 ms */
};
/* Hub class-specific descriptor is constructed dynamically */
/***************************************************************************
* Function Name : rh_send_irq
*
* This function examine the port change in the virtual root hub.
*
* Note: This function assumes only one port exist in the root hub.
*
* Input: hci = data structure for the host controller
* rh_data = The pointer to port change data
* rh_len = length of the data in bytes
*
* Return: length of data
**************************************************************************/
static int rh_send_irq (hci_t * hci, void *rh_data, int rh_len)
{
int num_ports;
int i;
int ret;
int len;
__u8 data[8];
DBGFUNC ("enter rh_send_irq: \n");
/* Assuming the root hub has one port. This value need to change if
* there are more than one port for the root hub
*/
num_ports = 1;
/* The root hub status is not implemented, it basically has two fields:
* -- Local Power Status
* -- Over Current Indicator
* -- Local Power Change
* -- Over Current Indicator
*
* Right now, It is assume the power is good and no changes
*/
*(__u8 *) data = 0;
ret = *(__u8 *) data;
/* Has the port status change within the root hub: It checks for
* -- Port Connect Status change
* -- Port Enable Change
*/
for (i = 0; i < num_ports; i++) {
*(__u8 *) (data + (i + 1) / 8) |=
(((getPortStatusAndChange (hci) >> 16) & (PORT_CONNECT_STAT | PORT_ENABLE_STAT)) ? 1 : 0) << ((i + 1) % 8);
ret += *(__u8 *) (data + (i + 1) / 8);
/* After the port change is read, it should be reset so the next time
* is it doesn't trigger a change again */
}
len = i / 8 + 1;
if (ret > 0) {
memcpy (rh_data, data, min (len, min (rh_len, (int)sizeof (data))));
return len;
}
return 0;
}
/***************************************************************************
* Function Name : rh_int_timer_do
*
* This function is called when the timer expires. It gets the the port
* change data and pass along to the upper protocol.
*
* Note: The virtual root hub interrupt pipe are polled by the timer
* every "interval" ms
*
* Input: ptr = ptr to the urb
*
* Return: none
**************************************************************************/
static void rh_int_timer_do (unsigned long ptr)
{
int len;
struct urb *urb = (struct urb *) ptr;
hci_t *hci = urb->dev->bus->hcpriv;
DBGFUNC ("enter rh_int_timer_do\n");
if (hci->rh.send) {
len = rh_send_irq (hci, urb->transfer_buffer,
urb->transfer_buffer_length);
if (len > 0) {
urb->actual_length = len;
if (urb_debug == 2)
urb_print (urb, "RET-t(rh)",
usb_pipeout (urb->pipe));
if (urb->complete) {
urb->complete (urb);
}
}
}
/* re-activate the timer */
rh_init_int_timer (urb);
}
/***************************************************************************
* Function Name : rh_init_int_timer
*
* This function creates a timer that act as interrupt pipe in the
* virtual hub.
*
* Note: The virtual root hub's interrupt pipe are polled by the timer
* every "interval" ms
*
* Input: urb = USB request block
*
* Return: 0
**************************************************************************/
static int rh_init_int_timer (struct urb * urb)
{
hci_t *hci = urb->dev->bus->hcpriv;
hci->rh.interval = urb->interval;
init_timer (&hci->rh.rh_int_timer);
hci->rh.rh_int_timer.function = rh_int_timer_do;
hci->rh.rh_int_timer.data = (unsigned long) urb;
hci->rh.rh_int_timer.expires = jiffies + (HZ * (urb->interval < 30 ? 30 : urb->interval)) / 1000;
add_timer (&hci->rh.rh_int_timer);
return 0;
}
/*-------------------------------------------------------------------------*/
/* helper macro */
#define OK(x) len = (x); break
/***************************************************************************
* Function Name : rh_submit_urb
*
* This function handles all USB request to the the virtual root hub
*
* Input: urb = USB request block
*
* Return: 0
**************************************************************************/
static int rh_submit_urb (struct urb * urb)
{
struct usb_device *usb_dev = urb->dev;
hci_t *hci = usb_dev->bus->hcpriv;
unsigned int pipe = urb->pipe;
struct usb_ctrlrequest *cmd = (struct usb_ctrlrequest *) urb->setup_packet;
void *data = urb->transfer_buffer;
int leni = urb->transfer_buffer_length;
int len = 0;
int status = TD_CC_NOERROR;
__u32 datab[4];
__u8 *data_buf = (__u8 *) datab;
__u16 bmRType_bReq;
__u16 wValue;
__u16 wIndex;
__u16 wLength;
DBGFUNC ("enter rh_submit_urb\n");
if (usb_pipeint (pipe)) {
hci->rh.urb = urb;
hci->rh.send = 1;
hci->rh.interval = urb->interval;
rh_init_int_timer (urb);
urb->status = cc_to_error (TD_CC_NOERROR);
return 0;
}
bmRType_bReq = cmd->bRequestType | (cmd->bRequest << 8);
wValue = le16_to_cpu (cmd->wValue);
wIndex = le16_to_cpu (cmd->wIndex);
wLength = le16_to_cpu (cmd->wLength);
DBG ("rh_submit_urb, req = %d(%x) len=%d",
bmRType_bReq, bmRType_bReq, wLength);
switch (bmRType_bReq) {
/* Request Destination:
without flags: Device,
RH_INTERFACE: interface,
RH_ENDPOINT: endpoint,
RH_CLASS means HUB here,
RH_OTHER | RH_CLASS almost ever means HUB_PORT here
*/
case RH_GET_STATUS:
*(__u16 *) data_buf = cpu_to_le16 (1);
OK (2);
case RH_GET_STATUS | RH_INTERFACE:
*(__u16 *) data_buf = cpu_to_le16 (0);
OK (2);
case RH_GET_STATUS | RH_ENDPOINT:
*(__u16 *) data_buf = cpu_to_le16 (0);
OK (2);
case RH_GET_STATUS | RH_CLASS:
*(__u32 *) data_buf = cpu_to_le32 (0);
OK (4);
case RH_GET_STATUS | RH_OTHER | RH_CLASS:
*(__u32 *) data_buf =
cpu_to_le32 (getPortStatusAndChange (hci));
OK (4);
case RH_CLEAR_FEATURE | RH_ENDPOINT:
switch (wValue) {
case (RH_ENDPOINT_STALL):
OK (0);
}
break;
case RH_CLEAR_FEATURE | RH_CLASS:
switch (wValue) {
case RH_C_HUB_LOCAL_POWER:
OK (0);
case (RH_C_HUB_OVER_CURRENT):
/* Over Current Not Implemented */
OK (0);
}
break;
case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS:
switch (wValue) {
case (RH_PORT_ENABLE):
clrPortStatus (hci, PORT_ENABLE_STAT);
OK (0);
case (RH_PORT_SUSPEND):
clrPortStatus (hci, PORT_SUSPEND_STAT);
OK (0);
case (RH_PORT_POWER):
clrPortStatus (hci, PORT_POWER_STAT);
OK (0);
case (RH_C_PORT_CONNECTION):
clrPortChange (hci, PORT_CONNECT_STAT);
OK (0);
case (RH_C_PORT_ENABLE):
clrPortChange (hci, PORT_ENABLE_STAT);
OK (0);
case (RH_C_PORT_SUSPEND):
clrPortChange (hci, PORT_SUSPEND_STAT);
OK (0);
case (RH_C_PORT_OVER_CURRENT):
clrPortChange (hci, PORT_OVER_CURRENT_STAT);
OK (0);
case (RH_C_PORT_RESET):
clrPortChange (hci, PORT_RESET_STAT);
OK (0);
}
break;
case RH_SET_FEATURE | RH_OTHER | RH_CLASS:
switch (wValue) {
case (RH_PORT_SUSPEND):
setPortStatus (hci, PORT_SUSPEND_STAT);
OK (0);
case (RH_PORT_RESET):
setPortStatus (hci, PORT_RESET_STAT);
// USBReset(hci);
clrPortChange (hci,
PORT_CONNECT_CHANGE | PORT_ENABLE_CHANGE
| PORT_SUSPEND_CHANGE |
PORT_OVER_CURRENT_CHANGE);
setPortChange (hci, PORT_RESET_CHANGE);
clrPortStatus (hci, PORT_RESET_STAT);
setPortStatus (hci, PORT_ENABLE_STAT);
OK (0);
case (RH_PORT_POWER):
setPortStatus (hci, PORT_POWER_STAT);
OK (0);
case (RH_PORT_ENABLE):
setPortStatus (hci, PORT_ENABLE_STAT);
OK (0);
}
break;
case RH_SET_ADDRESS:
hci->rh.devnum = wValue;
OK (0);
case RH_GET_DESCRIPTOR:
DBGVERBOSE ("rh_submit_urb: RH_GET_DESCRIPTOR, wValue = 0x%x\n", wValue);
switch ((wValue & 0xff00) >> 8) {
case (0x01): /* device descriptor */
len = min (leni, min ((__u16)sizeof (root_hub_dev_des), wLength));
data_buf = root_hub_dev_des;
OK (len);
case (0x02): /* configuration descriptor */
len = min (leni, min ((__u16)sizeof (root_hub_config_des), wLength));
data_buf = root_hub_config_des;
OK (len);
case (0x03): /* string descriptors */
len = usb_root_hub_string (wValue & 0xff, (int) (long) 0,
"SL811HS", data, wLength);
if (len > 0) {
data_buf = data;
OK (min (leni, len));
}
default:
status = SL11H_STATMASK_STALL;
}
break;
case RH_GET_DESCRIPTOR | RH_CLASS:
data_buf[0] = 9; // min length;
data_buf[1] = 0x29;
data_buf[2] = 1; // # of downstream port
data_buf[3] = 0;
datab[1] = 0;
data_buf[5] = 50; // 100 ms for port reset
data_buf[7] = 0xfc; // which port is attachable
if (data_buf[2] < 7) {
data_buf[8] = 0xff;
} else {
}
len = min (leni, min ((__u16)data_buf[0], wLength));
OK (len);
case RH_GET_CONFIGURATION:
*(__u8 *) data_buf = 0x01;
OK (1);
case RH_SET_CONFIGURATION:
OK (0);
default:
DBGERR ("unsupported root hub command");
status = SL11H_STATMASK_STALL;
}
len = min (len, leni);
if (data != data_buf)
memcpy (data, data_buf, len);
urb->actual_length = len;
urb->status = cc_to_error (status);
urb->hcpriv = NULL;
urb->dev = NULL;
if (urb->complete) {
urb->complete (urb);
}
return 0;
}
/***************************************************************************
* Function Name : rh_unlink_urb
*
* This function unlinks the URB
*
* Input: urb = USB request block
*
* Return: 0
**************************************************************************/
static int rh_unlink_urb (struct urb * urb)
{
hci_t *hci = urb->dev->bus->hcpriv;
DBGFUNC ("enter rh_unlink_urb\n");
if (hci->rh.urb == urb) {
hci->rh.send = 0;
del_timer (&hci->rh.rh_int_timer);
hci->rh.urb = NULL;
urb->hcpriv = NULL;
usb_put_dev (urb->dev);
urb->dev = NULL;
if (urb->transfer_flags & USB_ASYNC_UNLINK) {
urb->status = -ECONNRESET;
if (urb->complete) {
urb->complete (urb);
}
} else
urb->status = -ENOENT;
}
return 0;
}
/***************************************************************************
* Function Name : rh_connect_rh
*
* This function connect the virtual root hub to the USB stack
*
* Input: urb = USB request block
*
* Return: 0
**************************************************************************/
static int rh_connect_rh (hci_t * hci)
{
struct usb_device *usb_dev;
hci->rh.devnum = 0;
usb_dev = usb_alloc_dev (NULL, hci->bus);
if (!usb_dev)
return -ENOMEM;
hci->bus->root_hub = usb_dev;
usb_connect (usb_dev);
if (usb_new_device (usb_dev) != 0) {
usb_free_dev (usb_dev);
return -ENODEV;
}
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment