diff options
author | Zhang Wei <wei.zhang@freescale.com> | 2007-06-06 10:08:14 +0200 |
---|---|---|
committer | Markus Klotzbuecher <mk@pollux.denx.de> | 2007-06-06 10:08:14 +0200 |
commit | 4dae14ce8fbdf380017dc54f172218e7d2acc889 (patch) | |
tree | ced3af27f0cb09a8475906d3ea0b452c2dcd94af /drivers/usb_ohci.c | |
parent | fdcfaa1b02268b2899e374b35adf936c911a47eb (diff) | |
download | u-boot-imx-4dae14ce8fbdf380017dc54f172218e7d2acc889.zip u-boot-imx-4dae14ce8fbdf380017dc54f172218e7d2acc889.tar.gz u-boot-imx-4dae14ce8fbdf380017dc54f172218e7d2acc889.tar.bz2 |
USB PCI-OHCI, interrupt pipe and usb event poll support
This patch added USB PCI-OHCI chips support, interrupt pipe support
and usb event poll support. For supporting the USB interrupt pipe, the
globe urb_priv is moved to purb in ed struct. Now, we can process
several urbs at one time. The interrupt pipe support codes are ported
from Linux kernel 2.4.
Signed-off-by: Zhang Wei <wei.zhang@freescale.com>
Diffstat (limited to 'drivers/usb_ohci.c')
-rw-r--r-- | drivers/usb_ohci.c | 310 |
1 files changed, 257 insertions, 53 deletions
diff --git a/drivers/usb_ohci.c b/drivers/usb_ohci.c index 70cb6a3..459c809 100644 --- a/drivers/usb_ohci.c +++ b/drivers/usb_ohci.c @@ -1,5 +1,11 @@ /* - * URB OHCI HCD (Host Controller Driver) for USB on the AT91RM9200. + * URB OHCI HCD (Host Controller Driver) for USB on the AT91RM9200 and PCI bus. + * + * Interrupt support is added. Now, it has been tested + * on ULI1575 chip and works well with USB keyboard. + * + * (C) Copyright 2007 + * Zhang Wei, Freescale Semiconductor, Inc. <wei.zhang@freescale.com> * * (C) Copyright 2003 * Gary Jennejohn, DENX Software Engineering <gj@denx.de> @@ -35,7 +41,7 @@ * 1 - you MUST define LITTLEENDIAN in the configuration file for the * board or this driver will NOT work! * 2 - this driver is intended for use with USB Mass Storage Devices - * (BBB) ONLY. There is NO support for Interrupt or Isochronous pipes! + * (BBB) and USB keyboard. There is NO support for Isochronous pipes! * 3 - when running on a PQFP208 AT91RM9200, define CONFIG_AT91C_PQFP_UHPBUG * to activate workaround for bug #41 or this driver will NOT work! */ @@ -56,6 +62,8 @@ # include <asm/arch/pxa-regs.h> #elif defined(CONFIG_MPC5200) # include <mpc5xxx.h> +#elif defined(CONFIG_PCI_OHCI) +# include <pci.h> #endif #include <malloc.h> @@ -66,6 +74,7 @@ defined(CONFIG_S3C2400) || \ defined(CONFIG_S3C2410) || \ defined(CONFIG_440EP) || \ + defined(CONFIG_PCI_OHCI) || \ defined(CONFIG_MPC5200) # define OHCI_USE_NPS /* force NoPowerSwitching mode */ #endif @@ -79,12 +88,19 @@ #define OHCI_CONTROL_INIT \ (OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE | OHCI_CTRL_PLE -#define readl(a) (*((vu_long *)(a))) -#define writel(a, b) (*((vu_long *)(b)) = ((vu_long)a)) +#define readl(a) m32_swap(*((vu_long *)(a))) +#define writel(a, b) (*((vu_long *)(b)) = m32_swap((vu_long)a)) #define min_t(type,x,y) ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; }) -#undef DEBUG +#ifdef CONFIG_PCI_OHCI +static struct pci_device_id ohci_pci_ids[] = { + {0x10b9, 0x5237}, /* ULI1575 PCI OHCI module ids */ + /* Please add supported PCI OHCI controller ids here */ + {0, 0} +}; +#endif + #ifdef DEBUG #define dbg(format, arg...) printf("DEBUG: " format "\n", ## arg) #else @@ -114,15 +130,11 @@ struct ohci_hcca ghcca[1]; struct ohci_hcca *phcca; /* this allocates EDs for all possible endpoints */ struct ohci_device ohci_dev; -/* urb_priv */ -urb_priv_t urb_priv; /* RHSC flag */ int got_rhsc; /* device which was disconnected */ struct usb_device *devgone; -/* flag guarding URB transation */ -int urb_finished = 0; /*-------------------------------------------------------------------------*/ @@ -177,6 +189,7 @@ static void urb_free_priv (urb_priv_t * urb) } } } + free(urb); } /*-------------------------------------------------------------------------*/ @@ -187,11 +200,10 @@ static int sohci_get_current_frame_number (struct usb_device * dev); /* debug| print the main components of an URB * small: 0) header + data packets 1) just header */ -static void pkt_print (struct usb_device * dev, unsigned long pipe, void * buffer, +static void pkt_print (urb_priv_t *purb, struct usb_device * dev, + unsigned long pipe, void * buffer, int transfer_len, struct devrequest * setup, char * str, int small) { - urb_priv_t * purb = &urb_priv; - dbg("%s URB:[%4x] dev:%2d,ep:%2d-%c,type:%s,len:%d/%d stat:%#lx", str, sohci_get_current_frame_number (dev), @@ -200,7 +212,7 @@ static void pkt_print (struct usb_device * dev, unsigned long pipe, void * buffe usb_pipeout (pipe)? 'O': 'I', usb_pipetype (pipe) < 2? (usb_pipeint (pipe)? "INTR": "ISOC"): (usb_pipecontrol (pipe)? "CTRL": "BULK"), - purb->actual_length, + (purb ? purb->actual_length : 0), transfer_len, dev->status); #ifdef OHCI_VERBOSE_DEBUG if (!small) { @@ -214,10 +226,11 @@ static void pkt_print (struct usb_device * dev, unsigned long pipe, void * buffe } if (transfer_len > 0 && buffer) { printf (__FILE__ ": data(%d/%d):", - purb->actual_length, + (purb ? purb->actual_length : 0), transfer_len); len = usb_pipeout (pipe)? - transfer_len: purb->actual_length; + transfer_len: + (purb ? purb->actual_length : 0); for (i = 0; i < 16 && i < len; i++) printf (" %02x", ((__u8 *) buffer) [i]); printf ("%s\n", i < len? "...": ""); @@ -413,13 +426,17 @@ static void ohci_dump (ohci_t *controller, int verbose) /* get a transfer request */ -int sohci_submit_job(struct usb_device *dev, unsigned long pipe, void *buffer, - int transfer_len, struct devrequest *setup, int interval) +int sohci_submit_job(urb_priv_t *urb, struct devrequest *setup) { ohci_t *ohci; ed_t * ed; - urb_priv_t *purb_priv; + urb_priv_t *purb_priv = urb; int i, size = 0; + struct usb_device *dev = urb->dev; + unsigned long pipe = urb->pipe; + void *buffer = urb->transfer_buffer; + int transfer_len = urb->transfer_buffer_length; + int interval = urb->interval; ohci = &gohci; @@ -430,18 +447,11 @@ int sohci_submit_job(struct usb_device *dev, unsigned long pipe, void *buffer, return -1; } - /* if we have an unfinished URB from previous transaction let's - * fail and scream as quickly as possible so as not to corrupt - * further communication */ - if (!urb_finished) { - err("sohci_submit_job: URB NOT FINISHED"); - return -1; - } /* we're about to begin a new transaction here so mark the URB unfinished */ - urb_finished = 0; + urb->finished = 0; /* every endpoint has a ed, locate and fill it */ - if (!(ed = ep_add_ed (dev, pipe))) { + if (!(ed = ep_add_ed (dev, pipe, interval, 1))) { err("sohci_submit_job: ENOMEM"); return -1; } @@ -455,13 +465,17 @@ int sohci_submit_job(struct usb_device *dev, unsigned long pipe, void *buffer, size = (transfer_len == 0)? 2: (transfer_len - 1) / 4096 + 3; break; + case PIPE_INTERRUPT: /* 1 TD */ + size = 1; + break; } + ed->purb = urb; + if (size >= (N_URB_TD - 1)) { err("need %d TDs, only have %d", size, N_URB_TD); return -1; } - purb_priv = &urb_priv; purb_priv->pipe = pipe; /* fill the private part of the URB */ @@ -497,6 +511,40 @@ int sohci_submit_job(struct usb_device *dev, unsigned long pipe, void *buffer, return 0; } +static inline int sohci_return_job(struct ohci *hc, urb_priv_t *urb) +{ + struct ohci_regs *regs = hc->regs; + + switch (usb_pipetype (urb->pipe)) { + case PIPE_INTERRUPT: + /* implicitly requeued */ + if (urb->dev->irq_handle && + (urb->dev->irq_act_len = urb->actual_length)) { + writel (OHCI_INTR_WDH, ®s->intrenable); + readl (®s->intrenable); /* PCI posting flush */ + urb->dev->irq_handle(urb->dev); + writel (OHCI_INTR_WDH, ®s->intrdisable); + readl (®s->intrdisable); /* PCI posting flush */ + } + urb->actual_length = 0; + td_submit_job ( + urb->dev, + urb->pipe, + urb->transfer_buffer, + urb->transfer_buffer_length, + NULL, + urb, + urb->interval); + break; + case PIPE_CONTROL: + case PIPE_BULK: + break; + default: + return 0; + } + return 1; +} + /*-------------------------------------------------------------------------*/ #ifdef DEBUG @@ -514,13 +562,73 @@ static int sohci_get_current_frame_number (struct usb_device *usb_dev) * ED handling functions *-------------------------------------------------------------------------*/ +/* search for the right branch to insert an interrupt ed into the int tree + * do some load ballancing; + * returns the branch and + * sets the interval to interval = 2^integer (ld (interval)) */ + +static int ep_int_ballance (ohci_t * ohci, int interval, int load) +{ + int i, branch = 0; + + /* search for the least loaded interrupt endpoint + * branch of all 32 branches + */ + for (i = 0; i < 32; i++) + if (ohci->ohci_int_load [branch] > ohci->ohci_int_load [i]) + branch = i; + + branch = branch % interval; + for (i = branch; i < 32; i += interval) + ohci->ohci_int_load [i] += load; + + return branch; +} + +/*-------------------------------------------------------------------------*/ + +/* 2^int( ld (inter)) */ + +static int ep_2_n_interval (int inter) +{ + int i; + for (i = 0; ((inter >> i) > 1 ) && (i < 5); i++); + return 1 << i; +} + +/*-------------------------------------------------------------------------*/ + +/* the int tree is a binary tree + * in order to process it sequentially the indexes of the branches have to be mapped + * the mapping reverses the bits of a word of num_bits length */ + +static int ep_rev (int num_bits, int word) +{ + int i, wout = 0; + + for (i = 0; i < num_bits; i++) + wout |= (((word >> i) & 1) << (num_bits - i - 1)); + return wout; +} + +/*-------------------------------------------------------------------------* + * ED handling functions + *-------------------------------------------------------------------------*/ + /* link an ed into one of the HC chains */ static int ep_link (ohci_t *ohci, ed_t *edi) { volatile ed_t *ed = edi; + int int_branch; + int i; + int inter; + int interval; + int load; + __u32 * ed_p; ed->state = ED_OPER; + ed->int_interval = 0; switch (ed->type) { case PIPE_CONTROL: @@ -554,12 +662,49 @@ static int ep_link (ohci_t *ohci, ed_t *edi) } ohci->ed_bulktail = edi; break; + + case PIPE_INTERRUPT: + load = ed->int_load; + interval = ep_2_n_interval (ed->int_period); + ed->int_interval = interval; + int_branch = ep_int_ballance (ohci, interval, load); + ed->int_branch = int_branch; + + for (i = 0; i < ep_rev (6, interval); i += inter) { + inter = 1; + for (ed_p = &(ohci->hcca->int_table[ep_rev (5, i) + int_branch]); + (*ed_p != 0) && (((ed_t *)ed_p)->int_interval >= interval); + ed_p = &(((ed_t *)ed_p)->hwNextED)) + inter = ep_rev (6, ((ed_t *)ed_p)->int_interval); + ed->hwNextED = *ed_p; + *ed_p = m32_swap(ed); + } + break; } return 0; } /*-------------------------------------------------------------------------*/ +/* scan the periodic table to find and unlink this ED */ +static void periodic_unlink ( struct ohci *ohci, volatile struct ed *ed, + unsigned index, unsigned period) +{ + for (; index < NUM_INTS; index += period) { + __u32 *ed_p = &ohci->hcca->int_table [index]; + + /* ED might have been unlinked through another path */ + while (*ed_p != 0) { + if (((struct ed *)m32_swap (ed_p)) == ed) { + *ed_p = ed->hwNextED; + break; + } + ed_p = & (((struct ed *)m32_swap (ed_p))->hwNextED); + } + } +} + + /* unlink an ed from one of the HC chains. * just the link to the ed is unlinked. * the link from the ed still points to another operational ed or 0 @@ -568,6 +713,7 @@ static int ep_link (ohci_t *ohci, ed_t *edi) static int ep_unlink (ohci_t *ohci, ed_t *edi) { volatile ed_t *ed = edi; + int i; ed->hwINFO |= m32_swap (OHCI_ED_SKIP); @@ -605,6 +751,12 @@ static int ep_unlink (ohci_t *ohci, ed_t *edi) ((ed_t *)m32_swap (*((__u32 *)&ed->hwNextED)))->ed_prev = ed->ed_prev; } break; + + case PIPE_INTERRUPT: + periodic_unlink (ohci, ed, 0, 1); + for (i = ed->int_branch; i < 32; i += ed->int_interval) + ohci->ohci_int_load[i] -= ed->int_load; + break; } ed->state = ED_UNLINK; return 0; @@ -621,7 +773,8 @@ static int ep_unlink (ohci_t *ohci, ed_t *edi) * info fields are setted anyway even though most of them should not * change */ -static ed_t * ep_add_ed (struct usb_device *usb_dev, unsigned long pipe) +static ed_t * ep_add_ed (struct usb_device *usb_dev, unsigned long pipe, + int interval, int load) { td_t *td; ed_t *ed_ret; @@ -654,6 +807,11 @@ static ed_t * ep_add_ed (struct usb_device *usb_dev, unsigned long pipe) | usb_pipeslow (pipe) << 13 | usb_maxpacket (usb_dev, pipe) << 16); + if (ed->type == PIPE_INTERRUPT && ed->state == ED_UNLINK) { + ed->int_period = interval; + ed->int_load = load; + } + return ed_ret; } @@ -768,6 +926,13 @@ static void td_submit_job (struct usb_device *dev, unsigned long pipe, void *buf if (!ohci->sleeping) writel (OHCI_CLF, &ohci->regs->cmdstatus); /* start Control list */ break; + + case PIPE_INTERRUPT: + info = usb_pipeout (urb->pipe)? + TD_CC | TD_DP_OUT | toggle: + TD_CC | TD_R | TD_DP_IN | toggle; + td_fill (ohci, info, data, data_len, dev, cnt++, urb); + break; } if (urb->length != cnt) dbg("TD LENGTH %d != CNT %d", urb->length, cnt); @@ -783,7 +948,7 @@ static void td_submit_job (struct usb_device *dev, unsigned long pipe, void *buf static void dl_transfer_length(td_t * td) { __u32 tdINFO, tdBE, tdCBP; - urb_priv_t *lurb_priv = &urb_priv; + urb_priv_t *lurb_priv = td->ed->purb; tdINFO = m32_swap (td->hwINFO); tdBE = m32_swap (td->hwBE); @@ -820,7 +985,7 @@ static td_t * dl_reverse_done_list (ohci_t *ohci) td_list = (td_t *)td_list_hc; if (TD_CC_GET (m32_swap (td_list->hwINFO))) { - lurb_priv = &urb_priv; + lurb_priv = td_list->ed->purb; dbg(" USB-error/status: %x : %p", TD_CC_GET (m32_swap (td_list->hwINFO)), td_list); if (td_list->ed->hwHeadP & m32_swap (0x1)) { @@ -860,10 +1025,10 @@ static int dl_done_list (ohci_t *ohci, td_t *td_list) while (td_list) { td_list_next = td_list->next_dl_td; - lurb_priv = &urb_priv; tdINFO = m32_swap (td_list->hwINFO); ed = td_list->ed; + lurb_priv = ed->purb; dl_transfer_length(td_list); @@ -882,14 +1047,16 @@ static int dl_done_list (ohci_t *ohci, td_t *td_list) (lurb_priv->state != URB_DEL)) #else if ((ed->state & (ED_OPER | ED_UNLINK))) - urb_finished = 1; +#endif + lurb_priv->finished = sohci_return_job(ohci, + lurb_priv); else dbg("dl_done_list: strange.., ED state %x, ed->state\n"); } else dbg("dl_done_list: processing TD %x, len %x\n", lurb_priv->td_cnt, lurb_priv->length); -#endif - if (ed->state != ED_NEW) { + if (ed->state != ED_NEW && + (usb_pipetype (lurb_priv->pipe) != PIPE_INTERRUPT)) { edHeadP = m32_swap (ed->hwHeadP) & 0xfffffff0; edTailP = m32_swap (ed->hwTailP); @@ -1063,8 +1230,7 @@ static int ohci_submit_rh_msg(struct usb_device *dev, unsigned long pipe, __u16 wLength; #ifdef DEBUG -urb_priv.actual_length = 0; -pkt_print(dev, pipe, buffer, transfer_len, cmd, "SUB(rh)", usb_pipein(pipe)); +pkt_print(NULL, dev, pipe, buffer, transfer_len, cmd, "SUB(rh)", usb_pipein(pipe)); #else wait_ms(1); #endif @@ -1279,9 +1445,7 @@ pkt_print(dev, pipe, buffer, transfer_len, cmd, "SUB(rh)", usb_pipein(pipe)); dev->status = stat; #ifdef DEBUG - if (transfer_len) - urb_priv.actual_length = transfer_len; - pkt_print(dev, pipe, buffer, transfer_len, cmd, "RET(rh)", 0/*usb_pipein(pipe)*/); + pkt_print(NULL, dev, pipe, buffer, transfer_len, cmd, "RET(rh)", 0/*usb_pipein(pipe)*/); #else wait_ms(1); #endif @@ -1299,6 +1463,16 @@ int submit_common_msg(struct usb_device *dev, unsigned long pipe, void *buffer, int stat = 0; int maxsize = usb_maxpacket(dev, pipe); int timeout; + urb_priv_t *urb; + + urb = malloc(sizeof(urb_priv_t)); + memset(urb, 0, sizeof(urb_priv_t)); + + urb->dev = dev; + urb->pipe = pipe; + urb->transfer_buffer = buffer; + urb->transfer_buffer_length = transfer_len; + urb->interval = interval; /* device pulled? Shortcut the action. */ if (devgone == dev) { @@ -1307,8 +1481,8 @@ int submit_common_msg(struct usb_device *dev, unsigned long pipe, void *buffer, } #ifdef DEBUG - urb_priv.actual_length = 0; - pkt_print(dev, pipe, buffer, transfer_len, setup, "SUB", usb_pipein(pipe)); + urb->actual_length = 0; + pkt_print(urb, dev, pipe, buffer, transfer_len, setup, "SUB", usb_pipein(pipe)); #else wait_ms(1); #endif @@ -1318,7 +1492,7 @@ int submit_common_msg(struct usb_device *dev, unsigned long pipe, void *buffer, return -1; } - if (sohci_submit_job(dev, pipe, buffer, transfer_len, setup, interval) < 0) { + if (sohci_submit_job(urb, setup) < 0) { err("sohci_submit_job failed"); return -1; } @@ -1353,18 +1527,20 @@ int submit_common_msg(struct usb_device *dev, unsigned long pipe, void *buffer, * finished we need to re-iterate this loop so as * hc_interrupt() gets called again as there needs to be some * more TD's to process still */ - if ((stat >= 0) && (stat != 0xff) && (urb_finished)) { + if ((stat >= 0) && (stat != 0xff) && (urb->finished)) { /* 0xff is returned for an SF-interrupt */ break; } if (--timeout) { wait_ms(1); + if (!urb->finished) + dbg("\%"); + } else { err("CTL:TIMEOUT "); dbg("submit_common_msg: TO status %x\n", stat); - stat = USB_ST_CRC_ERR; - urb_finished = 1; + urb->finished = 1; stat = USB_ST_CRC_ERR; break; } @@ -1374,13 +1550,14 @@ int submit_common_msg(struct usb_device *dev, unsigned long pipe, void *buffer, dev->act_len = transfer_len; #ifdef DEBUG - pkt_print(dev, pipe, buffer, transfer_len, setup, "RET(ctlr)", usb_pipein(pipe)); + pkt_print(urb, dev, pipe, buffer, transfer_len, setup, "RET(ctlr)", usb_pipein(pipe)); #else wait_ms(1); #endif /* free TDs in urb_priv */ - urb_free_priv (&urb_priv); + if (usb_pipetype (pipe) != PIPE_INTERRUPT) + urb_free_priv (urb); return 0; } @@ -1399,8 +1576,7 @@ int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, info("submit_control_msg"); #ifdef DEBUG - urb_priv.actual_length = 0; - pkt_print(dev, pipe, buffer, transfer_len, setup, "SUB", usb_pipein(pipe)); + pkt_print(NULL, dev, pipe, buffer, transfer_len, setup, "SUB", usb_pipein(pipe)); #else wait_ms(1); #endif @@ -1423,7 +1599,8 @@ int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, int transfer_len, int interval) { info("submit_int_msg"); - return -1; + return submit_common_msg(dev, pipe, buffer, transfer_len, NULL, + interval); } /*-------------------------------------------------------------------------* @@ -1537,6 +1714,12 @@ static int hc_start (ohci_t * ohci) /*-------------------------------------------------------------------------*/ +/* Poll USB interrupt. */ +void usb_event_poll(void) +{ + hc_interrupt(); +} + /* an interrupt happens */ static int hc_interrupt (void) @@ -1587,8 +1770,10 @@ static int hc_interrupt (void) if (ints & OHCI_INTR_WDH) { wait_ms(1); writel (OHCI_INTR_WDH, ®s->intrdisable); + (void)readl (®s->intrdisable); /* flush */ stat = dl_done_list (&gohci, dl_reverse_done_list (&gohci)); writel (OHCI_INTR_WDH, ®s->intrenable); + (void)readl (®s->intrdisable); /* flush */ } if (ints & OHCI_INTR_SO) { @@ -1634,6 +1819,9 @@ static char ohci_inited = 0; int usb_lowlevel_init(void) { +#ifdef CONFIG_PCI_OHCI + pci_dev_t pdev; +#endif #ifdef CFG_USB_OHCI_CPU_INIT /* cpu dependant init */ @@ -1647,7 +1835,6 @@ int usb_lowlevel_init(void) return -1; #endif memset (&gohci, 0, sizeof (ohci_t)); - memset (&urb_priv, 0, sizeof (urb_priv_t)); /* align the storage */ if ((__u32)&ghcca[0] & 0xff) { @@ -1673,7 +1860,25 @@ int usb_lowlevel_init(void) gohci.disabled = 1; gohci.sleeping = 0; gohci.irq = -1; +#ifdef CONFIG_PCI_OHCI + pdev = pci_find_devices(ohci_pci_ids, 0); + + if (pdev != -1) { + u16 vid, did; + u32 base; + pci_read_config_word(pdev, PCI_VENDOR_ID, &vid); + pci_read_config_word(pdev, PCI_DEVICE_ID, &did); + printf("OHCI pci controller (%04x, %04x) found @(%d:%d:%d)\n", + vid, did, (pdev >> 16) & 0xff, + (pdev >> 11) & 0x1f, (pdev >> 8) & 0x7); + pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &base); + printf("OHCI regs address 0x%08x\n", base); + gohci.regs = (struct ohci_regs *)base; + } else + return -1; +#else gohci.regs = (struct ohci_regs *)CFG_USB_OHCI_REGS_BASE; +#endif gohci.flags = 0; gohci.slot_name = CFG_USB_OHCI_SLOT_NAME; @@ -1716,7 +1921,6 @@ int usb_lowlevel_init(void) ohci_dump (&gohci, 1); #else wait_ms(1); - urb_finished = 1; #endif ohci_inited = 1; return 0; |