All of lore.kernel.org
 help / color / mirror / Atom feed
From: gabriele@precidata.com (Gabriele Mondada)
To: linux-arm-kernel@lists.infradead.org
Subject: LPC32xx and MMCI driver
Date: Thu, 20 Dec 2012 11:06:15 +0100	[thread overview]
Message-ID: <50D2E317.6000005@precidata.com> (raw)
In-Reply-To: <50D2E156.20707@precidata.com>

Hi,
Currently, LPC32xx plateform do not enable DMA on the mmci driver. This 
makes the driver useless because getting out data from a 64 bytes FIFO 
by interrupt is not fast enough (at standard SD-card data rate).

DMA is not enabled because LPC32xx has a bug that prevent DMA to work 
properly with the MMC controller (silicon bug, I guess). NXP did a patch 
to workaround this, but it has not been commited on the main branch. The 
patch is for linux 2.6.39.2 and does not use dmaengine.

So, I reworked this patch to make it compatible with the last kernel 
(3.7). Here it is. Have I any chance to see this patch be commited on 
the main branch?

Thanks a lot,
Gabriele


diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index d1cc579..cdc8899 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -385,6 +385,7 @@ static void pl08x_start_next_txd(struct 
pl08x_dma_chan *plchan)
      while ((val & PL080_CONFIG_ACTIVE) || (val & PL080_CONFIG_ENABLE))
          val = readl(phychan->base + PL080_CH_CONFIG);

+    /* TODO: why val and not txd->ccfg ? */
      writel(val | PL080_CONFIG_ENABLE, phychan->base + PL080_CH_CONFIG);
  }

@@ -1758,6 +1759,26 @@ static void pl08x_free_virtual_channels(struct 
dma_device *dmadev)
      }
  }

