diff options
-rw-r--r-- | drivers/usb/host/Kconfig | 26 | ||||
-rw-r--r-- | drivers/usb/host/Makefile | 4 | ||||
-rw-r--r-- | drivers/usb/host/isp1760-hcd.c | 2231 | ||||
-rw-r--r-- | drivers/usb/host/isp1760-hcd.h | 206 | ||||
-rw-r--r-- | drivers/usb/host/isp1760-if.c | 298 |
5 files changed, 2764 insertions, 1 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 0b87480dd71..acac56852b4 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -95,6 +95,32 @@ config USB_ISP116X_HCD To compile this driver as a module, choose M here: the module will be called isp116x-hcd. +config USB_ISP1760_HCD + tristate "ISP 1760 HCD support" + depends on USB && EXPERIMENTAL + ---help--- + The ISP1760 chip is a USB 2.0 host controller. + + This driver does not support isochronous transfers or OTG. + + To compile this driver as a module, choose M here: the + module will be called isp1760-hcd. + +config USB_ISP1760_PCI + bool "Support for the PCI bus" + depends on USB_ISP1760_HCD && PCI + ---help--- + Enables support for the device present on the PCI bus. + This should only be required if you happen to have the eval kit from + NXP and you are going to test it. + +config USB_ISP1760_OF + bool "Support for the OF platform bus" + depends on USB_ISP1760_HCD && OF + ---help--- + Enables support for the device present on the PowerPC + OpenFirmware platform bus. + config USB_OHCI_HCD tristate "OHCI HCD support" depends on USB && USB_ARCH_HAS_OHCI diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index bb8e9d44f37..f1edda2dcfd 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -6,6 +6,8 @@ ifeq ($(CONFIG_USB_DEBUG),y) EXTRA_CFLAGS += -DDEBUG endif +isp1760-objs := isp1760-hcd.o isp1760-if.o + obj-$(CONFIG_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o @@ -16,4 +18,4 @@ obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o - +obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c new file mode 100644 index 00000000000..4ba96c1e060 --- /dev/null +++ b/drivers/usb/host/isp1760-hcd.c @@ -0,0 +1,2231 @@ +/* + * Driver for the NXP ISP1760 chip + * + * However, the code might contain some bugs. What doesn't work for sure is: + * - ISO + * - OTG + e The interrupt line is configured as active low, level. + * + * (c) 2007 Sebastian Siewior <bigeasy@linutronix.de> + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/usb.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <asm/unaligned.h> + +#include "../core/hcd.h" +#include "isp1760-hcd.h" + +static struct kmem_cache *qtd_cachep; +static struct kmem_cache *qh_cachep; + +struct isp1760_hcd { + u32 hcs_params; + spinlock_t lock; + struct inter_packet_info atl_ints[32]; + struct inter_packet_info int_ints[32]; + struct memory_chunk memory_pool[BLOCKS]; + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 + unsigned periodic_size; + unsigned i_thresh; + unsigned long reset_done; + unsigned long next_statechange; +}; + +static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) +{ + return (struct isp1760_hcd *) (hcd->hcd_priv); +} +static inline struct usb_hcd *priv_to_hcd(struct isp1760_hcd *priv) +{ + return container_of((void *) priv, struct usb_hcd, hcd_priv); +} + +/* Section 2.2 Host Controller Capability Registers */ +#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ +#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ +#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ +#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ + +/* Section 2.3 Host Controller Operational Registers */ +#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ +#define STS_PCD (1<<2) /* port change detect */ +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC) + +struct isp1760_qtd { + struct isp1760_qtd *hw_next; + u8 packet_type; + u8 toggle; + + void *data_buffer; + /* the rest is HCD-private */ + struct list_head qtd_list; + struct urb *urb; + size_t length; + + /* isp special*/ + u32 status; +#define URB_COMPLETE_NOTIFY (1 << 0) +#define URB_ENQUEUED (1 << 1) +#define URB_TYPE_ATL (1 << 2) +#define URB_TYPE_INT (1 << 3) +}; + +struct isp1760_qh { + /* first part defined by EHCI spec */ + struct list_head qtd_list; + struct isp1760_hcd *priv; + + /* periodic schedule info */ + unsigned short period; /* polling interval */ + struct usb_device *dev; + + u32 toggle; + u32 ping; +}; + +#define ehci_port_speed(priv, portsc) (1 << USB_PORT_FEAT_HIGHSPEED) + +static unsigned int isp1760_readl(__u32 __iomem *regs) +{ + return readl(regs); +} + +static void isp1760_writel(const unsigned int val, __u32 __iomem *regs) +{ + writel(val, regs); +} + +/* + * The next two copy via MMIO data to/from the device. memcpy_{to|from}io() + * doesn't quite work because some people have to enforce 32-bit access + */ +static void priv_read_copy(struct isp1760_hcd *priv, u32 *src, + __u32 __iomem *dst, u32 offset, u32 len) +{ + struct usb_hcd *hcd = priv_to_hcd(priv); + u32 val; + u8 *buff8; + + if (!src) { + printk(KERN_ERR "ERROR: buffer: %p len: %d\n", src, len); + return; + } + isp1760_writel(offset, hcd->regs + HC_MEMORY_REG); + /* XXX + * 90nsec delay, the spec says something how this could be avoided. + */ + mdelay(1); + + while (len >= 4) { + *src = __raw_readl(dst); + len -= 4; + src++; + dst++; + } + + if (!len) + return; + + /* in case we have 3, 2 or 1 by left. The dst buffer may not be fully + * allocated. + */ + val = isp1760_readl(dst); + + buff8 = (u8 *)src; + while (len) { + + *buff8 = val; + val >>= 8; + len--; + buff8++; + } +} + +static void priv_write_copy(const struct isp1760_hcd *priv, const u32 *src, + __u32 __iomem *dst, u32 len) +{ + while (len >= 4) { + __raw_writel(*src, dst); + len -= 4; + src++; + dst++; + } + + if (!len) + return; + /* in case we have 3, 2 or 1 by left. The buffer is allocated and the + * extra bytes should not be read by the HW + */ + + __raw_writel(*src, dst); +} + +/* memory management of the 60kb on the chip from 0x1000 to 0xffff */ +static void init_memory(struct isp1760_hcd *priv) +{ + int i; + u32 payload; + + payload = 0x1000; + for (i = 0; i < BLOCK_1_NUM; i++) { + priv->memory_pool[i].start = payload; + priv->memory_pool[i].size = BLOCK_1_SIZE; + priv->memory_pool[i].free = 1; + payload += priv->memory_pool[i].size; + } + + + for (i = BLOCK_1_NUM; i < BLOCK_1_NUM + BLOCK_2_NUM; i++) { + priv->memory_pool[i].start = payload; + priv->memory_pool[i].size = BLOCK_2_SIZE; + priv->memory_pool[i].free = 1; + payload += priv->memory_pool[i].size; + } + + + for (i = BLOCK_1_NUM + BLOCK_2_NUM; i < BLOCKS; i++) { + priv->memory_pool[i].start = payload; + priv->memory_pool[i].size = BLOCK_3_SIZE; + priv->memory_pool[i].free = 1; + payload += priv->memory_pool[i].size; + } + + BUG_ON(payload - priv->memory_pool[i - 1].size > PAYLOAD_SIZE); +} + +static u32 alloc_mem(struct isp1760_hcd *priv, u32 size) +{ + int i; + + if (!size) + return ISP1760_NULL_POINTER; + + for (i = 0; i < BLOCKS; i++) { + if (priv->memory_pool[i].size >= size && + priv->memory_pool[i].free) { + + priv->memory_pool[i].free = 0; + return priv->memory_pool[i].start; + } + } + + printk(KERN_ERR "ISP1760 MEM: can not allocate %d bytes of memory\n", + size); + printk(KERN_ERR "Current memory map:\n"); + for (i = 0; i < BLOCKS; i++) { + printk(KERN_ERR "Pool %2d size %4d status: %d\n", + i, priv->memory_pool[i].size, + priv->memory_pool[i].free); + } + /* XXX maybe -ENOMEM could be possible */ + BUG(); + return 0; +} + +static void free_mem(struct isp1760_hcd *priv, u32 mem) +{ + int i; + + if (mem == ISP1760_NULL_POINTER) + return; + + for (i = 0; i < BLOCKS; i++) { + if (priv->memory_pool[i].start == mem) { + + BUG_ON(priv->memory_pool[i].free); + + priv->memory_pool[i].free = 1; + return ; + } + } + + printk(KERN_ERR "Trying to free not-here-allocated memory :%08x\n", + mem); + BUG(); +} + +static void isp1760_init_regs(struct usb_hcd *hcd) +{ + isp1760_writel(0, hcd->regs + HC_BUFFER_STATUS_REG); + isp1760_writel(NO_TRANSFER_ACTIVE, hcd->regs + + HC_ATL_PTD_SKIPMAP_REG); + isp1760_writel(NO_TRANSFER_ACTIVE, hcd->regs + + HC_INT_PTD_SKIPMAP_REG); + isp1760_writel(NO_TRANSFER_ACTIVE, hcd->regs + + HC_ISO_PTD_SKIPMAP_REG); + + isp1760_writel(~NO_TRANSFER_ACTIVE, hcd->regs + + HC_ATL_PTD_DONEMAP_REG); + isp1760_writel(~NO_TRANSFER_ACTIVE, hcd->regs + + HC_INT_PTD_DONEMAP_REG); + isp1760_writel(~NO_TRANSFER_ACTIVE, hcd->regs + + HC_ISO_PTD_DONEMAP_REG); +} + +static int handshake(struct isp1760_hcd *priv, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = isp1760_readl(ptr); + if (result == ~0) + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct isp1760_hcd *priv) +{ + int retval; + struct usb_hcd *hcd = priv_to_hcd(priv); + u32 command = isp1760_readl(hcd->regs + HC_USBCMD); + + command |= CMD_RESET; + isp1760_writel(command, hcd->regs + HC_USBCMD); + hcd->state = HC_STATE_HALT; + priv->next_statechange = jiffies; + retval = handshake(priv, hcd->regs + HC_USBCMD, + CMD_RESET, 0, 250 * 1000); + return retval; +} + +static void qh_destroy(struct isp1760_qh *qh) +{ + BUG_ON(!list_empty(&qh->qtd_list)); + kmem_cache_free(qh_cachep, qh); +} + +static struct isp1760_qh *isp1760_qh_alloc(struct isp1760_hcd *priv, + gfp_t flags) +{ + struct isp1760_qh *qh; + + qh = kmem_cache_zalloc(qh_cachep, flags); + if (!qh) + return qh; + + INIT_LIST_HEAD(&qh->qtd_list); + qh->priv = priv; + return qh; +} + +/* magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ + +/* one-time init, only for memory state */ +static int priv_init(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 hcc_params; + + spin_lock_init(&priv->lock); + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + priv->periodic_size = DEFAULT_I_TDPS; + + /* controllers may cache some of the periodic schedule ... */ + hcc_params = isp1760_readl(hcd->regs + HC_HCCPARAMS); + /* full frame cache */ + if (HCC_ISOC_CACHE(hcc_params)) + priv->i_thresh = 8; + else /* N microframes cached */ + priv->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + return 0; +} + +static int isp1760_hc_setup(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int result; + u32 scratch; + + isp1760_writel(0xdeadbabe, hcd->regs + HC_SCRATCH_REG); + scratch = isp1760_readl(hcd->regs + HC_SCRATCH_REG); + if (scratch != 0xdeadbabe) { + printk(KERN_ERR "ISP1760: Scratch test failed.\n"); + return -ENODEV; + } + + /* pre reset */ + isp1760_init_regs(hcd); + + /* reset */ + isp1760_writel(SW_RESET_RESET_ALL, hcd->regs + HC_RESET_REG); + mdelay(100); + + isp1760_writel(SW_RESET_RESET_HC, hcd->regs + HC_RESET_REG); + mdelay(100); + + result = ehci_reset(priv); + if (result) + return result; + + /* Step 11 passed */ + + isp1760_writel(INTERRUPT_ENABLE_MASK, hcd->regs + HC_INTERRUPT_REG); + isp1760_writel(INTERRUPT_ENABLE_MASK, hcd->regs + HC_INTERRUPT_ENABLE); + + /* ATL reset */ + scratch = isp1760_readl(hcd->regs + HC_HW_MODE_CTRL); + isp1760_writel(scratch | ALL_ATX_RESET, hcd->regs + HC_HW_MODE_CTRL); + mdelay(10); + isp1760_writel(scratch, hcd->regs + HC_HW_MODE_CTRL); + + isp1760_writel(PORT1_POWER | PORT1_INIT2, hcd->regs + HC_PORT1_CTRL); + mdelay(10); + + priv->hcs_params = isp1760_readl(hcd->regs + HC_HCSPARAMS); + + return priv_init(hcd); +} + +static void isp1760_init_maps(struct usb_hcd *hcd) +{ + /*set last maps, for iso its only 1, else 32 tds bitmap*/ + isp1760_writel(0x80000000, hcd->regs + HC_ATL_PTD_LASTPTD_REG); + isp1760_writel(0x80000000, hcd->regs + HC_INT_PTD_LASTPTD_REG); + isp1760_writel(0x00000001, hcd->regs + HC_ISO_PTD_LASTPTD_REG); +} + +static void isp1760_enable_interrupts(struct usb_hcd *hcd) +{ + isp1760_writel(0, hcd->regs + HC_ATL_IRQ_MASK_AND_REG); + isp1760_writel(0, hcd->regs + HC_ATL_IRQ_MASK_OR_REG); + isp1760_writel(0, hcd->regs + HC_INT_IRQ_MASK_AND_REG); + isp1760_writel(0, hcd->regs + HC_INT_IRQ_MASK_OR_REG); + isp1760_writel(0, hcd->regs + HC_ISO_IRQ_MASK_AND_REG); + isp1760_writel(0xffffffff, hcd->regs + HC_ISO_IRQ_MASK_OR_REG); + /* step 23 passed */ +} + +static int isp1760_run(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int retval; + u32 temp; + u32 command; + u32 chipid; + + hcd->uses_new_polling = 1; + hcd->poll_rh = 0; + + hcd->state = HC_STATE_RUNNING; + isp1760_enable_interrupts(hcd); + temp = isp1760_readl(hcd->regs + HC_HW_MODE_CTRL); + temp |= FINAL_HW_CONFIG; + isp1760_writel(temp, hcd->regs + HC_HW_MODE_CTRL); + + command = isp1760_readl(hcd->regs + HC_USBCMD); + command &= ~(CMD_LRESET|CMD_RESET); + command |= CMD_RUN; + isp1760_writel(command, hcd->regs + HC_USBCMD); + + retval = handshake(priv, hcd->regs + HC_USBCMD, CMD_RUN, CMD_RUN, + 250 * 1000); + if (retval) + return retval; + + /* + * XXX + * Spec says to write FLAG_CF as last config action, priv code grabs + * the semaphore while doing so. + */ + down_write(&ehci_cf_port_reset_rwsem); + isp1760_writel(FLAG_CF, hcd->regs + HC_CONFIGFLAG); + + retval = handshake(priv, hcd->regs + HC_CONFIGFLAG, FLAG_CF, FLAG_CF, + 250 * 1000); + up_write(&ehci_cf_port_reset_rwsem); + if (retval) + return retval; + + chipid = isp1760_readl(hcd->regs + HC_CHIP_ID_REG); + isp1760_info(priv, "USB ISP %04x HW rev. %d started\n", chipid & 0xffff, + chipid >> 16); + + /* PTD Register Init Part 2, Step 28 */ + /* enable INTs */ + isp1760_init_maps(hcd); + + /* GRR this is run-once init(), being done every time the HC starts. + * So long as they're part of class devices, we can't do it init() + * since the class device isn't created that early. + */ + return 0; +} + +static u32 base_to_chip(u32 base) +{ + return ((base - 0x400) >> 3); +} + +static void transform_into_atl(struct isp1760_hcd *priv, struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct urb *urb, + u32 payload, struct ptd *ptd) +{ + u32 dw0; + u32 dw1; + u32 dw2; + u32 dw3; + u32 maxpacket; + u32 multi; + u32 pid_code; + u32 rl = RL_COUNTER; + u32 nak = NAK_COUNTER; + + /* according to 3.6.2, max packet len can not be > 0x400 */ + maxpacket = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + multi = 1 + ((maxpacket >> 11) & 0x3); + maxpacket &= 0x7ff; + + /* DW0 */ + dw0 = PTD_VALID; + dw0 |= PTD_LENGTH(qtd->length); + dw0 |= PTD_MAXPACKET(maxpacket); + dw0 |= PTD_ENDPOINT(usb_pipeendpoint(urb->pipe)); + dw1 = usb_pipeendpoint(urb->pipe) >> 1; + + /* DW1 */ + dw1 |= PTD_DEVICE_ADDR(usb_pipedevice(urb->pipe)); + + pid_code = qtd->packet_type; + dw1 |= PTD_PID_TOKEN(pid_code); + + if (usb_pipebulk(urb->pipe)) + dw1 |= PTD_TRANS_BULK; + else if (usb_pipeint(urb->pipe)) + dw1 |= PTD_TRANS_INT; + + if (urb->dev->speed != USB_SPEED_HIGH) { + /* split transaction */ + + dw1 |= PTD_TRANS_SPLIT; + if (urb->dev->speed == USB_SPEED_LOW) + dw1 |= PTD_SE_USB_LOSPEED; + + dw1 |= PTD_PORT_NUM(urb->dev->ttport); + dw1 |= PTD_HUB_NUM(urb->dev->tt->hub->devnum); + + /* SE bit for Split INT transfers */ + if (usb_pipeint(urb->pipe) && + (urb->dev->speed == USB_SPEED_LOW)) + dw1 |= 2 << 16; + + dw3 = 0; + rl = 0; + nak = 0; + } else { + dw0 |= PTD_MULTI(multi); + if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe)) + dw3 = qh->ping; + else + dw3 = 0; + } + /* DW2 */ + dw2 = 0; + dw2 |= PTD_DATA_START_ADDR(base_to_chip(payload)); + dw2 |= PTD_RL_CNT(rl); + dw3 |= PTD_NAC_CNT(nak); + + /* DW3 */ + if (usb_pipecontrol(urb->pipe)) + dw3 |= PTD_DATA_TOGGLE(qtd->toggle); + else + dw3 |= qh->toggle; + + + dw3 |= PTD_ACTIVE; + /* Cerr */ + dw3 |= PTD_CERR(ERR_COUNTER); + + memset(ptd, 0, sizeof(*ptd)); + + ptd->dw0 = cpu_to_le32(dw0); + ptd->dw1 = cpu_to_le32(dw1); + ptd->dw2 = cpu_to_le32(dw2); + ptd->dw3 = cpu_to_le32(dw3); +} + +static void transform_add_int(struct isp1760_hcd *priv, struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct urb *urb, + u32 payload, struct ptd *ptd) +{ + u32 maxpacket; + u32 multi; + u32 numberofusofs; + u32 i; + u32 usofmask, usof; + u32 period; + + maxpacket = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + multi = 1 + ((maxpacket >> 11) & 0x3); + maxpacket &= 0x7ff; + /* length of the data per uframe */ + maxpacket = multi * maxpacket; + + numberofusofs = urb->transfer_buffer_length / maxpacket; + if (urb->transfer_buffer_length % maxpacket) + numberofusofs += 1; + + usofmask = 1; + usof = 0; + for (i = 0; i < numberofusofs; i++) { + usof |= usofmask; + usofmask <<= 1; + } + + if (urb->dev->speed != USB_SPEED_HIGH) { + /* split */ + ptd->dw5 = __constant_cpu_to_le32(0x1c); + + if (qh->period >= 32) + period = qh->period / 2; + else + period = qh->period; + + } else { + + if (qh->period >= 8) + period = qh->period/8; + else + period = qh->period; + + if (period >= 32) + period = 16; + + if (qh->period >= 8) { + /* millisecond period */ + period = (period << 3); + } else { + /* usof based tranmsfers */ + /* minimum 4 usofs */ + usof = 0x11; + } + } + + ptd->dw2 |= cpu_to_le32(period); + ptd->dw4 = cpu_to_le32(usof); +} + +static void transform_into_int(struct isp1760_hcd *priv, struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct urb *urb, + u32 payload, struct ptd *ptd) +{ + transform_into_atl(priv, qh, qtd, urb, payload, ptd); + transform_add_int(priv, qh, qtd, urb, payload, ptd); +} + +static int qtd_fill(struct isp1760_qtd *qtd, void *databuffer, size_t len, + u32 token) +{ + int count; + + qtd->data_buffer = databuffer; + qtd->packet_type = GET_QTD_TOKEN_TYPE(token); + qtd->toggle = GET_DATA_TOGGLE(token); + + if (len > HC_ATL_PL_SIZE) + count = HC_ATL_PL_SIZE; + else + count = len; + + qtd->length = count; + return count; +} + +static int check_error(struct ptd *ptd) +{ + int error = 0; + u32 dw3; + + dw3 = le32_to_cpu(ptd->dw3); + if (dw3 & DW3_HALT_BIT) + error = -EPIPE; + + if (dw3 & DW3_ERROR_BIT) { + printk(KERN_ERR "error bit is set in DW3\n"); + error = -EPIPE; + } + + if (dw3 & DW3_QTD_ACTIVE) { + printk(KERN_ERR "transfer active bit is set DW3\n"); + printk(KERN_ERR "nak counter: %d, rl: %d\n", (dw3 >> 19) & 0xf, + (le32_to_cpu(ptd->dw2) >> 25) & 0xf); + } + + return error; +} + +static void check_int_err_status(u32 dw4) +{ + u32 i; + + dw4 >>= 8; + + for (i = 0; i < 8; i++) { + switch (dw4 & 0x7) { + case INT_UNDERRUN: + printk(KERN_ERR "ERROR: under run , %d\n", i); + break; + + case INT_EXACT: + printk(KERN_ERR "ERROR: transaction error, %d\n", i); + break; + + case INT_BABBLE: + printk(KERN_ERR "ERROR: babble error, %d\n", i); + break; + } + dw4 >>= 3; + } +} + +static void enqueue_one_qtd(struct isp1760_qtd *qtd, struct isp1760_hcd *priv, + u32 payload) +{ + u32 token; + struct usb_hcd *hcd = priv_to_hcd(priv); + + token = qtd->packet_type; + + if (qtd->length && (qtd->length <= HC_ATL_PL_SIZE)) { + switch (token) { + case IN_PID: + break; + case OUT_PID: + case SETUP_PID: + priv_write_copy(priv, qtd->data_buffer, + hcd->regs + payload, + qtd->length); + } + } +} + +static void enqueue_one_atl_qtd(u32 atl_regs, u32 payload, + struct isp1760_hcd *priv, struct isp1760_qh *qh, + struct urb *urb, u32 slot, struct isp1760_qtd *qtd) +{ + struct ptd ptd; + struct usb_hcd *hcd = priv_to_hcd(priv); + + transform_into_atl(priv, qh, qtd, urb, payload, &ptd); + priv_write_copy(priv, (u32 *)&ptd, hcd->regs + atl_regs, sizeof(ptd)); + enqueue_one_qtd(qtd, priv, payload); + + priv->atl_ints[slot].urb = urb; + priv->atl_ints[slot].qh = qh; + priv->atl_ints[slot].qtd = qtd; + priv->atl_ints[slot].data_buffer = qtd->data_buffer; + priv->atl_ints[slot].payload = payload; + qtd->status |= URB_ENQUEUED | URB_TYPE_ATL; + qtd->status |= slot << 16; +} + +static void enqueue_one_int_qtd(u32 int_regs, u32 payload, + struct isp1760_hcd *priv, struct isp1760_qh *qh, + struct urb *urb, u32 slot, struct isp1760_qtd *qtd) +{ + struct ptd ptd; + struct usb_hcd *hcd = priv_to_hcd(priv); + + transform_into_int(priv, qh, qtd, urb, payload, &ptd); + priv_write_copy(priv, (u32 *)&ptd, hcd->regs + int_regs, sizeof(ptd)); + enqueue_one_qtd(qtd, priv, payload); + + priv->int_ints[slot].urb = urb; + priv->int_ints[slot].qh = qh; + priv->int_ints[slot].qtd = qtd; + priv->int_ints[slot].data_buffer = qtd->data_buffer; + priv->int_ints[slot].payload = payload; + qtd->status |= URB_ENQUEUED | URB_TYPE_INT; + qtd->status |= slot << 16; +} + +void enqueue_an_ATL_packet(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 skip_map, or_map; + u32 queue_entry; + u32 slot; + u32 atl_regs, payload; + u32 buffstatus; + + skip_map = isp1760_readl(hcd->regs + HC_ATL_PTD_SKIPMAP_REG); + + BUG_ON(!skip_map); + slot = __ffs(skip_map); + queue_entry = 1 << slot; + + atl_regs = ATL_REGS_OFFSET + slot * sizeof(struct ptd); + + payload = alloc_mem(priv, qtd->length); + + enqueue_one_atl_qtd(atl_regs, payload, priv, qh, qtd->urb, slot, qtd); + + or_map = isp1760_readl(hcd->regs + HC_ATL_IRQ_MASK_OR_REG); + or_map |= queue_entry; + isp1760_writel(or_map, hcd->regs + HC_ATL_IRQ_MASK_OR_REG); + + skip_map &= ~queue_entry; + isp1760_writel(skip_map, hcd->regs + HC_ATL_PTD_SKIPMAP_REG); + + buffstatus = isp1760_readl(hcd->regs + HC_BUFFER_STATUS_REG); + buffstatus |= ATL_BUFFER; + isp1760_writel(buffstatus, hcd->regs + HC_BUFFER_STATUS_REG); +} + +void enqueue_an_INT_packet(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 skip_map, or_map; + u32 queue_entry; + u32 slot; + u32 int_regs, payload; + u32 buffstatus; + + skip_map = isp1760_readl(hcd->regs + HC_INT_PTD_SKIPMAP_REG); + + BUG_ON(!skip_map); + slot = __ffs(skip_map); + queue_entry = 1 << slot; + + int_regs = INT_REGS_OFFSET + slot * sizeof(struct ptd); + + payload = alloc_mem(priv, qtd->length); + + enqueue_one_int_qtd(int_regs, payload, priv, qh, qtd->urb, slot, qtd); + + or_map = isp1760_readl(hcd->regs + HC_INT_IRQ_MASK_OR_REG); + or_map |= queue_entry; + isp1760_writel(or_map, hcd->regs + HC_INT_IRQ_MASK_OR_REG); + + skip_map &= ~queue_entry; + isp1760_writel(skip_map, hcd->regs + HC_INT_PTD_SKIPMAP_REG); + + buffstatus = isp1760_readl(hcd->regs + HC_BUFFER_STATUS_REG); + buffstatus |= INT_BUFFER; + isp1760_writel(buffstatus, hcd->regs + HC_BUFFER_STATUS_REG); +} + +static void isp1760_urb_done(struct isp1760_hcd *priv, struct urb *urb, int status) +__releases(priv->lock) +__acquires(priv->lock) +{ + if (!urb->unlinked) { + if (status == -EINPROGRESS) + status = 0; + } + + /* complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(priv_to_hcd(priv), urb); + spin_unlock(&priv->lock); + usb_hcd_giveback_urb(priv_to_hcd(priv), urb, status); + spin_lock(&priv->lock); +} + +static void isp1760_qtd_free(struct isp1760_qtd *qtd) +{ + kmem_cache_free(qtd_cachep, qtd); +} + +static struct isp1760_qtd *clean_this_qtd(struct isp1760_qtd *qtd) +{ + struct isp1760_qtd *tmp_qtd; + + tmp_qtd = qtd->hw_next; + list_del(&qtd->qtd_list); + isp1760_qtd_free(qtd); + return tmp_qtd; +} + +/* + * Remove this QTD from the QH list and free its memory. If this QTD + * isn't the last one than remove also his successor(s). + * Returns the QTD which is part of an new URB and should be enqueued. + */ +static struct isp1760_qtd *clean_up_qtdlist(struct isp1760_qtd *qtd) +{ + struct isp1760_qtd *tmp_qtd; + int last_one; + + do { + tmp_qtd = qtd->hw_next; + last_one = qtd->status & URB_COMPLETE_NOTIFY; + list_del(&qtd->qtd_list); + isp1760_qtd_free(qtd); + qtd = tmp_qtd; + } while (!last_one && qtd); + + return qtd; +} + +static void do_atl_int(struct usb_hcd *usb_hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(usb_hcd); + u32 done_map, skip_map; + struct ptd ptd; + struct urb *urb = NULL; + u32 atl_regs_base; + u32 atl_regs; + u32 queue_entry; + u32 payload; + u32 length; + u32 or_map; + u32 status = -EINVAL; + int error; + struct isp1760_qtd *qtd; + struct isp1760_qh *qh; + u32 rl; + u32 nakcount; + + done_map = isp1760_readl(usb_hcd->regs + + HC_ATL_PTD_DONEMAP_REG); + skip_map = isp1760_readl(usb_hcd->regs + + HC_ATL_PTD_SKIPMAP_REG); + + or_map = isp1760_readl(usb_hcd->regs + HC_ATL_IRQ_MASK_OR_REG); + or_map &= ~done_map; + isp1760_writel(or_map, usb_hcd->regs + HC_ATL_IRQ_MASK_OR_REG); + + atl_regs_base = ATL_REGS_OFFSET; + while (done_map) { + u32 dw1; + u32 dw2; + u32 dw3; + + status = 0; + + queue_entry = __ffs(done_map); + done_map &= ~(1 << queue_entry); + skip_map |= 1 << queue_entry; + + atl_regs = atl_regs_base + queue_entry * sizeof(struct ptd); + + urb = priv->atl_ints[queue_entry].urb; + qtd = priv->atl_ints[queue_entry].qtd; + qh = priv->atl_ints[queue_entry].qh; + payload = priv->atl_ints[queue_entry].payload; + + if (!qh) { + printk(KERN_ERR "qh is 0\n"); + continue; + } + priv_read_copy(priv, (u32 *)&ptd, usb_hcd->regs + atl_regs, + atl_regs, sizeof(ptd)); + + dw1 = le32_to_cpu(ptd.dw1); + dw2 = le32_to_cpu(ptd.dw2); + dw3 = le32_to_cpu(ptd.dw3); + rl = (dw2 >> 25) & 0x0f; + nakcount = (dw3 >> 19) & 0xf; + + /* Transfer Error, *but* active and no HALT -> reload */ + if ((dw3 & DW3_ERROR_BIT) && (dw3 & DW3_QTD_ACTIVE) && + !(dw3 & DW3_HALT_BIT)) { + + /* according to ppriv code, we have to + * reload this one if trasfered bytes != requested bytes + * else act like everything went smooth.. + * XXX This just doesn't feel right and hasn't + * triggered so far. + */ + + length = PTD_XFERRED_LENGTH(dw3); + printk(KERN_ERR "Should reload now.... transfered %d " + "of %zu\n", length, qtd->length); + BUG(); + } + + if (!nakcount && (dw3 & DW3_QTD_ACTIVE)) { + u32 buffstatus; + + /* XXX + * NAKs are handled in HW by the chip. Usually if the + * device is not able to send data fast enough. + * This did not trigger for a long time now. + */ + printk(KERN_ERR "Reloading ptd %p/%p... qh %p readed: " + "%d of %d done: %08x cur: %08x\n", qtd, + urb, qh, PTD_XFERRED_LENGTH(dw3), + qtd->length, done_map, + (1 << queue_entry)); + + /* RL counter = ERR counter */ + dw3 &= ~(0xf << 19); + dw3 |= rl << 19; + dw3 &= ~(3 << (55 - 32)); + dw3 |= ERR_COUNTER << (55 - 32); + + /* + * It is not needed to write skip map back because it + * is unchanged. Just make sure that this entry is + * unskipped once it gets written to the HW. + */ + skip_map &= ~(1 << queue_entry); + or_map = isp1760_readl(usb_hcd->regs + + HC_ATL_IRQ_MASK_OR_REG); + or_map |= 1 << queue_entry; + isp1760_writel(or_map, usb_hcd->regs + + HC_ATL_IRQ_MASK_OR_REG); + + ptd.dw3 = cpu_to_le32(dw3); + priv_write_copy(priv, (u32 *)&ptd, usb_hcd->regs + + atl_regs, sizeof(ptd)); + + ptd.dw0 |= __constant_cpu_to_le32(PTD_VALID); + priv_write_copy(priv, (u32 *)&ptd, usb_hcd->regs + + atl_regs, sizeof(ptd)); + + buffstatus = isp1760_readl(usb_hcd->regs + + HC_BUFFER_STATUS_REG); + buffstatus |= ATL_BUFFER; + isp1760_writel(buffstatus, usb_hcd->regs + + HC_BUFFER_STATUS_REG); + continue; + } + + error = check_error(&ptd); + if (error) { + status = error; + priv->atl_ints[queue_entry].qh->toggle = 0; + priv->atl_ints[queue_entry].qh->ping = 0; + urb->status = -EPIPE; + +#if 0 + printk(KERN_ERR "Error in %s().\n", __func__); + printk(KERN_ERR "IN dw0: %08x dw1: %08x dw2: %08x " + "dw3: %08x dw4: %08x dw5: %08x dw6: " + "%08x dw7: %08x\n", + ptd.dw0, ptd.dw1, ptd.dw2, ptd.dw3, + ptd.dw4, ptd.dw5, ptd.dw6, ptd.dw7); +#endif + } else { + if (usb_pipetype(urb->pipe) == PIPE_BULK) { + priv->atl_ints[queue_entry].qh->toggle = dw3 & + (1 << 25); + priv->atl_ints[queue_entry].qh->ping = dw3 & + (1 << 26); + } + } + + length = PTD_XFERRED_LENGTH(dw3); + if (length) { + switch (DW1_GET_PID(dw1)) { + case IN_PID: + priv_read_copy(priv, + priv->atl_ints[queue_entry].data_buffer, + usb_hcd->regs + payload, payload, + length); + + case OUT_PID: + + urb->actual_length += length; + + case SETUP_PID: + break; + } + } + + priv->atl_ints[queue_entry].data_buffer = NULL; + priv->atl_ints[queue_entry].urb = NULL; + priv->atl_ints[queue_entry].qtd = NULL; + priv->atl_ints[queue_entry].qh = NULL; + + free_mem(priv, payload); + + isp1760_writel(skip_map, usb_hcd->regs + + HC_ATL_PTD_SKIPMAP_REG); + + if (urb->status == -EPIPE) { + /* HALT was received */ + + qtd = clean_up_qtdlist(qtd); + isp1760_urb_done(priv, urb, urb->status); + + } else if (usb_pipebulk(urb->pipe) && (length < qtd->length)) { + /* short BULK received */ + + printk(KERN_ERR "short bulk, %d instead %d\n", length, + qtd->length); + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + urb->status = -EREMOTEIO; + printk(KERN_ERR "not okey\n"); + } + + if (urb->status == -EINPROGRESS) + urb->status = 0; + + qtd = clean_up_qtdlist(qtd); + + isp1760_urb_done(priv, urb, urb->status); + + } else if (qtd->status & URB_COMPLETE_NOTIFY) { + /* that was the last qtd of that URB */ + + if (urb->status == -EINPROGRESS) + urb->status = 0; + + qtd = clean_this_qtd(qtd); + isp1760_urb_done(priv, urb, urb->status); + + } else { + /* next QTD of this URB */ + + qtd = clean_this_qtd(qtd); + BUG_ON(!qtd); + } + + if (qtd) + enqueue_an_ATL_packet(usb_hcd, qh, qtd); + + skip_map = isp1760_readl(usb_hcd->regs + + HC_ATL_PTD_SKIPMAP_REG); + } |