aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/host/Kconfig26
-rw-r--r--drivers/usb/host/Makefile4
-rw-r--r--drivers/usb/host/isp1760-hcd.c2231
-rw-r--r--drivers/usb/host/isp1760-hcd.h206
-rw-r--r--drivers/usb/host/isp1760-if.c298
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);
+ }