+#ifdef CONFIG_ARCH_LPC32XX
+/*
+ * This exported function is used by mmci driver to workaround a bug in the
+ * LPC32xx CPU.
+ */
+void pl08x_force_dma_burst(struct dma_chan *chan)
+{
+    struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
+    struct pl08x_driver_data *pl08x = plchan->host;
+
+    dev_dbg(&pl08x->adev->dev,
+        "force burst signal=%d chan=%p plchan=%p\n",
+        plchan->signal, chan, plchan);
+    if (plchan->signal >= 0)
+        writel(1 << plchan->signal, pl08x->base + PL080_SOFT_BREQ);
+}
+
+EXPORT_SYMBOL_GPL(pl08x_force_dma_burst);
+#endif
+
  #ifdef CONFIG_DEBUG_FS
  static const char *pl08x_state_str(enum pl08x_dma_chan_state state)
  {
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index edc3e9b..0890905 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -3,6 +3,7 @@
   *
   *  Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
   *  Copyright (C) 2010 ST-Ericsson SA
+ *  Copyright (C) 2012 Gabriele Mondada <gmondada@precidata.com>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
@@ -42,6 +43,16 @@

  #define DRIVER_NAME "mmci-pl18x"

+#define COOKIE_PREP    0x00000001
+#define COOKIE_SINGLE    0x00000002
+#define COOKIE_ID_MASK    0xFF000000
+#define COOKIE_ID_SHIFT    24
+#define COOKIE_ID(n)    (COOKIE_ID_MASK & ((n) << COOKIE_ID_SHIFT))
+
+#ifdef CONFIG_ARCH_LPC32XX
+#define DMA_TX_SIZE SZ_64K
+#endif
+
  static unsigned int fmax = 515633;

  /**
@@ -132,6 +143,10 @@ static struct variant_data variant_ux500v2 = {
      .signal_direction    = true,
  };

+#ifdef CONFIG_ARCH_LPC32XX
+void pl08x_force_dma_burst(struct dma_chan *chan);
+#endif
+
  /*
   * This must be called with host->lock held
   */
@@ -266,14 +281,38 @@ static void __devinit mmci_dma_setup(struct 
mmci_host *host)
      struct mmci_platform_data *plat = host->plat;
      const char *rxname, *txname;
      dma_cap_mask_t mask;
-
+#ifdef CONFIG_ARCH_LPC32XX
+    int i;
+#endif
+
      if (!plat || !plat->dma_filter) {
          dev_info(mmc_dev(host->mmc), "no DMA platform data\n");
          return;
      }

-    /* initialize pre request cookie */
-    host->next_data.cookie = 1;
+#ifdef CONFIG_ARCH_LPC32XX
+    /*
+     * The LPC32XX do not support sg on TX DMA. So
+     * a temporary bouncing buffer is used if more than 1 sg segment
+     * is passed in the data request. The bouncing buffer will get a
+     * contiguous copy of the TX data and it will be used instead.
+     */
+    for (i=0; i<2; i++) {
+            host->dma_v_tx[i] = dma_alloc_coherent(mmc_dev(host->mmc),
+            DMA_TX_SIZE, &host->dma_p_tx[i], GFP_KERNEL);
+            if (host->dma_v_tx[i] == NULL) {
+            dev_err(mmc_dev(host->mmc), "error getting DMA region\n");
+            return;
+        }
+            dev_info(mmc_dev(host->mmc), "DMA buffer: phy:%p, virt:%p\n",
+            (void *)host->dma_p_tx[i], host->dma_v_tx[i]);
+    }
+
+    host->dma_v_tx_current = host->dma_v_tx[0];
+    host->dma_p_tx_current = host->dma_p_tx[0];
+    host->next_data.dma_v_tx = host->dma_v_tx[1];
+    host->next_data.dma_p_tx = host->dma_p_tx[1];
+#endif

      /* Try to acquire a generic DMA engine slave channel */
      dma_cap_zero(mask);
@@ -343,12 +382,26 @@ static void __devinit mmci_dma_setup(struct 
mmci_host *host)
  static inline void mmci_dma_release(struct mmci_host *host)
  {
      struct mmci_platform_data *plat = host->plat;
+#ifdef CONFIG_ARCH_LPC32XX
+    int i;
+#endif

      if (host->dma_rx_channel)
          dma_release_channel(host->dma_rx_channel);
      if (host->dma_tx_channel && plat->dma_tx_param)
          dma_release_channel(host->dma_tx_channel);
      host->dma_rx_channel = host->dma_tx_channel = NULL;
+
+#ifdef CONFIG_ARCH_LPC32XX
+    for (i=0; i<2; i++) {
+        if (host->dma_v_tx[i] == NULL)
+            continue;
+        dma_free_coherent(mmc_dev(host->mmc), DMA_TX_SIZE,
+            host->dma_v_tx[i], host->dma_p_tx[i]);
+        host->dma_v_tx[i] = NULL;
+        host->dma_p_tx[i] = 0;
+    }
+#endif
  }

  static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data)
@@ -384,8 +437,9 @@ static void mmci_dma_unmap(struct mmci_host *host, 
struct mmc_data *data)
          dir = DMA_FROM_DEVICE;
      }

-    if (!data->host_cookie)
+    if (data->host_cookie && !(data->host_cookie & COOKIE_SINGLE))
          dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir);
