]> git.karo-electronics.de Git - linux-beck.git/commitdiff
mmc: dw_mmc: add quirk for broken data transfer over scheme
authorAddy Ke <addy.ke@rock-chips.com>
Mon, 10 Aug 2015 16:27:18 +0000 (01:27 +0900)
committerUlf Hansson <ulf.hansson@linaro.org>
Mon, 17 Aug 2015 10:45:28 +0000 (12:45 +0200)
This patch add a new quirk to add a s/w timer to notify the driver
to terminate current transfer and report a data timeout to the core,
if DTO interrupt does NOT come within the given time.

dw_mmc call mmc_request_done func to finish transfer depends on
DTO interrupt. If DTO interrupt does not come in sending data state,
the current transfer will be blocked.

We got the reply from synopsys:
There are two counters but both use the same value of [31:8] bits.
Data timeout counter doesn't wait for stop clock and you should get
DRTO even when the clock is not stopped.
Host Starvation timeout counter is triggered with stop clock condition.

This means that host should get DRTO and DTO interrupt.

But this case really exists, when driver reads tuning data from
card on RK3288-pink2 board. I measured waveforms by oscilloscope
and found that card clock was always on and data lines were always
holded high level in sending data state.

There are two possibility that data over interrupt doesn't come in
reading data state on RK3X SoCs:
- get command done interrupt, but doesn't get any data-related interrupt.
- get data error interrupt, but doesn't get data over interrupt.

Signed-off-by: Addy Ke <addy.ke@rock-chips.com>
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Signed-off-by: Jaehoon Chung <jh80.chung@samsung.com>
drivers/mmc/host/dw_mmc-rockchip.c
drivers/mmc/host/dw_mmc.c
include/linux/mmc/dw_mmc.h

index de15121bba7dffe07725451890100d57abba8b25..bc76aa22473ea2f6e75f8b98d96e90b60ed99538 100644 (file)
@@ -73,6 +73,9 @@ static int dw_mci_rockchip_init(struct dw_mci *host)
        /* It is slot 8 on Rockchip SoCs */
        host->sdio_id0 = 8;
 
+       /* It needs this quirk on all Rockchip SoCs */
+       host->pdata->quirks |= DW_MCI_QUIRK_BROKEN_DTO;
+
        return 0;
 }
 
index f8ac14a146936f16aa61311a8349a2062d0bb487..fcbf5524fd3136f6242036ae206239f83c90db71 100644 (file)
@@ -1574,6 +1574,20 @@ static int dw_mci_data_complete(struct dw_mci *host, struct mmc_data *data)
        return data->error;
 }
 
+static void dw_mci_set_drto(struct dw_mci *host)
+{
+       unsigned int drto_clks;
+       unsigned int drto_ms;
+
+       drto_clks = mci_readl(host, TMOUT) >> 8;
+       drto_ms = DIV_ROUND_UP(drto_clks, host->bus_hz / 1000);
+
+       /* add a bit spare time */
+       drto_ms += 10;
+
+       mod_timer(&host->dto_timer, jiffies + msecs_to_jiffies(drto_ms));
+}
+
 static void dw_mci_tasklet_func(unsigned long priv)
 {
        struct dw_mci *host = (struct dw_mci *)priv;
@@ -1651,8 +1665,16 @@ static void dw_mci_tasklet_func(unsigned long priv)
                        }
 
                        if (!test_and_clear_bit(EVENT_XFER_COMPLETE,
-                                               &host->pending_events))
+                                               &host->pending_events)) {
+                               /*
+                                * If all data-related interrupts don't come
+                                * within the given time in reading data state.
+                                */
+                               if ((host->quirks & DW_MCI_QUIRK_BROKEN_DTO) &&
+                                   (host->dir_status == DW_MCI_RECV_STATUS))
+                                       dw_mci_set_drto(host);
                                break;
+                       }
 
                        set_bit(EVENT_XFER_COMPLETE, &host->completed_events);
 
