Commit 2fb55956 authored by Ben Hutchings's avatar Ben Hutchings Committed by Ulf Hansson

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: default avatarBen Hutchings <ben.hutchings@codethink.co.uk>
Signed-off-by: default avatarWolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent 0ea28210
...@@ -139,14 +139,65 @@ static int sh_mobile_sdhi_clk_enable(struct tmio_mmc_host *host) ...@@ -139,14 +139,65 @@ static int sh_mobile_sdhi_clk_enable(struct tmio_mmc_host *host)
if (ret < 0) if (ret < 0)
return ret; return ret;
/*
* 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); 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 */ /* enable 16bit data access on SDBUF as default */
sh_mobile_sdhi_sdbuf_width(host, 16); sh_mobile_sdhi_sdbuf_width(host, 16);
return 0; 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) static void sh_mobile_sdhi_clk_disable(struct tmio_mmc_host *host)
{ {
struct sh_mobile_sdhi *priv = host_to_priv(host); struct sh_mobile_sdhi *priv = host_to_priv(host);
...@@ -265,6 +316,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) ...@@ -265,6 +316,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
host->dma = dma_priv; host->dma = dma_priv;
host->write16_hook = sh_mobile_sdhi_write16_hook; host->write16_hook = sh_mobile_sdhi_write16_hook;
host->clk_enable = sh_mobile_sdhi_clk_enable; 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->clk_disable = sh_mobile_sdhi_clk_disable;
host->multi_io_quirk = sh_mobile_sdhi_multi_io_quirk; host->multi_io_quirk = sh_mobile_sdhi_multi_io_quirk;
...@@ -362,7 +414,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) ...@@ -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) mmc_hostname(host->mmc), (unsigned long)
(platform_get_resource(pdev, IORESOURCE_MEM, 0)->start), (platform_get_resource(pdev, IORESOURCE_MEM, 0)->start),
host->mmc->f_max / 1000000); host->mmc->f_max / 1000000);
......
...@@ -96,6 +96,8 @@ struct tmio_mmc_host { ...@@ -96,6 +96,8 @@ struct tmio_mmc_host {
int (*write16_hook)(struct tmio_mmc_host *host, int addr); int (*write16_hook)(struct tmio_mmc_host *host, int addr);
int (*clk_enable)(struct tmio_mmc_host *host); 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); void (*clk_disable)(struct tmio_mmc_host *host);
int (*multi_io_quirk)(struct mmc_card *card, int (*multi_io_quirk)(struct mmc_card *card,
unsigned int direction, int blk_size); unsigned int direction, int blk_size);
......
...@@ -160,9 +160,12 @@ static void tmio_mmc_set_clock(struct tmio_mmc_host *host, ...@@ -160,9 +160,12 @@ static void tmio_mmc_set_clock(struct tmio_mmc_host *host,
u32 clk = 0, clock; u32 clk = 0, clock;
if (new_clock) { if (new_clock) {
for (clock = host->mmc->f_min, clk = 0x80000080; if (host->clk_update)
new_clock >= (clock << 1); clock = host->clk_update(host, new_clock) / 512;
clk >>= 1) else
clock = host->mmc->f_min;
for (clk = 0x80000080; new_clock >= (clock << 1); clk >>= 1)
clock <<= 1; clock <<= 1;
/* 1/1 clock is option */ /* 1/1 clock is option */
...@@ -837,19 +840,12 @@ static void tmio_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) ...@@ -837,19 +840,12 @@ static void tmio_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
pm_runtime_put_autosuspend(mmc_dev(mmc)); 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) if (!host->clk_enable)
return -ENOTSUPP; return -ENOTSUPP;
ret = host->clk_enable(host); return host->clk_enable(host);
if (!ret)
mmc->f_min = mmc->f_max / 512;
return ret;
} }
static void tmio_mmc_power_on(struct tmio_mmc_host *host, unsigned short vdd) 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, ...@@ -1135,7 +1131,7 @@ int tmio_mmc_host_probe(struct tmio_mmc_host *_host,
mmc->caps & MMC_CAP_NONREMOVABLE || mmc->caps & MMC_CAP_NONREMOVABLE ||
mmc->slot.cd_irq >= 0); 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_max = pdata->hclk;
mmc->f_min = mmc->f_max / 512; mmc->f_min = mmc->f_max / 512;
} }
...@@ -1263,7 +1259,7 @@ int tmio_mmc_host_runtime_resume(struct device *dev) ...@@ -1263,7 +1259,7 @@ int tmio_mmc_host_runtime_resume(struct device *dev)
struct tmio_mmc_host *host = mmc_priv(mmc); struct tmio_mmc_host *host = mmc_priv(mmc);
tmio_mmc_reset(host); tmio_mmc_reset(host);
tmio_mmc_clk_update(host); tmio_mmc_clk_enable(host);
if (host->clk_cache) { if (host->clk_cache) {
tmio_mmc_set_clock(host, host->clk_cache); tmio_mmc_set_clock(host, host->clk_cache);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment