[ARM] 3578/1: AT91RM9200 Clock update
authorAndrew Victor <andrew@sanpeople.com>
Mon, 19 Jun 2006 12:20:23 +0000 (13:20 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Mon, 19 Jun 2006 12:20:23 +0000 (13:20 +0100)
Patch from Andrew Victor

Some updates to the clock infrastructure for the AT91RM9200.

1. Hard-coded values replaced with names defined in at91rm9200_sys.h.
2. Added the four PIO clocks, which are enabled at startup.
3. At startup, disable all unused clocks.
4. Minor bugfix for usage counts associated with MCK. [Patch from David
Brownell]
5. Added at91_clock_associate() function to associate device & function
with a particular clock.  [Patch from David Brownell]

Signed-off-by: Andrew Victor <andrew@sanpeople.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
arch/arm/mach-at91rm9200/clock.c

index 8b95467c6d6170566d55c2b6dfed9c7bdbb4a8f2..30042d2bac5fc9d1ec79e96aa76fce092a16b68b 100644 (file)
@@ -32,8 +32,6 @@
 
 #include "generic.h"
 
-#undef DEBUG
-
 /*
  * There's a lot more which can be done with clocks, including cpufreq
  * integration, slow clock mode support (for system suspend), letting
@@ -41,7 +39,9 @@
  */
 
 struct clk {
-       const char      *name;
+       const char      *name;          /* unique clock name */
+       const char      *function;      /* function of the clock */
+       struct device   *dev;           /* device associated with function */
        unsigned long   rate_hz;
        struct clk      *parent;
        u32             pmc_mask;
@@ -71,15 +71,14 @@ static struct clk clk32k = {
 };
 static struct clk main_clk = {
        .name           = "main",
-       .pmc_mask       = 1 << 0,       /* in PMC_SR */
-       .users          = 1,
+       .pmc_mask       = AT91_PMC_MOSCS,       /* in PMC_SR */
        .id             = 1,
        .primary        = 1,
 };
 static struct clk plla = {
        .name           = "plla",
        .parent         = &main_clk,
-       .pmc_mask       = 1 << 1,       /* in PMC_SR */
+       .pmc_mask       = AT91_PMC_LOCKA,       /* in PMC_SR */
        .id             = 2,
        .primary        = 1,
        .pll            = 1,
@@ -105,7 +104,7 @@ static void pllb_mode(struct clk *clk, int is_on)
 static struct clk pllb = {
        .name           = "pllb",
        .parent         = &main_clk,
-       .pmc_mask       = 1 << 2,       /* in PMC_SR */
+       .pmc_mask       = AT91_PMC_LOCKB,       /* in PMC_SR */
        .mode           = pllb_mode,
        .id             = 3,
        .primary        = 1,
@@ -177,8 +176,7 @@ static struct clk pck3 = {
  */
 static struct clk mck = {
        .name           = "mck",
-       .pmc_mask       = 1 << 3,       /* in PMC_SR */
-       .users          = 1,            /* (must be) always on */
+       .pmc_mask       = AT91_PMC_MCKRDY,      /* in PMC_SR */
 };
 
 static void pmc_periph_mode(struct clk *clk, int is_on)
@@ -249,6 +247,30 @@ static struct clk spi_clk = {
        .pmc_mask       = 1 << AT91_ID_SPI,
        .mode           = pmc_periph_mode,
 };
+static struct clk pioA_clk = {
+       .name           = "pioA_clk",
+       .parent         = &mck,
+       .pmc_mask       = 1 << AT91_ID_PIOA,
+       .mode           = pmc_periph_mode,
+};
+static struct clk pioB_clk = {
+       .name           = "pioB_clk",
+       .parent         = &mck,
+       .pmc_mask       = 1 << AT91_ID_PIOB,
+       .mode           = pmc_periph_mode,
+};
+static struct clk pioC_clk = {
+       .name           = "pioC_clk",
+       .parent         = &mck,
+       .pmc_mask       = 1 << AT91_ID_PIOC,
+       .mode           = pmc_periph_mode,
+};
+static struct clk pioD_clk = {
+       .name           = "pioD_clk",
+       .parent         = &mck,
+       .pmc_mask       = 1 << AT91_ID_PIOD,
+       .mode           = pmc_periph_mode,
+};
 
 static struct clk *const clock_list[] = {
        /* four primary clocks -- MUST BE FIRST! */
@@ -279,21 +301,46 @@ static struct clk *const clock_list[] = {
        &udc_clk,
        &twi_clk,
        &spi_clk,
+       &pioA_clk,
+       &pioB_clk,
+       &pioC_clk,
+       &pioD_clk,
        // ssc0..ssc2
        // tc0..tc5
+       // irq0..irq6
        &ohci_clk,
        &ether_clk,
 };
 
 
+/*
+ * Associate a particular clock with a function (eg, "uart") and device.
+ * The drivers can then request the same 'function' with several different
+ * devices and not care about which clock name to use.
+ */
+void __init at91_clock_associate(const char *id, struct device *dev, const char *func)
+{
+       struct clk *clk = clk_get(NULL, id);
+
+       if (!dev || !clk || !IS_ERR(clk_get(dev, func)))
+               return;
+
+       clk->function = func;
+       clk->dev = dev;
+}
+
 /* clocks are all static for now; no refcounting necessary */
 struct clk *clk_get(struct device *dev, const char *id)
 {
        int i;
 
        for (i = 0; i < ARRAY_SIZE(clock_list); i++) {
-               if (strcmp(id, clock_list[i]->name) == 0)
-                       return clock_list[i];
+               struct clk *clk = clock_list[i];
+
+               if (strcmp(id, clk->name) == 0)
+                       return clk;
+               if (clk->function && (dev == clk->dev) && strcmp(id, clk->function) == 0)
+                       return clk;
        }
 
        return ERR_PTR(-ENOENT);
@@ -593,6 +640,30 @@ fail:
        return 0;
 }
 
+
+/*
+ * Several unused clocks may be active.  Turn them off.
+ */
+static void at91_periphclk_reset(void)
+{
+       unsigned long reg;
+       int i;
+
+       reg = at91_sys_read(AT91_PMC_PCSR);
+
+       for (i = 0; i < ARRAY_SIZE(clock_list); i++) {
+               struct clk      *clk = clock_list[i];
+
+               if (clk->mode != pmc_periph_mode)
+                       continue;
+
+               if (clk->users > 0)
+                       reg &= ~clk->pmc_mask;
+       }
+
+       at91_sys_write(AT91_PMC_PCDR, reg);
+}
+
 int __init at91_clock_init(unsigned long main_clock)
 {
        unsigned tmp, freq, mckr;
@@ -626,7 +697,6 @@ int __init at91_clock_init(unsigned long main_clock)
         */
        at91_pllb_usb_init = at91_pll_calc(main_clock, 48000000 * 2) | AT91_PMC_USB96M;
        pllb.rate_hz = at91_pll_rate(&pllb, main_clock, at91_pllb_usb_init);
-       at91_sys_write(AT91_PMC_PCDR, (1 << AT91_ID_UHP) | (1 << AT91_ID_UDP));
        at91_sys_write(AT91_PMC_SCDR, AT91_PMC_UHP | AT91_PMC_UDP);
        at91_sys_write(AT91_CKGR_PLLBR, 0);
        at91_sys_write(AT91_PMC_SCER, AT91_PMC_MCKUDP);
@@ -640,11 +710,13 @@ int __init at91_clock_init(unsigned long main_clock)
         */
        mckr = at91_sys_read(AT91_PMC_MCKR);
        mck.parent = clock_list[mckr & AT91_PMC_CSS];
-       mck.parent->users++;
        freq = mck.parent->rate_hz;
        freq /= (1 << ((mckr >> 2) & 3));               /* prescale */
        mck.rate_hz = freq / (1 + ((mckr >> 8) & 3));   /* mdiv */
 
+       /* MCK and CPU clock are "always on" */
+       clk_enable(&mck);
+
        printk("Clocks: CPU %u MHz, master %u MHz, main %u.%03u MHz\n",
                freq / 1000000, (unsigned) mck.rate_hz / 1000000,
                (unsigned) main_clock / 1000000,
@@ -663,19 +735,28 @@ int __init at91_clock_init(unsigned long main_clock)
                        continue;
 
                pckr = at91_sys_read(AT91_PMC_PCKR(clk->id));
-               parent = clock_list[pckr & 3];
+               parent = clock_list[pckr & AT91_PMC_CSS];
                clk->parent = parent;
                clk->rate_hz = parent->rate_hz / (1 << ((pckr >> 2) & 3));
+
+               if (clk->users == 0) {
+                       /* not being used, so switch it off */
+                       at91_sys_write(AT91_PMC_SCDR, clk->pmc_mask);
+               }
        }
 #else
-       /* disable unused clocks */
+       /* disable all programmable clocks */
        at91_sys_write(AT91_PMC_SCDR, AT91_PMC_PCK0 | AT91_PMC_PCK1 | AT91_PMC_PCK2 | AT91_PMC_PCK3);
-#endif /* CONFIG_AT91_PROGRAMMABLE_CLOCKS */
+#endif
 
-       /* FIXME several unused clocks may still be active...  provide
-        * a CONFIG option to turn off all unused clocks at some point
-        * before driver init starts.
-        */
+       /* enable the PIO clocks */
+       clk_enable(&pioA_clk);
+       clk_enable(&pioB_clk);
+       clk_enable(&pioC_clk);
+       clk_enable(&pioD_clk);
+
+       /* disable all other unused peripheral clocks */
+       at91_periphclk_reset();
 
        return 0;
 }