@@ -1685,8 +1707,17 @@ static void dw_mci_tasklet_func(unsigned long priv)
 
                case STATE_DATA_BUSY:
                        if (!test_and_clear_bit(EVENT_DATA_COMPLETE,
-                                               &host->pending_events))
+                                               &host->pending_events)) {
+                               /*
+                                * If data error interrupt comes but data over
+                                * interrupt doesn't come within the given time.
+                                * in reading data state.
+                                */
+                               if ((host->quirks & DW_MCI_QUIRK_BROKEN_DTO) &&
+                                   (host->dir_status == DW_MCI_RECV_STATUS))
+                                       dw_mci_set_drto(host);
                                break;
+                       }
 
                        host->data = NULL;
                        set_bit(EVENT_DATA_COMPLETE, &host->completed_events);
@@ -2259,6 +2290,9 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
                }
 
                if (pending & SDMMC_INT_DATA_OVER) {
+                       if (host->quirks & DW_MCI_QUIRK_BROKEN_DTO)
+                               del_timer(&host->dto_timer);
+
                        mci_writel(host, RINTSTS, SDMMC_INT_DATA_OVER);
                        if (!host->data_status)
                                host->data_status = pending;
@@ -2644,6 +2678,28 @@ static void dw_mci_cmd11_timer(unsigned long arg)
        tasklet_schedule(&host->tasklet);
 }
 
+static void dw_mci_dto_timer(unsigned long arg)
+{
+       struct dw_mci *host = (struct dw_mci *)arg;
+
+       switch (host->state) {
+       case STATE_SENDING_DATA:
+       case STATE_DATA_BUSY:
+               /*
+                * If DTO interrupt does NOT come in sending data state,
+                * we should notify the driver to terminate current transfer
+                * and report a data timeout to the core.
+                */
+               host->data_status = SDMMC_INT_DRTO;
+               set_bit(EVENT_DATA_ERROR, &host->pending_events);
+               set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
+               tasklet_schedule(&host->tasklet);
+               break;
+       default:
+               break;
+       }
+}
+
 #ifdef CONFIG_OF
 static struct dw_mci_of_quirks {
        char *quirk;
@@ -2822,6 +2878,10 @@ int dw_mci_probe(struct dw_mci *host)
 
        host->quirks = host->pdata->quirks;
 
+       if (host->quirks & DW_MCI_QUIRK_BROKEN_DTO)
+               setup_timer(&host->dto_timer,
+                           dw_mci_dto_timer, (unsigned long)host);
+
        spin_lock_init(&host->lock);
        spin_lock_init(&host->irq_lock);
        INIT_LIST_HEAD(&host->queue);
index b14fcb5012f179dcf64b79b3dc52eef4f64ca1d2..134c57422740944fb8d1d004c2f8126b2bdeb604 100644 (file)
@@ -98,6 +98,7 @@ struct mmc_data;
  * @irq_flags: The flags to be passed to request_irq.
  * @irq: The irq value to be passed to request_irq.
  * @sdio_id0: Number of slot0 in the SDIO interrupt registers.
+ * @dto_timer: Timer for broken data transfer over scheme.
  *
  * Locking
  * =======
@@ -200,6 +201,7 @@ struct dw_mci {
        int                     sdio_id0;
 
        struct timer_list       cmd11_timer;
+       struct timer_list       dto_timer;
 };
 
 /* DMA ops for Internal/External DMAC interface */
@@ -222,6 +224,8 @@ struct dw_mci_dma_ops {
 #define DW_MCI_QUIRK_HIGHSPEED                 BIT(2)
 /* Unreliable card detection */
 #define DW_MCI_QUIRK_BROKEN_CARD_DETECTION     BIT(3)
+/* Timer for broken data transfer over scheme */
+#define DW_MCI_QUIRK_BROKEN_DTO                        BIT(4)
 
 struct dma_pdata;