aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/host/hc_crisv10.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/hc_crisv10.c')
-rw-r--r--drivers/usb/host/hc_crisv10.c4556
1 files changed, 4556 insertions, 0 deletions
diff --git a/drivers/usb/host/hc_crisv10.c b/drivers/usb/host/hc_crisv10.c
new file mode 100644
index 00000000000..4b12be822bd
--- /dev/null
+++ b/drivers/usb/host/hc_crisv10.c
@@ -0,0 +1,4556 @@
+/*
+ * usb-host.c: ETRAX 100LX USB Host Controller Driver (HCD)
+ *
+ * Copyright (c) 2002, 2003 Axis Communications AB.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/system.h>
+#include <asm/arch/svinto.h>
+
+#include <linux/usb.h>
+/* Ugly include because we don't live with the other host drivers. */
+#include <../drivers/usb/core/hcd.h>
+#include <../drivers/usb/core/usb.h>
+
+#include "hc_crisv10.h"
+
+#define ETRAX_USB_HC_IRQ USB_HC_IRQ_NBR
+#define ETRAX_USB_RX_IRQ USB_DMA_RX_IRQ_NBR
+#define ETRAX_USB_TX_IRQ USB_DMA_TX_IRQ_NBR
+
+static const char *usb_hcd_version = "$Revision: 1.2 $";
+
+#undef KERN_DEBUG
+#define KERN_DEBUG ""
+
+
+#undef USB_DEBUG_RH
+#undef USB_DEBUG_EPID
+#undef USB_DEBUG_SB
+#undef USB_DEBUG_DESC
+#undef USB_DEBUG_URB
+#undef USB_DEBUG_TRACE
+#undef USB_DEBUG_BULK
+#undef USB_DEBUG_CTRL
+#undef USB_DEBUG_INTR
+#undef USB_DEBUG_ISOC
+
+#ifdef USB_DEBUG_RH
+#define dbg_rh(format, arg...) printk(KERN_DEBUG __FILE__ ": (RH) " format "\n" , ## arg)
+#else
+#define dbg_rh(format, arg...) do {} while (0)
+#endif
+
+#ifdef USB_DEBUG_EPID
+#define dbg_epid(format, arg...) printk(KERN_DEBUG __FILE__ ": (EPID) " format "\n" , ## arg)
+#else
+#define dbg_epid(format, arg...) do {} while (0)
+#endif
+
+#ifdef USB_DEBUG_SB
+#define dbg_sb(format, arg...) printk(KERN_DEBUG __FILE__ ": (SB) " format "\n" , ## arg)
+#else
+#define dbg_sb(format, arg...) do {} while (0)
+#endif
+
+#ifdef USB_DEBUG_CTRL
+#define dbg_ctrl(format, arg...) printk(KERN_DEBUG __FILE__ ": (CTRL) " format "\n" , ## arg)
+#else
+#define dbg_ctrl(format, arg...) do {} while (0)
+#endif
+
+#ifdef USB_DEBUG_BULK
+#define dbg_bulk(format, arg...) printk(KERN_DEBUG __FILE__ ": (BULK) " format "\n" , ## arg)
+#else
+#define dbg_bulk(format, arg...) do {} while (0)
+#endif
+
+#ifdef USB_DEBUG_INTR
+#define dbg_intr(format, arg...) printk(KERN_DEBUG __FILE__ ": (INTR) " format "\n" , ## arg)
+#else
+#define dbg_intr(format, arg...) do {} while (0)
+#endif
+
+#ifdef USB_DEBUG_ISOC
+#define dbg_isoc(format, arg...) printk(KERN_DEBUG __FILE__ ": (ISOC) " format "\n" , ## arg)
+#else
+#define dbg_isoc(format, arg...) do {} while (0)
+#endif
+
+#ifdef USB_DEBUG_TRACE
+#define DBFENTER (printk(": Entering: %s\n", __FUNCTION__))
+#define DBFEXIT (printk(": Exiting: %s\n", __FUNCTION__))
+#else
+#define DBFENTER do {} while (0)
+#define DBFEXIT do {} while (0)
+#endif
+
+#define usb_pipeslow(pipe) (((pipe) >> 26) & 1)
+
+/*-------------------------------------------------------------------
+ Virtual Root Hub
+ -------------------------------------------------------------------*/
+
+static __u8 root_hub_dev_des[] =
+{
+ 0x12, /* __u8 bLength; */
+ 0x01, /* __u8 bDescriptorType; Device */
+ 0x00, /* __le16 bcdUSB; v1.0 */
+ 0x01,
+ 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */
+ 0x00, /* __u8 bDeviceSubClass; */
+ 0x00, /* __u8 bDeviceProtocol; */
+ 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */
+ 0x00, /* __le16 idVendor; */
+ 0x00,
+ 0x00, /* __le16 idProduct; */
+ 0x00,
+ 0x00, /* __le16 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, /* __le16 wTotalLength; */
+ 0x00,
+ 0x01, /* __u8 bNumInterfaces; */
+ 0x01, /* __u8 bConfigurationValue; */
+ 0x00, /* __u8 iConfiguration; */
+ 0x40, /* __u8 bmAttributes; Bit 7: Bus-powered */
+ 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 */
+ 0x08, /* __le16 ep_wMaxPacketSize; 8 Bytes */
+ 0x00,
+ 0xff /* __u8 ep_bInterval; 255 ms */
+};
+
+static __u8 root_hub_hub_des[] =
+{
+ 0x09, /* __u8 bLength; */
+ 0x29, /* __u8 bDescriptorType; Hub-descriptor */
+ 0x02, /* __u8 bNbrPorts; */
+ 0x00, /* __u16 wHubCharacteristics; */
+ 0x00,
+ 0x01, /* __u8 bPwrOn2pwrGood; 2ms */
+ 0x00, /* __u8 bHubContrCurrent; 0 mA */
+ 0x00, /* __u8 DeviceRemovable; *** 7 Ports max *** */
+ 0xff /* __u8 PortPwrCtrlMask; *** 7 ports max *** */
+};
+
+static struct timer_list bulk_start_timer = TIMER_INITIALIZER(NULL, 0, 0);
+static struct timer_list bulk_eot_timer = TIMER_INITIALIZER(NULL, 0, 0);
+
+/* We want the start timer to expire before the eot timer, because the former might start
+ traffic, thus making it unnecessary for the latter to time out. */
+#define BULK_START_TIMER_INTERVAL (HZ/10) /* 100 ms */
+#define BULK_EOT_TIMER_INTERVAL (HZ/10+2) /* 120 ms */
+
+#define OK(x) len = (x); dbg_rh("OK(%d): line: %d", x, __LINE__); break
+#define CHECK_ALIGN(x) if (((__u32)(x)) & 0x00000003) \
+{panic("Alignment check (DWORD) failed at %s:%s:%d\n", __FILE__, __FUNCTION__, __LINE__);}
+
+#define SLAB_FLAG (in_interrupt() ? SLAB_ATOMIC : SLAB_KERNEL)
+#define KMALLOC_FLAG (in_interrupt() ? GFP_ATOMIC : GFP_KERNEL)
+
+/* Most helpful debugging aid */
+#define assert(expr) ((void) ((expr) ? 0 : (err("assert failed at line %d",__LINE__))))
+
+/* Alternative assert define which stops after a failed assert. */
+/*
+#define assert(expr) \
+{ \
+ if (!(expr)) { \
+ err("assert failed at line %d",__LINE__); \
+ while (1); \
+ } \
+}
+*/
+
+
+/* FIXME: Should RX_BUF_SIZE be a config option, or maybe we should adjust it dynamically?
+ To adjust it dynamically we would have to get an interrupt when we reach the end
+ of the rx descriptor list, or when we get close to the end, and then allocate more
+ descriptors. */
+
+#define NBR_OF_RX_DESC 512
+#define RX_DESC_BUF_SIZE 1024
+#define RX_BUF_SIZE (NBR_OF_RX_DESC * RX_DESC_BUF_SIZE)
+
+/* The number of epids is, among other things, used for pre-allocating
+ ctrl, bulk and isoc EP descriptors (one for each epid).
+ Assumed to be > 1 when initiating the DMA lists. */
+#define NBR_OF_EPIDS 32
+
+/* Support interrupt traffic intervals up to 128 ms. */
+#define MAX_INTR_INTERVAL 128
+
+/* If periodic traffic (intr or isoc) is to be used, then one entry in the EP table
+ must be "invalid". By this we mean that we shouldn't care about epid attentions
+ for this epid, or at least handle them differently from epid attentions for "valid"
+ epids. This define determines which one to use (don't change it). */
+#define INVALID_EPID 31
+/* A special epid for the bulk dummys. */
+#define DUMMY_EPID 30
+
+/* This is just a software cache for the valid entries in R_USB_EPT_DATA. */
+static __u32 epid_usage_bitmask;
+
+/* A bitfield to keep information on in/out traffic is needed to uniquely identify
+ an endpoint on a device, since the most significant bit which indicates traffic
+ direction is lacking in the ep_id field (ETRAX epids can handle both in and
+ out traffic on endpoints that are otherwise identical). The USB framework, however,
+ relies on them to be handled separately. For example, bulk IN and OUT urbs cannot
+ be queued in the same list, since they would block each other. */
+static __u32 epid_out_traffic;
+
+/* DMA IN cache bug. Align the DMA IN buffers to 32 bytes, i.e. a cache line.
+ Since RX_DESC_BUF_SIZE is 1024 is a multiple of 32, all rx buffers will be cache aligned. */
+static volatile unsigned char RxBuf[RX_BUF_SIZE] __attribute__ ((aligned (32)));
+static volatile USB_IN_Desc_t RxDescList[NBR_OF_RX_DESC] __attribute__ ((aligned (4)));
+
+/* Pointers into RxDescList. */
+static volatile USB_IN_Desc_t *myNextRxDesc;
+static volatile USB_IN_Desc_t *myLastRxDesc;
+static volatile USB_IN_Desc_t *myPrevRxDesc;
+
+/* EP descriptors must be 32-bit aligned. */
+static volatile USB_EP_Desc_t TxCtrlEPList[NBR_OF_EPIDS] __attribute__ ((aligned (4)));
+static volatile USB_EP_Desc_t TxBulkEPList[NBR_OF_EPIDS] __attribute__ ((aligned (4)));
+/* After each enabled bulk EP (IN or OUT) we put two disabled EP descriptors with the eol flag set,
+ causing the DMA to stop the DMA channel. The first of these two has the intr flag set, which
+ gives us a dma8_sub0_descr interrupt. When we receive this, we advance the DMA one step in the
+ EP list and then restart the bulk channel, thus forcing a switch between bulk EP descriptors
+ in each frame. */
+static volatile USB_EP_Desc_t TxBulkDummyEPList[NBR_OF_EPIDS][2] __attribute__ ((aligned (4)));
+
+static volatile USB_EP_Desc_t TxIsocEPList[NBR_OF_EPIDS] __attribute__ ((aligned (4)));
+static volatile USB_SB_Desc_t TxIsocSB_zout __attribute__ ((aligned (4)));
+
+static volatile USB_EP_Desc_t TxIntrEPList[MAX_INTR_INTERVAL] __attribute__ ((aligned (4)));
+static volatile USB_SB_Desc_t TxIntrSB_zout __attribute__ ((aligned (4)));
+
+/* A zout transfer makes a memory access at the address of its buf pointer, which means that setting
+ this buf pointer to 0 will cause an access to the flash. In addition to this, setting sw_len to 0
+ results in a 16/32 bytes (depending on DMA burst size) transfer. Instead, we set it to 1, and point
+ it to this buffer. */
+static int zout_buffer[4] __attribute__ ((aligned (4)));
+
+/* Cache for allocating new EP and SB descriptors. */
+static kmem_cache_t *usb_desc_cache;
+
+/* Cache for the registers allocated in the top half. */
+static kmem_cache_t *top_half_reg_cache;
+
+/* Cache for the data allocated in the isoc descr top half. */
+static kmem_cache_t *isoc_compl_cache;
+
+static struct usb_bus *etrax_usb_bus;
+
+/* This is a circular (double-linked) list of the active urbs for each epid.
+ The head is never removed, and new urbs are linked onto the list as
+ urb_entry_t elements. Don't reference urb_list directly; use the wrapper
+ functions instead. Note that working with these lists might require spinlock
+ protection. */
+static struct list_head urb_list[NBR_OF_EPIDS];
+
+/* Read about the need and usage of this lock in submit_ctrl_urb. */
+static spinlock_t urb_list_lock;
+
+/* Used when unlinking asynchronously. */
+static struct list_head urb_unlink_list;
+
+/* for returning string descriptors in UTF-16LE */
+static int ascii2utf (char *ascii, __u8 *utf, int utfmax)
+{
+ int retval;
+
+ for (retval = 0; *ascii && utfmax > 1; utfmax -= 2, retval += 2) {
+ *utf++ = *ascii++ & 0x7f;
+ *utf++ = 0;
+ }
+ return retval;
+}
+
+static int usb_root_hub_string (int id, int serial, char *type, __u8 *data, int len)
+{
+ char buf [30];
+
+ // assert (len > (2 * (sizeof (buf) + 1)));
+ // assert (strlen (type) <= 8);
+
+ // language ids
+ if (id == 0) {
+ *data++ = 4; *data++ = 3; /* 4 bytes data */
+ *data++ = 0; *data++ = 0; /* some language id */
+ return 4;
+
+ // serial number
+ } else if (id == 1) {
+ sprintf (buf, "%x", serial);
+
+ // product description
+ } else if (id == 2) {
+ sprintf (buf, "USB %s Root Hub", type);
+
+ // id 3 == vendor description
+
+ // unsupported IDs --> "stall"
+ } else
+ return 0;
+
+ data [0] = 2 + ascii2utf (buf, data + 2, len - 2);
+ data [1] = 3;
+ return data [0];
+}
+
+/* Wrappers around the list functions (include/linux/list.h). */
+
+static inline int urb_list_empty(int epid)
+{
+ return list_empty(&urb_list[epid]);
+}
+
+/* Returns first urb for this epid, or NULL if list is empty. */
+static inline struct urb *urb_list_first(int epid)
+{
+ struct urb *first_urb = 0;
+
+ if (!urb_list_empty(epid)) {
+ /* Get the first urb (i.e. head->next). */
+ urb_entry_t *urb_entry = list_entry((&urb_list[epid])->next, urb_entry_t, list);
+ first_urb = urb_entry->urb;
+ }
+ return first_urb;
+}
+
+/* Adds an urb_entry last in the list for this epid. */
+static inline void urb_list_add(struct urb *urb, int epid)
+{
+ urb_entry_t *urb_entry = (urb_entry_t *)kmalloc(sizeof(urb_entry_t), KMALLOC_FLAG);
+ assert(urb_entry);
+
+ urb_entry->urb = urb;
+ list_add_tail(&urb_entry->list, &urb_list[epid]);
+}
+
+/* Search through the list for an element that contains this urb. (The list
+ is expected to be short and the one we are about to delete will often be
+ the first in the list.) */
+static inline urb_entry_t *__urb_list_entry(struct urb *urb, int epid)
+{
+ struct list_head *entry;
+ struct list_head *tmp;
+ urb_entry_t *urb_entry;
+
+ list_for_each_safe(entry, tmp, &urb_list[epid]) {
+ urb_entry = list_entry(entry, urb_entry_t, list);
+ assert(urb_entry);
+ assert(urb_entry->urb);
+
+ if (urb_entry->urb == urb) {
+ return urb_entry;
+ }
+ }
+ return 0;
+}
+
+/* Delete an urb from the list. */
+static inline void urb_list_del(struct urb *urb, int epid)
+{
+ urb_entry_t *urb_entry = __urb_list_entry(urb, epid);
+ assert(urb_entry);
+
+ /* Delete entry and free. */
+ list_del(&urb_entry->list);
+ kfree(urb_entry);
+}
+
+/* Move an urb to the end of the list. */
+static inline void urb_list_move_last(struct urb *urb, int epid)
+{
+ urb_entry_t *urb_entry = __urb_list_entry(urb, epid);
+ assert(urb_entry);
+
+ list_del(&urb_entry->list);
+ list_add_tail(&urb_entry->list, &urb_list[epid]);
+}
+
+/* Get the next urb in the list. */
+static inline struct urb *urb_list_next(struct urb *urb, int epid)
+{
+ urb_entry_t *urb_entry = __urb_list_entry(urb, epid);
+
+ assert(urb_entry);
+
+ if (urb_entry->list.next != &urb_list[epid]) {
+ struct list_head *elem = urb_entry->list.next;
+ urb_entry = list_entry(elem, urb_entry_t, list);
+ return urb_entry->urb;
+ } else {
+ return NULL;
+ }
+}
+
+
+
+/* For debug purposes only. */
+static inline void urb_list_dump(int epid)
+{
+ struct list_head *entry;
+ struct list_head *tmp;
+ urb_entry_t *urb_entry;
+ int i = 0;
+
+ info("Dumping urb list for epid %d", epid);
+
+ list_for_each_safe(entry, tmp, &urb_list[epid]) {
+ urb_entry = list_entry(entry, urb_entry_t, list);
+ info(" entry %d, urb = 0x%lx", i, (unsigned long)urb_entry->urb);
+ }
+}
+
+static void init_rx_buffers(void);
+static int etrax_rh_unlink_urb(struct urb *urb);
+static void etrax_rh_send_irq(struct urb *urb);
+static void etrax_rh_init_int_timer(struct urb *urb);
+static void etrax_rh_int_timer_do(unsigned long ptr);
+
+static int etrax_usb_setup_epid(struct urb *urb);
+static int etrax_usb_lookup_epid(struct urb *urb);
+static int etrax_usb_allocate_epid(void);
+static void etrax_usb_free_epid(int epid);
+
+static int etrax_remove_from_sb_list(struct urb *urb);
+
+static void* etrax_usb_buffer_alloc(struct usb_bus* bus, size_t size, int mem_flags, dma_addr_t *dma);
+static void etrax_usb_buffer_free(struct usb_bus *bus, size_t size, void *addr, dma_addr_t dma);
+
+static void etrax_usb_add_to_bulk_sb_list(struct urb *urb, int epid);
+static void etrax_usb_add_to_ctrl_sb_list(struct urb *urb, int epid);
+static void etrax_usb_add_to_intr_sb_list(struct urb *urb, int epid);
+static void etrax_usb_add_to_isoc_sb_list(struct urb *urb, int epid);
+
+static int etrax_usb_submit_bulk_urb(struct urb *urb);
+static int etrax_usb_submit_ctrl_urb(struct urb *urb);
+static int etrax_usb_submit_intr_urb(struct urb *urb);
+static int etrax_usb_submit_isoc_urb(struct urb *urb);
+
+static int etrax_usb_submit_urb(struct urb *urb, int mem_flags);
+static int etrax_usb_unlink_urb(struct urb *urb, int status);
+static int etrax_usb_get_frame_number(struct usb_device *usb_dev);
+
+static irqreturn_t etrax_usb_tx_interrupt(int irq, void *vhc, struct pt_regs *regs);
+static irqreturn_t etrax_usb_rx_interrupt(int irq, void *vhc, struct pt_regs *regs);
+static irqreturn_t etrax_usb_hc_interrupt_top_half(int irq, void *vhc, struct pt_regs *regs);
+static void etrax_usb_hc_interrupt_bottom_half(void *data);
+
+static void etrax_usb_isoc_descr_interrupt_bottom_half(void *data);
+
+
+/* The following is a list of interrupt handlers for the host controller interrupts we use.
+ They are called from etrax_usb_hc_interrupt_bottom_half. */
+static void etrax_usb_hc_isoc_eof_interrupt(void);
+static void etrax_usb_hc_bulk_eot_interrupt(int timer_induced);
+static void etrax_usb_hc_epid_attn_interrupt(usb_interrupt_registers_t *reg);
+static void etrax_usb_hc_port_status_interrupt(usb_interrupt_registers_t *reg);
+static void etrax_usb_hc_ctl_status_interrupt(usb_interrupt_registers_t *reg);
+
+static int etrax_rh_submit_urb (struct urb *urb);
+
+/* Forward declaration needed because they are used in the rx interrupt routine. */
+static void etrax_usb_complete_urb(struct urb *urb, int status);
+static void etrax_usb_complete_bulk_urb(struct urb *urb, int status);
+static void etrax_usb_complete_ctrl_urb(struct urb *urb, int status);
+static void etrax_usb_complete_intr_urb(struct urb *urb, int status);
+static void etrax_usb_complete_isoc_urb(struct urb *urb, int status);
+
+static int etrax_usb_hc_init(void);
+static void etrax_usb_hc_cleanup(void);
+
+static struct usb_operations etrax_usb_device_operations =
+{
+ .get_frame_number = etrax_usb_get_frame_number,
+ .submit_urb = etrax_usb_submit_urb,
+ .unlink_urb = etrax_usb_unlink_urb,
+ .buffer_alloc = etrax_usb_buffer_alloc,
+ .buffer_free = etrax_usb_buffer_free
+};
+
+/* Note that these functions are always available in their "__" variants, for use in
+ error situations. The "__" missing variants are controlled by the USB_DEBUG_DESC/
+ USB_DEBUG_URB macros. */
+static void __dump_urb(struct urb* purb)
+{
+ printk("\nurb :0x%08lx\n", (unsigned long)purb);
+ printk("dev :0x%08lx\n", (unsigned long)purb->dev);
+ printk("pipe :0x%08x\n", purb->pipe);
+ printk("status :%d\n", purb->status);
+ printk("transfer_flags :0x%08x\n", purb->transfer_flags);
+ printk("transfer_buffer :0x%08lx\n", (unsigned long)purb->transfer_buffer);
+ printk("transfer_buffer_length:%d\n", purb->transfer_buffer_length);
+ printk("actual_length :%d\n", purb->actual_length);
+ printk("setup_packet :0x%08lx\n", (unsigned long)purb->setup_packet);
+ printk("start_frame :%d\n", purb->start_frame);
+ printk("number_of_packets :%d\n", purb->number_of_packets);
+ printk("interval :%d\n", purb->interval);
+ printk("error_count :%d\n", purb->error_count);
+ printk("context :0x%08lx\n", (unsigned long)purb->context);
+ printk("complete :0x%08lx\n\n", (unsigned long)purb->complete);
+}
+
+static void __dump_in_desc(volatile USB_IN_Desc_t *in)
+{
+ printk("\nUSB_IN_Desc at 0x%08lx\n", (unsigned long)in);
+ printk(" sw_len : 0x%04x (%d)\n", in->sw_len, in->sw_len);
+ printk(" command : 0x%04x\n", in->command);
+ printk(" next : 0x%08lx\n", in->next);
+ printk(" buf : 0x%08lx\n", in->buf);
+ printk(" hw_len : 0x%04x (%d)\n", in->hw_len, in->hw_len);
+ printk(" status : 0x%04x\n\n", in->status);
+}
+
+static void __dump_sb_desc(volatile USB_SB_Desc_t *sb)
+{
+ char tt = (sb->command & 0x30) >> 4;
+ char *tt_string;
+
+ switch (tt) {
+ case 0:
+ tt_string = "zout";
+ break;
+ case 1:
+ tt_string = "in";
+ break;
+ case 2:
+ tt_string = "out";
+ break;
+ case 3:
+ tt_string = "setup";
+ break;
+ default:
+ tt_string = "unknown (weird)";
+ }
+
+ printk("\n USB_SB_Desc at 0x%08lx\n", (unsigned long)sb);
+ printk(" command : 0x%04x\n", sb->command);
+ printk(" rem : %d\n", (sb->command & 0x3f00) >> 8);
+ printk(" full : %d\n", (sb->command & 0x40) >> 6);
+ printk(" tt : %d (%s)\n", tt, tt_string);
+ printk(" intr : %d\n", (sb->command & 0x8) >> 3);
+ printk(" eot : %d\n", (sb->command & 0x2) >> 1);
+ printk(" eol : %d\n", sb->command & 0x1);
+ printk(" sw_len : 0x%04x (%d)\n", sb->sw_len, sb->sw_len);
+ printk(" next : 0x%08lx\n", sb->next);
+ printk(" buf : 0x%08lx\n\n", sb->buf);
+}
+
+
+static void __dump_ep_desc(volatile USB_EP_Desc_t *ep)
+{
+ printk("\nUSB_EP_Desc at 0x%08lx\n", (unsigned long)ep);
+ printk(" command : 0x%04x\n", ep->command);
+ printk(" ep_id : %d\n", (ep->command & 0x1f00) >> 8);
+ printk(" enable : %d\n", (ep->command & 0x10) >> 4);
+ printk(" intr : %d\n", (ep->command & 0x8) >> 3);
+ printk(" eof : %d\n", (ep->command & 0x2) >> 1);
+ printk(" eol : %d\n", ep->command & 0x1);
+ printk(" hw_len : 0x%04x (%d)\n", ep->hw_len, ep->hw_len);
+ printk(" next : 0x%08lx\n", ep->next);
+ printk(" sub : 0x%08lx\n\n", ep->sub);
+}
+
+static inline void __dump_ep_list(int pipe_type)
+{
+ volatile USB_EP_Desc_t *ep;
+ volatile USB_EP_Desc_t *first_ep;
+ volatile USB_SB_Desc_t *sb;
+
+ switch (pipe_type)
+ {
+ case PIPE_BULK:
+ first_ep = &TxBulkEPList[0];
+ break;
+ case PIPE_CONTROL:
+ first_ep = &TxCtrlEPList[0];
+ break;
+ case PIPE_INTERRUPT:
+ first_ep = &TxIntrEPList[0];
+ break;
+ case PIPE_ISOCHRONOUS:
+ first_ep = &TxIsocEPList[0];
+ break;
+ default:
+ warn("Cannot dump unknown traffic type");
+ return;
+ }
+ ep = first_ep;
+
+ printk("\n\nDumping EP list...\n\n");
+
+ do {
+ __dump_ep_desc(ep);
+ /* Cannot phys_to_virt on 0 as it turns into 80000000, which is != 0. */
+ sb = ep->sub ? phys_to_virt(ep->sub) : 0;
+ while (sb) {
+ __dump_sb_desc(sb);
+ sb = sb->next ? phys_to_virt(sb->next) : 0;
+ }
+ ep = (volatile USB_EP_Desc_t *)(phys_to_virt(ep->next));
+
+ } while (ep != first_ep);
+}
+
+static inline void __dump_ept_data(int epid)
+{
+ unsigned long flags;
+ __u32 r_usb_ept_data;
+
+ if (epid < 0 || epid > 31) {
+ printk("Cannot dump ept data for invalid epid %d\n", epid);
+ return;
+ }
+
+ save_flags(flags);
+ cli();
+ *R_USB_EPT_INDEX = IO_FIELD(R_USB_EPT_INDEX, value, epid);
+ nop();
+ r_usb_ept_data = *R_USB_EPT_DATA;
+ restore_flags(flags);
+
+ printk("\nR_USB_EPT_DATA = 0x%x for epid %d :\n", r_usb_ept_data, epid);
+ if (r_usb_ept_data == 0) {
+ /* No need for more detailed printing. */
+ return;
+ }
+ printk(" valid : %d\n", (r_usb_ept_data & 0x80000000) >> 31);
+ printk(" hold : %d\n", (r_usb_ept_data & 0x40000000) >> 30);
+ printk(" error_count_in : %d\n", (r_usb_ept_data & 0x30000000) >> 28);
+ printk(" t_in : %d\n", (r_usb_ept_data & 0x08000000) >> 27);
+ printk(" low_speed : %d\n", (r_usb_ept_data & 0x04000000) >> 26);
+ printk(" port : %d\n", (r_usb_ept_data & 0x03000000) >> 24);
+ printk(" error_code : %d\n", (r_usb_ept_data & 0x00c00000) >> 22);
+ printk(" t_out : %d\n", (r_usb_ept_data & 0x00200000) >> 21);
+ printk(" error_count_out : %d\n", (r_usb_ept_data & 0x00180000) >> 19);
+ printk(" max_len : %d\n", (r_usb_ept_data & 0x0003f800) >> 11);
+ printk(" ep : %d\n", (r_usb_ept_data & 0x00000780) >> 7);
+ printk(" dev : %d\n", (r_usb_ept_data & 0x0000003f));
+}
+
+static inline void __dump_ept_data_list(void)
+{
+ int i;
+
+ printk("Dumping the whole R_USB_EPT_DATA list\n");
+
+ for (i = 0; i < 32; i++) {
+ __dump_ept_data(i);
+ }
+}
+#ifdef USB_DEBUG_DESC
+#define dump_in_desc(...) __dump_in_desc(...)
+#define dump_sb_desc(...) __dump_sb_desc(...)
+#define dump_ep_desc(...) __dump_ep_desc(...)
+#else
+#define dump_in_desc(...) do {} while (0)
+#define dump_sb_desc(...) do {} while (0)
+#define dump_ep_desc(...) do {} while (0)
+#endif
+
+#ifdef USB_DEBUG_URB
+#define dump_urb(x) __dump_urb(x)
+#else
+#define dump_urb(x) do {} while (0)
+#endif
+
+static void init_rx_buffers(void)
+{
+ int i;
+
+ DBFENTER;
+
+ for (i = 0; i < (NBR_OF_RX_DESC - 1); i++) {
+ RxDescList[i].sw_len = RX_DESC_BUF_SIZE;
+ RxDescList[i].command = 0;
+ RxDescList[i].next = virt_to_phys(&RxDescList[i + 1]);
+ RxDescList[i].buf = virt_to_phys(RxBuf + (i * RX_DESC_BUF_SIZE));
+ RxDescList[i].hw_len = 0;
+ RxDescList[i].status = 0;
+
+ /* DMA IN cache bug. (struct etrax_dma_descr has the same layout as USB_IN_Desc
+ for the relevant fields.) */
+ prepare_rx_descriptor((struct etrax_dma_descr*)&RxDescList[i]);
+
+ }
+
+ RxDescList[i].sw_len = RX_DESC_BUF_SIZE;
+ RxDescList[i].command = IO_STATE(USB_IN_command, eol, yes);
+ RxDescList[i].next = virt_to_phys(&RxDescList[0]);
+ RxDescList[i].buf = virt_to_phys(RxBuf + (i * RX_DESC_BUF_SIZE));
+ RxDescList[i].hw_len = 0;
+ RxDescList[i].status = 0;
+
+ myNextRxDesc = &RxDescList[0];
+ myLastRxDesc = &RxDescList[NBR_OF_RX_DESC - 1];
+ myPrevRxDesc = &RxDescList[NBR_OF_RX_DESC - 1];
+
+ *R_DMA_CH9_FIRST = virt_to_phys(myNextRxDesc);
+ *R_DMA_CH9_CMD = IO_STATE(R_DMA_CH9_CMD, cmd, start);
+
+ DBFEXIT;
+}
+
+static void init_tx_bulk_ep(void)
+{
+ int i;
+
+ DBFENTER;
+
+ for (i = 0; i < (NBR_OF_EPIDS - 1); i++) {
+ CHECK_ALIGN(&TxBulkEPList[i]);
+ TxBulkEPList[i].hw_len = 0;
+ TxBulkEPList[i].command = IO_FIELD(USB_EP_command, epid, i);
+ TxBulkEPList[i].sub = 0;
+ TxBulkEPList[i].next = virt_to_phys(&TxBulkEPList[i + 1]);
+
+ /* Initiate two EPs, disabled and with the eol flag set. No need for any
+ preserved epid. */
+
+ /* The first one has the intr flag set so we get an interrupt when the DMA
+ channel is about to become disabled. */
+ CHECK_ALIGN(&TxBulkDummyEPList[i][0]);
+ TxBulkDummyEPList[i][0].hw_len = 0;
+ TxBulkDummyEPList[i][0].command = (IO_FIELD(USB_EP_command, epid, DUMMY_EPID) |
+ IO_STATE(USB_EP_command, eol, yes) |
+ IO_STATE(USB_EP_command, intr, yes));
+ TxBulkDummyEPList[i][0].sub = 0;
+ TxBulkDummyEPList[i][0].next = virt_to_phys(&TxBulkDummyEPList[i][1]);
+
+ /* The second one. */
+ CHECK_ALIGN(&TxBulkDummyEPList[i][1]);
+ TxBulkDummyEPList[i][1].hw_len = 0;
+ TxBulkDummyEPList[i][1].command = (IO_FIELD(USB_EP_command, epid, DUMMY_EPID) |
+ IO_STATE(USB_EP_command, eol, yes));
+ TxBulkDummyEPList[i][1].sub = 0;
+ /* The last dummy's next pointer is the same as the current EP's next pointer. */
+ TxBulkDummyEPList[i][1].next = virt_to_phys(&TxBulkEPList[i + 1]);
+ }
+
+ /* Configure the last one. */
+ CHECK_ALIGN(&TxBulkEPList[i]);
+ TxBulkEPList[i].hw_len = 0;
+ TxBulkEPList[i].command = (IO_STATE(USB_EP_command, eol, yes) |
+ IO_FIELD(USB_EP_command, epid, i));
+ TxBulkEPList[i].sub = 0;
+ TxBulkEPList[i].next = virt_to_phys(&TxBulkEPList[0]);
+
+ /* No need configuring dummy EPs for the last one as it will never be used for
+ bulk traffic (i == INVALD_EPID at this point). */
+
+ /* Set up to start on the last EP so we will enable it when inserting traffic
+ for the first time (imitating the situation where the DMA has stopped
+ because there was no more traffic). */
+ *R_DMA_CH8_SUB0_EP = virt_to_phys(&TxBulkEPList[i]);
+ /* No point in starting the bulk channel yet.
+ *R_DMA_CH8_SUB0_CMD = IO_STATE(R_DMA_CH8_SUB0_CMD, cmd, start); */
+ DBFEXIT;
+}
+
+static void init_tx_ctrl_ep(void)
+{
+ int i;
+
+ DBFENTER;
+
+ for (i = 0; i < (NBR_OF_EPIDS - 1); i++) {
+ CHECK_ALIGN(&TxCtrlEPList[i]);
+ TxCtrlEPList[i].hw_len = 0;
+ TxCtrlEPList[i].command = IO_FIELD(USB_EP_command, epid, i);
+ TxCtrlEPList[i].sub = 0;
+ TxCtrlEPList[i].next = virt_to_phys(&TxCtrlEPList[i + 1]);
+ }
+
+ CHECK_ALIGN(&TxCtrlEPList[i]);
+ TxCtrlEPList[i].hw_len = 0;
+ TxCtrlEPList[i].command = (IO_STATE(USB_EP_command, eol, yes) |
+ IO_FIELD(USB_EP_command, epid, i));
+
+ TxCtrlEPList[i].sub = 0;
+ TxCtrlEPList[i].next = virt_to_phys(&TxCtrlEPList[0]);
+
+ *R_DMA_CH8_SUB1_EP = virt_to_phys(&TxCtrlEPList[0]);
+ *R_DMA_CH8_SUB1_CMD = IO_STATE(R_DMA_CH8_SUB1_CMD, cmd, start);
+
+ DBFEXIT;
+}
+
+
+static void init_tx_intr_ep(void)
+{
+ int i;
+
+ DBFENTER;
+
+ /* Read comment at zout_buffer declaration for an explanation to this. */
+ TxIntrSB_zout.sw_len = 1;
+ TxIntrSB_zout.next = 0;
+ TxIntrSB_zout.buf = virt_to_phys(&zout_buffer[0]);
+ TxIntrSB_zout.command = (IO_FIELD(USB_SB_command, rem, 0) |
+ IO_STATE(USB_SB_command, tt, zout) |
+ IO_STATE(USB_SB_command, full, yes) |
+ IO_STATE(USB_SB_command, eot, yes) |
+ IO_STATE(USB_SB_command, eol, yes));
+
+ for (i = 0; i < (MAX_INTR_INTERVAL - 1); i++) {
+ CHECK_ALIGN(&TxIntrEPList[i]);
+ TxIntrEPList[i].hw_len = 0;
+ TxIntrEPList[i].command =
+ (IO_STATE(USB_EP_command, eof, yes) |
+ IO_STATE(USB_EP_command, enable, yes) |
+ IO_FIELD(USB_EP_command, epid, INVALID_EPID));
+ TxIntrEPList[i].sub = virt_to_phys(&TxIntrSB_zout);
+ TxIntrEPList[i].next = virt_to_phys(&TxIntrEPList[i + 1]);
+ }
+
+ CHECK_ALIGN(&TxIntrEPList[i]);
+ TxIntrEPList[i].hw_len = 0;
+ TxIntrEPList[i].command =
+ (IO_STATE(USB_EP_command, eof, yes) |
+ IO_STATE(USB_EP_command, eol, yes) |
+ IO_STATE(USB_EP_command, enable, yes) |
+ IO_FIELD(USB_EP_command, epid, INVALID_EPID));
+ TxIntrEPList[i].sub = virt_to_phys(&TxIntrSB_zout);
+ TxIntrEPList[i].next = virt_to_phys(&TxIntrEPList[0]);
+
+ *R_DMA_CH8_SUB2_EP = virt_to_phys(&TxIntrEPList[0]);
+ *R_DMA_CH8_SUB2_CMD = IO_STATE(R_DMA_CH8_SUB2_CMD, cmd, start);
+ DBFEXIT;
+}
+
+static void init_tx_isoc_ep(void)
+{
+ int i;
+
+ DBFENTER;
+
+ /* Read comment at zout_buffer declaration for an explanation to this. */
+ TxIsocSB_zout.sw_len = 1;
+ TxIsocSB_zout.next = 0;
+ TxIsocSB_zout.buf = virt_to_phys(&zout_buffer[0]);
+ TxIsocSB_zout.command = (IO_FIELD(USB_SB_command, rem, 0) |
+ IO_STATE(USB_SB_command, tt, zout) |
+ IO_STATE(USB_SB_command, full, yes) |
+ IO_STATE(USB_SB_command, eot, yes) |
+ IO_STATE(USB_SB_command, eol, yes));
+
+ /* The last isochronous EP descriptor is a dummy. */
+
+ for (i = 0; i < (NBR_OF_EPIDS - 1); i++) {
+ CHECK_ALIGN(&TxIsocEPList[i]);
+ TxIsocEPList[i].hw_len = 0;
+ TxIsocEPList[i].command = IO_FIELD(USB_EP_command, epid, i);
+ TxIsocEPList[i].sub = 0;
+ TxIsocEPList[i].next = virt_to_phys(&TxIsocEPList[i + 1]);
+ }
+
+ CHECK_ALIGN(&TxIsocEPList[i]);
+ TxIsocEPList[i].hw_len = 0;
+
+ /* Must enable the last EP descr to get eof interrupt. */
+ TxIsocEPList[i].command = (IO_STATE(USB_EP_command, enable, yes) |
+ IO_STATE(USB_EP_command, eof, yes) |
+ IO_STATE(USB_EP_command, eol, yes) |
+ IO_FIELD(USB_EP_command, epid, INVALID_EPID));
+ TxIsocEPList[i].sub = virt_to_phys(&TxIsocSB_zout);
+ TxIsocEPList[i].next = virt_to_phys(&TxIsocEPList[0]);
+
+ *R_DMA_CH8_SUB3_EP = virt_to_phys(&TxIsocEPList[0]);
+ *R_DMA_CH8_SUB3_CMD = IO_STATE(R_DMA_CH8_SUB3_CMD, cmd, start);
+
+ DBFEXIT;
+}
+
+static void etrax_usb_unlink_intr_urb(struct urb *urb)
+{
+ volatile USB_EP_Desc_t *first_ep; /* First EP in the list. */
+ volatile USB_EP_Desc_t *curr_ep; /* Current EP, the iterator. */
+ volatile USB_EP_Desc_t *next_ep; /* The EP after current. */
+ volatile USB_EP_Desc_t *unlink_ep; /* The one we should remove from the list. */
+
+ int epid;
+
+ /* Read 8.8.4 in Designer's Reference, "Removing an EP Descriptor from the List". */
+
+ DBFENTER;
+
+ epid = ((etrax_urb_priv_t *)urb->hcpriv)->epid;
+
+ first_ep = &TxIntrEPList[0];
+ curr_ep = first_ep;
+
+
+ /* Note that this loop removes all EP descriptors with this epid. This assumes
+ that all EP descriptors belong to the one and only urb for this epid. */
+
+ do {
+ next_ep = (USB_EP_Desc_t *)phys_to_virt(curr_ep->next);
+
+ if (IO_EXTRACT(USB_EP_command, epid, next_ep->command) == epid) {
+
+ dbg_intr("Found EP to unlink for epid %d", epid);
+
+ /* This is the one we should unlink. */
+ unlink_ep = next_ep;
+
+ /* Actually unlink the EP from the DMA list. */
+ curr_ep->next = unlink_ep->next;
+
+ /* Wait until the DMA is no longer at this descriptor. */
+ while (*R_DMA_CH8_SUB2_EP == virt_to_phys(unlink_ep));
+
+ /* Now we are free to remove it and its SB descriptor.
+ Note that it is assumed here that there is only one sb in the
+ sb list for this ep. */
+ kmem_cache_free(usb_desc_cache, phys_to_virt(unlink_ep->sub));
+ kmem_cache_free(usb_desc_cache, (USB_EP_Desc_t *)unlink_ep);
+ }
+
+ curr_ep = phys_to_virt(curr_ep->next);
+
+ } while (curr_ep != first_ep);
+ urb->hcpriv = NULL;
+}
+
+void etrax_usb_do_intr_recover(int epid)
+{
+ USB_EP_Desc_t *first_ep, *tmp_ep;
+
+ DBFENTER;
+
+ first_ep = (USB_EP_Desc_t *)phys_to_virt(*R_DMA_CH8_SUB2_EP);
+ tmp_ep = first_ep;
+
+ /* What this does is simply to walk the list of interrupt
+ ep descriptors and enable those that are disabled. */
+
+ do {
+ if (IO_EXTRACT(USB_EP_command, epid, tmp_ep->command) == epid &&
+ !(tmp_ep->command & IO_MASK(USB_EP_command, enable))) {
+ tmp_ep->command |= IO_STATE(USB_EP_command, enable, yes);
+ }
+
+ tmp_ep = (USB_EP_Desc_t *)phys_to_virt(tmp_ep->next);
+
+ } while (tmp_ep != first_ep);
+
+
+ DBFEXIT;
+}
+
+static int etrax_rh_unlink_urb (struct urb *urb)
+{
+ etrax_hc_t *hc;
+
+ DBFENTER;
+
+ hc = urb->dev->bus->hcpriv;
+
+ if (hc->rh.urb == urb) {
+ hc->rh.send = 0;
+ del_timer(&hc->rh.rh_int_timer);
+ }
+
+ DBFEXIT;
+ return 0;
+}
+
+static void etrax_rh_send_irq(struct urb *urb)
+{
+ __u16 data = 0;
+ etrax_hc_t *hc = urb->dev->bus->hcpriv;
+ DBFENTER;
+
+/*
+ dbg_rh("R_USB_FM_NUMBER : 0x%08X", *R_USB_FM_NUMBER);
+ dbg_rh("R_USB_FM_REMAINING: 0x%08X", *R_USB_FM_REMAINING);
+*/
+
+ data |= (hc->rh.wPortChange_1) ? (1 << 1) : 0;
+ data |= (hc->rh.wPortChange_2) ? (1 << 2) : 0;
+
+ *((__u16 *)urb->transfer_buffer) = cpu_to_le16(data);
+ /* FIXME: Why is actual_length set to 1 when data is 2 bytes?
+ Since only 1 byte is used, why not declare data as __u8? */
+ urb->actual_length = 1;
+ urb->status = 0;
+
+ if (hc->rh.send && urb->complete) {
+ dbg_rh("wPortChange_1: 0x%04X", hc->rh.wPortChange_1);
+ dbg_rh("wPortChange_2: 0x%04X", hc->rh.wPortChange_2);
+
+ urb->complete(urb, NULL);
+ }
+
+ DBFEXIT;
+}
+
+static void etrax_rh_init_int_timer(struct urb *urb)
+{
+ etrax_hc_t *hc;
+
+ DBFENTER;
+
+ hc = urb->dev->bus->hcpriv;
+ hc->rh.interval = urb->interval;
+ init_timer(&hc->rh.rh_int_timer);
+ hc->rh.rh_int_timer.function = etrax_rh_int_timer_do;
+ hc->rh.rh_int_timer.data = (unsigned long)urb;
+ /* FIXME: Is the jiffies resolution enough? All intervals < 10 ms will be mapped
+ to 0, and the rest to the nearest lower 10 ms. */
+ hc->rh.rh_int_timer.expires = jiffies + ((HZ * hc->rh.interval) / 1000);
+ add_timer(&hc->rh.rh_int_timer);
+
+ DBFEXIT;
+}
+
+static void etrax_rh_int_timer_do(unsigned long ptr)
+{
+ struct urb *urb;
+ etrax_hc_t *hc;
+
+ DBFENTER;
+
+ urb = (struct urb*)ptr;
+ hc = urb->dev->bus->hcpriv;
+
+ if (hc->rh.send) {
+ etrax_rh_