mmc: sdhci-s3c: Support controllers with no internal clock divider
authorJeongbae Seo <jeongbae.seo@samsung.com>
Fri, 8 Oct 2010 08:46:21 +0000 (17:46 +0900)
committerChris Ball <cjb@laptop.org>
Sun, 9 Jan 2011 04:52:22 +0000 (23:52 -0500)
This patch supports controllers with no internal clock divider in SDHCI,
such as the s5pc210 board.  The external clock divider can be used to
make a proper clock because SDHCI doesn't support internal clock divider
by itself.

If external clock divider type is selected, some functions related
to clock control will be overrided by other functions.

The current clock control index is added to let you know which
clock bus is used for SDHCI when overriding functions.

Checking functions are added into sdhci_s3c_consider_clock, because
the clock divider step is different from that of host controller.

Signed-off-by: Jeongbae Seo <jeongbae.seo@samsung.com>
Cc: Jaehoon Chung <jh80.chung@samsung.com>
Cc: Ben Dooks <ben-linux@fluff.org>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Chris Ball <cjb@laptop.org>
drivers/mmc/host/sdhci-s3c.c

index a7710f557849200b9b3d9641ad004472f41d6395..17203586305cf57f931449de97a7db7c5d099262 100644 (file)
@@ -130,6 +130,15 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost,
        if (!clksrc)
                return UINT_MAX;
 
+       /*
+        * Clock divider's step is different as 1 from that of host controller
+        * when 'clk_type' is S3C_SDHCI_CLK_DIV_EXTERNAL.
+        */
+       if (ourhost->pdata->clk_type) {
+               rate = clk_round_rate(clksrc, wanted);
+               return wanted - rate;
+       }
+
        rate = clk_get_rate(clksrc);
 
        for (div = 1; div < 256; div *= 2) {
@@ -232,6 +241,42 @@ static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host)
        return min;
 }
 
+/* sdhci_cmu_get_max_clk - callback to get maximum clock frequency.*/
+static unsigned int sdhci_cmu_get_max_clock(struct sdhci_host *host)
+{
+       struct sdhci_s3c *ourhost = to_s3c(host);
+
+       return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], UINT_MAX);
+}
+
+/* sdhci_cmu_get_min_clock - callback to get minimal supported clock value. */
+static unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host)
+{
+       struct sdhci_s3c *ourhost = to_s3c(host);
+
+       /*
+        * initial clock can be in the frequency range of
+        * 100KHz-400KHz, so we set it as max value.
+        */
+       return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], 400000);
+}
+
+/* sdhci_cmu_set_clock - callback on clock change.*/
+static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+       struct sdhci_s3c *ourhost = to_s3c(host);
+
+       /* don't bother if the clock is going off */
+       if (clock == 0)
+               return;
+
+       sdhci_s3c_set_clock(host, clock);
+
+       clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock);
+
+       host->clock = clock;
+}
+
 static struct sdhci_ops sdhci_s3c_ops = {
        .get_max_clock          = sdhci_s3c_get_max_clk,
        .set_clock              = sdhci_s3c_set_clock,
@@ -361,6 +406,13 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
 
                clks++;
                sc->clk_bus[ptr] = clk;
+
+               /*
+                * save current clock index to know which clock bus
+                * is used later in overriding functions.
+                */
+               sc->cur_clk = ptr;
+
                clk_enable(clk);
 
                dev_info(dev, "clock source %d: %s (%ld Hz)\n",
@@ -427,6 +479,16 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
        /* HSMMC on Samsung SoCs uses SDCLK as timeout clock */
        host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK;
 
+       /*
+        * If controller does not have internal clock divider,
+        * we can use overriding functions instead of default.
+        */
+       if (pdata->clk_type) {
+               sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock;
+               sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock;
+               sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock;
+       }
+
        /* It supports additional host capabilities if needed */
        if (pdata->host_caps)
                host->mmc->caps |= pdata->host_caps;