Add a change_clock callback to allow drivers to update
device specific clock selections and control registers
when there is a change in clock.
Move the main part of sdhci_set_clock() to a new routine
which can be called by the glue drivers to do the sdhci
standard clock management.
Update the sdhci-s3c driver to use this to select the
appropriate clock source when clocks change.
Signed-off-by: Ben Dooks <[email protected]>
Index: linux.git/drivers/mmc/host/sdhci-pci.c
===================================================================
--- linux.git.orig/drivers/mmc/host/sdhci-pci.c 2008-11-03 12:17:50.000000000 +0000
+++ linux.git/drivers/mmc/host/sdhci-pci.c 2008-11-03 12:18:41.000000000 +0000
@@ -391,6 +391,7 @@ static int sdhci_pci_enable_dma(struct s
static struct sdhci_ops sdhci_pci_ops = {
.enable_dma = sdhci_pci_enable_dma,
+ .change_clock = sdhci_change_clock,
};
/*****************************************************************************\
Index: linux.git/drivers/mmc/host/sdhci-s3c.c
===================================================================
--- linux.git.orig/drivers/mmc/host/sdhci-s3c.c 2008-11-03 12:18:27.000000000 +0000
+++ linux.git/drivers/mmc/host/sdhci-s3c.c 2008-11-03 12:18:41.000000000 +0000
@@ -20,6 +20,7 @@
#include <linux/mmc/host.h>
+#include <plat/regs-sdhci.h>
#include <plat/sdhci.h>
#include "sdhci.h"
@@ -31,6 +32,7 @@ struct sdhci_s3c {
struct platform_device *pdev;
struct resource *ioarea;
struct s3c_sdhci_platdata *pdata;
+ unsigned int cur_clk;
struct clk *clk_io; /* clock for io bus */
struct clk *clk_bus[MAX_BUS_CLK];
@@ -41,38 +43,50 @@ static inline struct sdhci_s3c *to_s3c(s
return sdhci_priv(host);
}
+static u32 get_curclk(u32 ctrl2)
+{
+ ctrl2 &= S3C_SDHCI_CTRL2_SELBASECLK_MASK;
+ ctrl2 >>= S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
+
+ return ctrl2;
+}
-static void sdhci_s3c_sel_sclk(struct sdhci_host *host)
+static void sdhci_s3c_check_sclk(struct sdhci_host *host)
{
struct sdhci_s3c *ourhost = to_s3c(host);
+ u32 tmp = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
- /* select sclk */
- u32 tmp = readl(host->ioaddr + 0x80);
+ if (get_curclk(tmp) != ourhost->cur_clk) {
+ dev_dbg(&ourhost->pdev->dev, "restored ctrl2 clock setting\n");
- if ((tmp & (3 << 4)) == (2 << 4))
- return;
-
- tmp &= ~(3<<4);
- tmp |= (2 << 4);
- writel(tmp, host->ioaddr + 0x80);
+ tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
+ tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
+ writel(tmp, host->ioaddr + 0x80);
+ }
}
-
static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host)
{
struct sdhci_s3c *ourhost = to_s3c(host);
- u32 control2;
- unsigned int rate;
+ struct clk *busclk;
+ unsigned int rate, max;
int clk;
/* note, a reset will reset the clock source */
- sdhci_s3c_sel_sclk(host);
+ sdhci_s3c_check_sclk(host);
+
+ for (max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) {
+ busclk = ourhost->clk_bus[clk];
+ if (!busclk)
+ continue;
- control2 = readl(host->ioaddr + 0x80);
- clk = clk_get_rate(ourhost->clk_bus[(control2 >> 4) & 3]);
+ rate = clk_get_rate(busclk);
+ if (rate > max)
+ max = rate;
+ }
- return clk;
+ return max;
}
static unsigned int sdhci_s3c_get_timeout_clk(struct sdhci_host *host)
@@ -87,7 +101,7 @@ static void sdhci_s3c_set_ios(struct sdh
struct s3c_sdhci_platdata *pdata = ourhost->pdata;
int width;
- sdhci_s3c_sel_sclk(host);
+ sdhci_s3c_check_sclk(host);
if (ios->power_mode != MMC_POWER_OFF) {
switch (ios->bus_width) {
@@ -110,9 +124,76 @@ static void sdhci_s3c_set_ios(struct sdh
ios, host->mmc->card);
}
+static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost,
+ unsigned int src,
+ unsigned int wanted)
+{
+ unsigned long rate;
+ struct clk *clksrc = ourhost->clk_bus[src];
+ int div;
+
+ if (!clksrc)
+ return UINT_MAX;
+
+ rate = clk_get_rate(clksrc);
+
+ for (div = 1; div < 256; div *= 2) {
+ if ((rate / div) <= wanted)
+ break;
+ }
+
+ dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n",
+ src, rate, wanted, rate / div);
+
+ return (wanted - (rate / div));
+}
+
+static void sdhci_s3c_change_clock(struct sdhci_host *host, unsigned int clock)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+ unsigned int best = UINT_MAX;
+ unsigned int delta;
+ int best_src = 0;
+ int src;
+ u32 ctrl;
+
+ for (src = 0; src < MAX_BUS_CLK; src++) {
+ delta = sdhci_s3c_consider_clock(ourhost, src, clock);
+ if (delta < best) {
+ best = delta;
+ best_src = src;
+ }
+ }
+
+ dev_dbg(&ourhost->pdev->dev,
+ "selected source %d, clock %d, delta %d\n",
+ best_src, clock, best);
+
+ /* turn clock off to card before changing clock source */
+ writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);
+
+ /* select the new clock source */
+
+ if (ourhost->cur_clk != best_src) {
+ struct clk *clk = ourhost->clk_bus[best_src];
+
+ ourhost->cur_clk = best_src;
+ host->max_clk = clk_get_rate(clk);
+ host->timeout_clk = host->max_clk / 1000000;
+
+ ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
+ ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
+ ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
+ writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
+ }
+
+ sdhci_change_clock(host, clock);
+}
+
static struct sdhci_ops sdhci_s3c_ops = {
.get_max_clock = sdhci_s3c_get_max_clk,
.get_timeout_clock = sdhci_s3c_get_timeout_clk,
+ .change_clock = sdhci_s3c_change_clock,
.set_ios = sdhci_s3c_set_ios,
};
@@ -210,7 +291,7 @@ static int __devinit sdhci_s3c_probe(str
if (pdata->cfg_gpio)
pdata->cfg_gpio(pdev, 0);
- sdhci_s3c_sel_sclk(host);
+ sdhci_s3c_check_sclk(host);
host->hw_name = "samsung-hsmmc";
host->ops = &sdhci_s3c_ops;
Index: linux.git/drivers/mmc/host/sdhci.c
===================================================================
--- linux.git.orig/drivers/mmc/host/sdhci.c 2008-11-03 12:18:39.000000000 +0000
+++ linux.git/drivers/mmc/host/sdhci.c 2008-11-03 12:18:41.000000000 +0000
@@ -907,13 +907,18 @@ static void sdhci_finish_command(struct
static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
{
+ if (clock == host->clock)
+ return;
+
+ host->ops->change_clock(host, clock);
+}
+
+void sdhci_change_clock(struct sdhci_host *host, unsigned int clock)
+{
int div;
u16 clk;
unsigned long timeout;
- if (clock == host->clock)
- return;
-
writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);
if (clock == 0)
@@ -950,6 +955,8 @@ out:
host->clock = clock;
}
+EXPORT_SYMBOL_GPL(sdhci_set_clock);
+
static void sdhci_set_power(struct sdhci_host *host, unsigned short power)
{
u8 pwr;
Index: linux.git/drivers/mmc/host/sdhci.h
===================================================================
--- linux.git.orig/drivers/mmc/host/sdhci.h 2008-11-03 12:17:50.000000000 +0000
+++ linux.git/drivers/mmc/host/sdhci.h 2008-11-03 12:18:41.000000000 +0000
@@ -273,6 +273,9 @@ struct sdhci_ops {
unsigned int (*get_max_clock)(struct sdhci_host *host);
unsigned int (*get_timeout_clock)(struct sdhci_host *host);
+ void (*change_clock)(struct sdhci_host *host,
+ unsigned int clock);
+
void (*set_ios)(struct sdhci_host *host,
struct mmc_ios *ios);
};
@@ -282,6 +285,8 @@ extern struct sdhci_host *sdhci_alloc_ho
size_t priv_size);
extern void sdhci_free_host(struct sdhci_host *host);
+extern void sdhci_change_clock(struct sdhci_host *host, unsigned int clock);
+
static inline void *sdhci_priv(struct sdhci_host *host)
{
return (void *)host->private;
--
Ben ([email protected], http://www.fluff.org/)
'a smiley only costs 4 bytes'
On Mon, 03 Nov 2008 20:09:51 +0000
Ben Dooks <[email protected]> wrote:
> Add a change_clock callback to allow drivers to update
> device specific clock selections and control registers
> when there is a change in clock.
>
> Move the main part of sdhci_set_clock() to a new routine
> which can be called by the glue drivers to do the sdhci
> standard clock management.
>
> Update the sdhci-s3c driver to use this to select the
> appropriate clock source when clocks change.
>
> Signed-off-by: Ben Dooks <[email protected]>
>
I'm afraid I don't quite follow on the requirements here. Care to
elaborate as to why this is needed?
> Index: linux.git/drivers/mmc/host/sdhci-pci.c
> ===================================================================
> --- linux.git.orig/drivers/mmc/host/sdhci-pci.c 2008-11-03 12:17:50.000000000 +0000
> +++ linux.git/drivers/mmc/host/sdhci-pci.c 2008-11-03 12:18:41.000000000 +0000
> @@ -391,6 +391,7 @@ static int sdhci_pci_enable_dma(struct s
>
> static struct sdhci_ops sdhci_pci_ops = {
> .enable_dma = sdhci_pci_enable_dma,
> + .change_clock = sdhci_change_clock,
> };
>
> /*****************************************************************************\
This seems pointless :)
> @@ -950,6 +955,8 @@ out:
> host->clock = clock;
> }
>
> +EXPORT_SYMBOL_GPL(sdhci_set_clock);
> +
Wrong symbol. :)
--
-- Pierre Ossman
Linux kernel, MMC maintainer http://www.kernel.org
rdesktop, core developer http://www.rdesktop.org
WARNING: This correspondence is being monitored by the
Swedish government. Make sure your server uses encryption
for SMTP traffic and consider using PGP for end-to-end
encryption.
On Fri, Nov 14, 2008 at 11:20:21PM +0100, Pierre Ossman wrote:
> On Mon, 03 Nov 2008 20:09:51 +0000
> Ben Dooks <[email protected]> wrote:
>
> > Add a change_clock callback to allow drivers to update
> > device specific clock selections and control registers
> > when there is a change in clock.
> >
> > Move the main part of sdhci_set_clock() to a new routine
> > which can be called by the glue drivers to do the sdhci
> > standard clock management.
> >
> > Update the sdhci-s3c driver to use this to select the
> > appropriate clock source when clocks change.
> >
> > Signed-off-by: Ben Dooks <[email protected]>
> >
>
> I'm afraid I don't quite follow on the requirements here. Care to
> elaborate as to why this is needed?
The controller has a number of possible clock sources, the bus clock
it is connected to (generally called hclk) and three more clocks from
the heirachy that is muxed/divided from either an 48MHz, 27MHz or the
EPLL.
The callback allows the recalculation and selection of the best clock
to run the card clock for the controller.
> > Index: linux.git/drivers/mmc/host/sdhci-pci.c
> > ===================================================================
> > --- linux.git.orig/drivers/mmc/host/sdhci-pci.c 2008-11-03 12:17:50.000000000 +0000
> > +++ linux.git/drivers/mmc/host/sdhci-pci.c 2008-11-03 12:18:41.000000000 +0000
> > @@ -391,6 +391,7 @@ static int sdhci_pci_enable_dma(struct s
> >
> > static struct sdhci_ops sdhci_pci_ops = {
> > .enable_dma = sdhci_pci_enable_dma,
> > + .change_clock = sdhci_change_clock,
> > };
> >
> > /*****************************************************************************\
>
> This seems pointless :)
>
> > @@ -950,6 +955,8 @@ out:
> > host->clock = clock;
> > }
> >
> > +EXPORT_SYMBOL_GPL(sdhci_set_clock);
> > +
>
> Wrong symbol. :)
ok, will fix
--
Ben ([email protected], http://www.fluff.org/)
'a smiley only costs 4 bytes'
On Sat, 15 Nov 2008 23:57:39 +0000
Ben Dooks <[email protected]> wrote:
>
> The controller has a number of possible clock sources, the bus clock
> it is connected to (generally called hclk) and three more clocks from
> the heirachy that is muxed/divided from either an 48MHz, 27MHz or the
> EPLL.
>
> The callback allows the recalculation and selection of the best clock
> to run the card clock for the controller.
>
Can these clock sources change mid-flight? Otherwise I don't see the
need for a callback up to the sdhci core. But I don't see any
connection points for such events.
--
-- Pierre Ossman
Linux kernel, MMC maintainer http://www.kernel.org
rdesktop, core developer http://www.rdesktop.org
WARNING: This correspondence is being monitored by the
Swedish government. Make sure your server uses encryption
for SMTP traffic and consider using PGP for end-to-end
encryption.