The errata2 workaround is described in the Datasheet as follows: An SOF INT can be used instead of an ATL INT with polling on Done bits. A time-out can be implemented and if a certain Done bit is never set, verification of the PTD completion can be done by reading PTD contents (valid bit). This is a proven workaround implemented in software. Therefore, we should do so, and ignore the check for the ACTIVE_BIT in DW3. Because it is possible that this bit is still set when the issue accures. Signed-off-by: Michael Grzeschik <m.grzeschik@xxxxxxxxxxxxxx> --- drivers/usb/host/isp1760-hcd.c | 110 +++++++++++++++++----------------------- drivers/usb/host/isp1760-hcd.h | 1 + 2 files changed, 48 insertions(+), 63 deletions(-) diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 90c57dc..4beedba 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -48,6 +48,8 @@ struct isp1760_hcd { int int_done_map; struct memory_chunk memory_pool[BLOCKS]; struct list_head qh_list[QH_END]; + int active_ptds; + int last_active_ptds; /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 @@ -512,6 +514,11 @@ 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; + priv->last_active_ptds = 0; + /* ATL reset */ reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET); mdelay(10); @@ -775,6 +782,7 @@ static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, slots[slot].qtd = qtd; slots[slot].qh = qh; ptd_write(hcd->regs, ptd_offset, slot, ptd); + priv->active_ptds++; if (ptd_offset == ATL_PTD_OFFSET) { skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); @@ -1147,6 +1155,7 @@ static void handle_done_ptds(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); @@ -1228,10 +1237,13 @@ static void handle_done_ptds(struct usb_hcd *hcd) schedule_ptds(hcd); } +#define SLOT_TIMEOUT 300 static irqreturn_t isp1760_irq(struct usb_hcd *hcd) { struct isp1760_hcd *priv = hcd_to_priv(hcd); u32 imask; + int slot; + struct ptd ptd; irqreturn_t irqret = IRQ_NONE; spin_lock(&priv->lock); @@ -1247,71 +1259,50 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd) 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: - spin_unlock(&priv->lock); - - 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 300 -#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); + /* Errata2: A time-out can be implemented and if a certain Done bit is + never set, verification of the PTD completion is done by reading PTD + contents (valid bit). This is a proven workaround implemented in + software. */ for (slot = 0; slot < 32; slot++) if (priv->atl_slots[slot].qh && time_after(jiffies, priv->atl_slots[slot].timestamp + - SLOT_TIMEOUT * HZ / 1000)) { + msecs_to_jiffies(SLOT_TIMEOUT)) && + !(priv->atl_done_map & (1 << slot))) { ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); - if (!FROM_DW0_VALID(ptd.dw0) && - !FROM_DW3_ACTIVE(ptd.dw3)) + + priv->atl_slots[slot].timestamp = jiffies; + if (!FROM_DW0_VALID(ptd.dw0)) { priv->atl_done_map |= 1 << slot; + dev_info(hcd->self.controller, "errata2 issue!\n"); + continue; + } } - if (priv->atl_done_map) - handle_done_ptds(hcd); - spin_unlock_irqrestore(&priv->lock, spinflags); + handle_done_ptds(hcd); - errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; - add_timer(&errata2_timer); + /* 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. */ + + if (priv->active_ptds != priv->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); + priv->last_active_ptds = priv->active_ptds; + } + + irqret = IRQ_HANDLED; +leave: + spin_unlock(&priv->lock); + + return irqret; } static int isp1760_run(struct usb_hcd *hcd) @@ -1359,12 +1350,6 @@ 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); @@ -1627,6 +1612,7 @@ static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, } qh->slot = -1; + priv->active_ptds--; } /* @@ -2113,8 +2099,6 @@ 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); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 33dc79c..b20ae45 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -74,6 +74,7 @@ void deinit_kmem_cache(void); #define HC_EOT_INT (1 << 3) #define HC_SOT_INT (1 << 1) #define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) +#define INTERRUPT_ENABLE_SOT_MASK (HC_SOT_INT) #define HC_ISO_IRQ_MASK_OR_REG 0x318 #define HC_INT_IRQ_MASK_OR_REG 0x31C -- 1.7.10 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html