mmc: tmio, sh_mobile_sdhi: Add support for variable input clock frequency
authorBen Hutchings <ben.hutchings@codethink.co.uk>
Fri, 1 Apr 2016 15:44:32 +0000 (17:44 +0200)
committerUlf Hansson <ulf.hansson@linaro.org>
Mon, 2 May 2016 08:33:12 +0000 (10:33 +0200)
Currently tmio_mmc assumes that the input clock frequency is fixed and
only its own clock divider can be changed.  This is not true in the
case of sh_mobile_sdhi; we can use the clock API to change it.

In tmio_mmc:
- Delegate setting of f_min from tmio to the clk_enable operation (if
  implemented), as it can be smaller than f_max / 512
- Add an optional clk_update operation called from tmio_mmc_set_clock()
  that updates the input clock frequency
- Rename tmio_mmc_clk_update() to tmio_mmc_clk_enable(), to avoid
  confusion with the clk_update operation

In sh_mobile_sdhi:
- Make the setting of f_max conditional; it should be set through the
  max-frequency property in the device tree in future
- Set f_min based on the input clock's minimum frequency
- Implement the clk_update operation, selecting the best input clock
  frequency for the bus frequency that's wanted

sh_mobile_sdhi_clk_update() is loosely based on Kuninori Morimoto's work
in sh_mmcif.

Signed-off-by: Ben Hutchings <ben.hutchings@codethink.co.uk>
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/host/sh_mobile_sdhi.c
drivers/mmc/host/tmio_mmc.h
drivers/mmc/host/tmio_mmc_pio.c

index 5584935..8fd1d6b 100644 (file)
@@ -139,7 +139,20 @@ static int sh_mobile_sdhi_clk_enable(struct tmio_mmc_host *host)
        if (ret < 0)
                return ret;
 
-       mmc->f_max = clk_get_rate(priv->clk);
+       /*
+        * The clock driver may not know what maximum frequency
+        * actually works, so it should be set with the max-frequency
+        * property which will already have been read to f_max.  If it
+        * was missing, assume the current frequency is the maximum.
+        */
+       if (!mmc->f_max)
+               mmc->f_max = clk_get_rate(priv->clk);
+
+       /*
+        * Minimum frequency is the minimum input clock frequency
+        * divided by our maximum divider.
+        */
+       mmc->f_min = max(clk_round_rate(priv->clk, 1) / 512, 1L);
 
        /* enable 16bit data access on SDBUF as default */
        sh_mobile_sdhi_sdbuf_width(host, 16);
@@ -147,6 +160,44 @@ static int sh_mobile_sdhi_clk_enable(struct tmio_mmc_host *host)
        return 0;
 }
 
+static unsigned int sh_mobile_sdhi_clk_update(struct tmio_mmc_host *host,
+                                             unsigned int new_clock)
+{
+       struct sh_mobile_sdhi *priv = host_to_priv(host);
+       unsigned int freq, best_freq, diff_min, diff;
+       int i;
+
+       diff_min = ~0;
+       best_freq = 0;
+
+       /*
+        * We want the bus clock to be as close as possible to, but no
+        * greater than, new_clock.  As we can divide by 1 << i for
+        * any i in [0, 9] we want the input clock to be as close as
+        * possible, but no greater than, new_clock << i.
+        */
+       for (i = min(9, ilog2(UINT_MAX / new_clock)); i >= 0; i--) {
+               freq = clk_round_rate(priv->clk, new_clock << i);
+               if (freq > (new_clock << i)) {
+                       /* Too fast; look for a slightly slower option */
+                       freq = clk_round_rate(priv->clk,
+                                             (new_clock << i) / 4 * 3);
+                       if (freq > (new_clock << i))
+                               continue;
+               }
+
+               diff = new_clock - (freq >> i);
+               if (diff <= diff_min) {
+                       best_freq = freq;
+                       diff_min = diff;
+               }
+       }
+
+       clk_set_rate(priv->clk, best_freq);
+
+       return best_freq;
+}
+
 static void sh_mobile_sdhi_clk_disable(struct tmio_mmc_host *host)
 {
        struct sh_mobile_sdhi *priv = host_to_priv(host);
@@ -265,6 +316,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
        host->dma               = dma_priv;
        host->write16_hook      = sh_mobile_sdhi_write16_hook;
        host->clk_enable        = sh_mobile_sdhi_clk_enable;
+       host->clk_update        = sh_mobile_sdhi_clk_update;
        host->clk_disable       = sh_mobile_sdhi_clk_disable;
        host->multi_io_quirk    = sh_mobile_sdhi_multi_io_quirk;
 
@@ -362,7 +414,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
                }
        }
 
