|
@@ -21,6 +21,7 @@
|
|
|
#include <linux/uaccess.h>
|
|
|
#include <linux/io.h>
|
|
|
#include <linux/mm.h>
|
|
|
+#include <linux/timer.h>
|
|
|
#include <asm/unaligned.h>
|
|
|
#include <asm/cacheflush.h>
|
|
|
|
|
@@ -39,7 +40,6 @@ struct isp1760_hcd {
|
|
|
int int_done_map;
|
|
|
struct memory_chunk memory_pool[BLOCKS];
|
|
|
struct list_head controlqhs, bulkqhs, interruptqhs;
|
|
|
- int active_ptds;
|
|
|
|
|
|
/* periodic schedule support */
|
|
|
#define DEFAULT_I_TDPS 1024
|
|
@@ -489,10 +489,6 @@ static int isp1760_hc_setup(struct usb_hcd *hcd)
|
|
|
16 : 32, (priv->devflags & ISP1760_FLAG_ANALOG_OC) ?
|
|
|
"analog" : "digital");
|
|
|
|
|
|
- /* This is weird: at the first plug-in of a device there seems to be
|
|
|
- one packet queued that never gets returned? */
|
|
|
- priv->active_ptds = -1;
|
|
|
-
|
|
|
/* ATL reset */
|
|
|
reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET);
|
|
|
mdelay(10);
|
|
@@ -741,8 +737,8 @@ static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot,
|
|
|
qh->slot = slot;
|
|
|
qtd->status = QTD_XFER_STARTED; /* Set this before writing ptd, since
|
|
|
interrupt routine may preempt and expects this value. */
|
|
|
+ slots[slot].timestamp = jiffies;
|
|
|
ptd_write(hcd->regs, ptd_offset, slot, ptd);
|
|
|
- priv->active_ptds++;
|
|
|
|
|
|
/* Make sure done map has not triggered from some unlinked transfer */
|
|
|
if (ptd_offset == ATL_PTD_OFFSET) {
|
|
@@ -1091,11 +1087,9 @@ static int check_atl_transfer(struct usb_hcd *hcd, struct ptd *ptd,
|
|
|
return PTD_STATE_QTD_DONE;
|
|
|
}
|
|
|
|
|
|
-static irqreturn_t isp1760_irq(struct usb_hcd *hcd)
|
|
|
+static void handle_done_ptds(struct usb_hcd *hcd)
|
|
|
{
|
|
|
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
|
|
- u32 imask;
|
|
|
- irqreturn_t irqret = IRQ_NONE;
|
|
|
struct ptd ptd;
|
|
|
struct isp1760_qh *qh;
|
|
|
int slot;
|
|
@@ -1104,27 +1098,14 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd)
|
|
|
u32 ptd_offset;
|
|
|
struct isp1760_qtd *qtd;
|
|
|
int modified;
|
|
|
- static int last_active_ptds;
|
|
|
- int int_skip_map, atl_skip_map;
|
|
|
-
|
|
|
- spin_lock(&priv->lock);
|
|
|
-
|
|
|
- if (!(hcd->state & HC_STATE_RUNNING))
|
|
|
- goto leave;
|
|
|
-
|
|
|
- imask = reg_read32(hcd->regs, HC_INTERRUPT_REG);
|
|
|
- if (unlikely(!imask))
|
|
|
- goto leave;
|
|
|
- reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */
|
|
|
+ int skip_map;
|
|
|
|
|
|
- int_skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG);
|
|
|
- atl_skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG);
|
|
|
- priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG);
|
|
|
- priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG);
|
|
|
- priv->int_done_map &= ~int_skip_map;
|
|
|
- priv->atl_done_map &= ~atl_skip_map;
|
|
|
+ skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG);
|
|
|
+ priv->int_done_map &= ~skip_map;
|
|
|
+ skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG);
|
|
|
+ priv->atl_done_map &= ~skip_map;
|
|
|
|
|
|
- modified = priv->int_done_map | priv->atl_done_map;
|
|
|
+ modified = priv->int_done_map || priv->atl_done_map;
|
|
|
|
|
|
while (priv->int_done_map || priv->atl_done_map) {
|
|
|
if (priv->int_done_map) {
|
|
@@ -1163,7 +1144,6 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd)
|
|
|
slots[slot].qtd = NULL;
|
|
|
qh = slots[slot].qh;
|
|
|
slots[slot].qh = NULL;
|
|
|
- priv->active_ptds--;
|
|
|
qh->slot = -1;
|
|
|
|
|
|
WARN_ON(qtd->status != QTD_XFER_STARTED);
|
|
@@ -1234,22 +1214,28 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd)
|
|
|
|
|
|
if (modified)
|
|
|
schedule_ptds(hcd);
|
|
|
+}
|
|
|
|
|
|
- /* ISP1760 Errata 2 explains that interrupts may be missed (or not
|
|
|
- happen?) if two USB devices are running simultaneously. Perhaps
|
|
|
- this happens when a PTD is finished during interrupt handling;
|
|
|
- enable SOF interrupts if PTDs are still scheduled when exiting this
|
|
|
- interrupt handler, just to be safe. */
|
|
|
+static irqreturn_t isp1760_irq(struct usb_hcd *hcd)
|
|
|
+{
|
|
|
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
|
|
+ u32 imask;
|
|
|
+ irqreturn_t irqret = IRQ_NONE;
|
|
|
|
|
|
- if (priv->active_ptds != last_active_ptds) {
|
|
|
- if (priv->active_ptds > 0)
|
|
|
- reg_write32(hcd->regs, HC_INTERRUPT_ENABLE,
|
|
|
- INTERRUPT_ENABLE_SOT_MASK);
|
|
|
- else
|
|
|
- reg_write32(hcd->regs, HC_INTERRUPT_ENABLE,
|
|
|
- INTERRUPT_ENABLE_MASK);
|
|
|
- last_active_ptds = priv->active_ptds;
|
|
|
- }
|
|
|
+ spin_lock(&priv->lock);
|
|
|
+
|
|
|
+ if (!(hcd->state & HC_STATE_RUNNING))
|
|
|
+ goto leave;
|
|
|
+
|
|
|
+ imask = reg_read32(hcd->regs, HC_INTERRUPT_REG);
|
|
|
+ if (unlikely(!imask))
|
|
|
+ goto leave;
|
|
|
+ reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */
|
|
|
+
|
|
|
+ priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG);
|
|
|
+ priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG);
|
|
|
+
|
|
|
+ handle_done_ptds(hcd);
|
|
|
|
|
|
irqret = IRQ_HANDLED;
|
|
|
leave:
|
|
@@ -1258,6 +1244,63 @@ leave:
|
|
|
return irqret;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Workaround for problem described in chip errata 2:
|
|
|
+ *
|
|
|
+ * Sometimes interrupts are not generated when ATL (not INT?) completion occurs.
|
|
|
+ * One solution suggested in the errata is to use SOF interrupts _instead_of_
|
|
|
+ * ATL done interrupts (the "instead of" might be important since it seems
|
|
|
+ * enabling ATL interrupts also causes the chip to sometimes - rarely - "forget"
|
|
|
+ * to set the PTD's done bit in addition to not generating an interrupt!).
|
|
|
+ *
|
|
|
+ * So if we use SOF + ATL interrupts, we sometimes get stale PTDs since their
|
|
|
+ * done bit is not being set. This is bad - it blocks the endpoint until reboot.
|
|
|
+ *
|
|
|
+ * If we use SOF interrupts only, we get latency between ptd completion and the
|
|
|
+ * actual handling. This is very noticeable in testusb runs which takes several
|
|
|
+ * minutes longer without ATL interrupts.
|
|
|
+ *
|
|
|
+ * A better solution is to run the code below every SLOT_CHECK_PERIOD ms. If it
|
|
|
+ * finds active ATL slots which are older than SLOT_TIMEOUT ms, it checks the
|
|
|
+ * slot's ACTIVE and VALID bits. If these are not set, the ptd is considered
|
|
|
+ * completed and its done map bit is set.
|
|
|
+ *
|
|
|
+ * The values of SLOT_TIMEOUT and SLOT_CHECK_PERIOD have been arbitrarily chosen
|
|
|
+ * not to cause too much lag when this HW bug occurs, while still hopefully
|
|
|
+ * ensuring that the check does not falsely trigger.
|
|
|
+ */
|
|
|
+#define SLOT_TIMEOUT 180
|
|
|
+#define SLOT_CHECK_PERIOD 200
|
|
|
+static struct timer_list errata2_timer;
|
|
|
+
|
|
|
+void errata2_function(unsigned long data)
|
|
|
+{
|
|
|
+ struct usb_hcd *hcd = (struct usb_hcd *) data;
|
|
|
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
|
|
+ int slot;
|
|
|
+ struct ptd ptd;
|
|
|
+ unsigned long spinflags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&priv->lock, spinflags);
|
|
|
+
|
|
|
+ for (slot = 0; slot < 32; slot++)
|
|
|
+ if ((priv->atl_slots[slot].qh || priv->atl_slots[slot].qtd) &&
|
|
|
+ time_after(jiffies + SLOT_TIMEOUT * HZ / 1000,
|
|
|
+ priv->atl_slots[slot].timestamp)) {
|
|
|
+ ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd);
|
|
|
+ if (!FROM_DW0_VALID(ptd.dw0) &&
|
|
|
+ !FROM_DW3_ACTIVE(ptd.dw3))
|
|
|
+ priv->atl_done_map |= 1 << slot;
|
|
|
+ }
|
|
|
+
|
|
|
+ handle_done_ptds(hcd);
|
|
|
+
|
|
|
+ spin_unlock_irqrestore(&priv->lock, spinflags);
|
|
|
+
|
|
|
+ errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000;
|
|
|
+ add_timer(&errata2_timer);
|
|
|
+}
|
|
|
+
|
|
|
static int isp1760_run(struct usb_hcd *hcd)
|
|
|
{
|
|
|
int retval;
|
|
@@ -1303,6 +1346,12 @@ static int isp1760_run(struct usb_hcd *hcd)
|
|
|
if (retval)
|
|
|
return retval;
|
|
|
|
|
|
+ init_timer(&errata2_timer);
|
|
|
+ errata2_timer.function = errata2_function;
|
|
|
+ errata2_timer.data = (unsigned long) hcd;
|
|
|
+ errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000;
|
|
|
+ add_timer(&errata2_timer);
|
|
|
+
|
|
|
chipid = reg_read32(hcd->regs, HC_CHIP_ID_REG);
|
|
|
dev_info(hcd->self.controller, "USB ISP %04x HW rev. %d started\n",
|
|
|
chipid & 0xffff, chipid >> 16);
|
|
@@ -1561,7 +1610,6 @@ static void kill_transfer(struct usb_hcd *hcd, struct urb *urb,
|
|
|
}
|
|
|
|
|
|
qh->slot = -1;
|
|
|
- priv->active_ptds--;
|
|
|
}
|
|
|
|
|
|
static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
|
|
@@ -2012,6 +2060,8 @@ static void isp1760_stop(struct usb_hcd *hcd)
|
|
|
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
|
|
u32 temp;
|
|
|
|
|
|
+ del_timer(&errata2_timer);
|
|
|
+
|
|
|
isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1,
|
|
|
NULL, 0);
|
|
|
mdelay(20);
|