diff options
author | Ben Hutchings <ben.hutchings@codethink.co.uk> | 2016-04-01 17:44:32 +0200 |
---|---|---|
committer | Ulf Hansson <ulf.hansson@linaro.org> | 2016-05-02 10:33:12 +0200 |
commit | 2fb55956ce4db259a5d6ce41cc32368b5055575e (patch) | |
tree | ebbab98e127f8f43614625c31e64156bfef30175 | |
parent | 0ea28210c15680bbabc3d6079f771f1a1e69509a (diff) |
mmc: tmio, sh_mobile_sdhi: Add support for variable input clock frequency
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>
-rw-r--r-- | drivers/mmc/host/sh_mobile_sdhi.c | 56 | ||||
-rw-r--r-- | drivers/mmc/host/tmio_mmc.h | 2 | ||||
-rw-r--r-- | drivers/mmc/host/tmio_mmc_pio.c | 24 |
3 files changed, 66 insertions, 16 deletions
diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 55849350202b..8fd1d6b29190 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -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); diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index 68fd8d791358..b44b58902906 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -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); diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index e1e57596dbe8..ecc16a3ee786 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -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); |