-       dev_info(&pdev->dev, "%s base at 0x%08lx clock rate %u MHz\n",
+       dev_info(&pdev->dev, "%s base at 0x%08lx max clock rate %u MHz\n",
                 mmc_hostname(host->mmc), (unsigned long)
                 (platform_get_resource(pdev, IORESOURCE_MEM, 0)->start),
                 host->mmc->f_max / 1000000);
index 68fd8d7..b44b589 100644 (file)
@@ -96,6 +96,8 @@ struct tmio_mmc_host {
 
        int (*write16_hook)(struct tmio_mmc_host *host, int addr);
        int (*clk_enable)(struct tmio_mmc_host *host);
+       unsigned int (*clk_update)(struct tmio_mmc_host *host,
+                                  unsigned int new_clock);
        void (*clk_disable)(struct tmio_mmc_host *host);
        int (*multi_io_quirk)(struct mmc_card *card,
                              unsigned int direction, int blk_size);
index e1e5759..ecc16a3 100644 (file)
@@ -160,9 +160,12 @@ static void tmio_mmc_set_clock(struct tmio_mmc_host *host,
        u32 clk = 0, clock;
 
        if (new_clock) {
-               for (clock = host->mmc->f_min, clk = 0x80000080;
-                    new_clock >= (clock << 1);
-                    clk >>= 1)
+               if (host->clk_update)
+                       clock = host->clk_update(host, new_clock) / 512;
+               else
+                       clock = host->mmc->f_min;
+
+               for (clk = 0x80000080; new_clock >= (clock << 1); clk >>= 1)
                        clock <<= 1;
 
                /* 1/1 clock is option */
@@ -837,19 +840,12 @@ fail:
        pm_runtime_put_autosuspend(mmc_dev(mmc));
 }
 
-static int tmio_mmc_clk_update(struct tmio_mmc_host *host)
+static int tmio_mmc_clk_enable(struct tmio_mmc_host *host)
 {
-       struct mmc_host *mmc = host->mmc;
-       int ret;
-
        if (!host->clk_enable)
                return -ENOTSUPP;
 
-       ret = host->clk_enable(host);
-       if (!ret)
-               mmc->f_min = mmc->f_max / 512;
-
-       return ret;
+       return host->clk_enable(host);
 }
 
 static void tmio_mmc_power_on(struct tmio_mmc_host *host, unsigned short vdd)
@@ -1135,7 +1131,7 @@ int tmio_mmc_host_probe(struct tmio_mmc_host *_host,
                                  mmc->caps & MMC_CAP_NONREMOVABLE ||
                                  mmc->slot.cd_irq >= 0);
 
-       if (tmio_mmc_clk_update(_host) < 0) {
+       if (tmio_mmc_clk_enable(_host) < 0) {
                mmc->f_max = pdata->hclk;
                mmc->f_min = mmc->f_max / 512;
        }
@@ -1263,7 +1259,7 @@ int tmio_mmc_host_runtime_resume(struct device *dev)
        struct tmio_mmc_host *host = mmc_priv(mmc);
 
        tmio_mmc_reset(host);
-       tmio_mmc_clk_update(host);
+       tmio_mmc_clk_enable(host);
 
        if (host->clk_cache) {
                tmio_mmc_set_clock(host, host->clk_cache);