+    /* TODO: no data->host_cookie=0 here ? */

      /*
       * Use of DMA with scatter-gather is impossible.
@@ -421,6 +475,7 @@ static int mmci_dma_prep_data(struct mmci_host 
*host, struct mmc_data *data,
      struct dma_async_tx_descriptor *desc;
      enum dma_data_direction buffer_dirn;
      int nr_sg;
+    bool single = false;

      /* Check if next job is already prepared */
      if (data->host_cookie && !next &&
@@ -440,6 +495,9 @@ static int mmci_dma_prep_data(struct mmci_host 
*host, struct mmc_data *data,
          conf.direction = DMA_MEM_TO_DEV;
          buffer_dirn = DMA_TO_DEVICE;
          chan = host->dma_tx_channel;
+#ifdef CONFIG_ARCH_LPC32XX
+        conf.device_fc = true;
+#endif
      }

      /* If there's no DMA channel, fall back to PIO */
@@ -451,13 +509,46 @@ static int mmci_dma_prep_data(struct mmci_host 
*host, struct mmc_data *data,
          return -EINVAL;

      device = chan->device;
-    nr_sg = dma_map_sg(device->dev, data->sg, data->sg_len, buffer_dirn);
-    if (nr_sg == 0)
-        return -EINVAL;
-
      dmaengine_slave_config(chan, &conf);
-    desc = dmaengine_prep_slave_sg(chan, data->sg, nr_sg,
-                        conf.direction, DMA_CTRL_ACK);
+
+#ifdef CONFIG_ARCH_LPC32XX
+    if ((data->flags & MMC_DATA_WRITE) && (data->sg_len > 1))
+        single = true;
+#endif
+
+    if (single) {
+        int i;
+        unsigned char *dst = next ? next->dma_v_tx : 
host->dma_v_tx_current;
+        size_t len = 0;
+
+        dev_dbg(mmc_dev(host->mmc), "use bounce buffer\n");
+        /* Move data to contiguous buffer first, then transfer it */
+            for (i = 0; i < data->sg_len; i++) {
+            unsigned long flags;
+            struct scatterlist *sg = &data->sg[i];
+            void *src;
+
+                    /* Map the current scatter buffer, copy data, and 
unmap */
+            local_irq_save(flags);
+            src = (unsigned char *)kmap_atomic(sg_page(sg)) + sg->offset;
+                    memcpy(dst + len, src, sg->length);
+                    len += sg->length;
+            kunmap_atomic(src);
+            local_irq_restore(flags);
+            }
+
+        desc = dmaengine_prep_slave_single(chan,
+            next ? next->dma_p_tx : host->dma_p_tx_current,
+            len, buffer_dirn, DMA_CTRL_ACK);
+    } else {
+        nr_sg = dma_map_sg(device->dev, data->sg, data->sg_len, 
buffer_dirn);
+        if (nr_sg == 0)
+            return -EINVAL;
+
+        desc = dmaengine_prep_slave_sg(chan, data->sg, nr_sg,
+                            conf.direction, DMA_CTRL_ACK);
+    }
+
      if (!desc)
          goto unmap_exit;

@@ -469,12 +560,20 @@ static int mmci_dma_prep_data(struct mmci_host 
*host, struct mmc_data *data,
          host->dma_desc_current = desc;
      }

+    data->host_cookie = COOKIE_PREP;
+    if (single)
+        data->host_cookie |= COOKIE_SINGLE;
+    if (next)
+        data->host_cookie |= COOKIE_ID(++next->cookie_cnt);
+
      return 0;

   unmap_exit:
      if (!next)
          dmaengine_terminate_all(chan);
-    dma_unmap_sg(device->dev, data->sg, data->sg_len, buffer_dirn);
+    if (!single)
+        dma_unmap_sg(device->dev, data->sg, data->sg_len, buffer_dirn);
+    data->host_cookie = 0;
      return -ENOMEM;
  }

@@ -512,11 +611,16 @@ static int mmci_dma_start_data(struct mmci_host 
*host, unsigned int datactrl)
  static void mmci_get_next_data(struct mmci_host *host, struct mmc_data 
*data)
  {
      struct mmci_host_next *next = &host->next_data;
+#ifdef CONFIG_ARCH_LPC32XX
+    void *tmp_v;
+    dma_addr_t tmp_p;
+#endif

-    if (data->host_cookie && data->host_cookie != next->cookie) {
-        pr_warning("[%s] invalid cookie: data->host_cookie %d"
-               " host->next_data.cookie %d\n",
-               __func__, data->host_cookie, host->next_data.cookie);
+    if (data->host_cookie && ((data->host_cookie & COOKIE_ID_MASK) !=
+                    COOKIE_ID(next->cookie_cnt))) {
+        pr_warning("[%s] invalid cookie: data->host_cookie=0x%08x"
+               " host->next_data.cookie_cnt=0x%08x\n",
+               __func__, data->host_cookie, host->next_data.cookie_cnt);
          data->host_cookie = 0;
      }

@@ -528,6 +632,15 @@ static void mmci_get_next_data(struct mmci_host 
*host, struct mmc_data *data)

      next->dma_desc = NULL;
      next->dma_chan = NULL;
+
+#ifdef CONFIG_ARCH_LPC32XX
+    tmp_v = host->next_data.dma_v_tx;
+    host->next_data.dma_v_tx = host->dma_v_tx_current;
+    host->dma_v_tx_current = tmp_v;
+    tmp_p = host->next_data.dma_p_tx;
+    host->next_data.dma_p_tx = host->dma_p_tx_current;
+    host->dma_p_tx_current = tmp_p;
+#endif
  }

  static void mmci_pre_request(struct mmc_host *mmc, struct mmc_request 
*mrq,
@@ -551,7 +664,7 @@ static void mmci_pre_request(struct mmc_host *mmc, 
struct mmc_request *mrq,
          if (mmci_dma_prep_data(host, data, nd))
              data->host_cookie = 0;
          else
-            data->host_cookie = ++nd->cookie < 0 ? 1 : nd->cookie;
+            data->host_cookie = COOKIE_ID(++nd->cookie_cnt) | COOKIE_PREP;
      }
  }

@@ -579,7 +692,7 @@ static void mmci_post_request(struct mmc_host *mmc, 
struct mmc_request *mrq,
      if (chan) {
          if (err)
              dmaengine_terminate_all(chan);
-        if (data->host_cookie)
+        if (data->host_cookie && !(data->host_cookie & COOKIE_SINGLE))
              dma_unmap_sg(mmc_dev(host->mmc), data->sg,
                       data->sg_len, dir);
          mrq->data->host_cookie = 0;
@@ -767,6 +880,15 @@ mmci_data_irq(struct mmci_host *host, struct 
mmc_data *data,
          dev_err(mmc_dev(host->mmc), "stray MCI_DATABLOCKEND interrupt\n");

      if (status & MCI_DATAEND || data->error) {
+#ifdef CONFIG_ARCH_LPC32XX
+        /*
+         * On LPC32XX, there is a problem with the DMA flow control and
+         * the last burst transfer is not performed. So we force the
+         * transfer programmatically here.
+         */
+        if (data->flags & MMC_DATA_READ)
+            pl08x_force_dma_burst(host->dma_rx_channel);
+#endif
          if (dma_inprogress(host))
              mmci_dma_unmap(host, data);
          mmci_stop_data(host);
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
index d437ccf..6e1ea3f 100644
--- a/drivers/mmc/host/mmci.h
+++ b/drivers/mmc/host/mmci.h
@@ -159,7 +159,11 @@ struct dma_chan;
  struct mmci_host_next {
      struct dma_async_tx_descriptor    *dma_desc;
      struct dma_chan            *dma_chan;
-    s32                cookie;
+    s32                cookie_cnt;
+#ifdef CONFIG_ARCH_LPC32XX
+    void                *dma_v_tx;
+    dma_addr_t            dma_p_tx;
+#endif
  };

  struct mmci_host {
@@ -202,6 +206,12 @@ struct mmci_host {
      struct dma_chan        *dma_tx_channel;
      struct dma_async_tx_descriptor    *dma_desc_current;
      struct mmci_host_next    next_data;
+#ifdef CONFIG_ARCH_LPC32XX
+    void            *dma_v_tx[2];
+    dma_addr_t        dma_p_tx[2];
+    void            *dma_v_tx_current;
+    dma_addr_t        dma_p_tx_current;
+#endif

  #define dma_inprogress(host)    ((host)->dma_current)
  #else

       reply	other threads:[~2012-12-20 10:06 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <50D2E156.20707@precidata.com>
2012-12-20 10:06 ` Gabriele Mondada [this message]
2012-12-20 10:43   ` LPC32xx and MMCI driver Roland Stigge
2012-12-22 21:24   ` Russell King - ARM Linux
2013-01-23 10:32     ` Gabriele Mondada

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=50D2E317.6000005@precidata.com \
    --to=gabriele@precidata.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.