summaryrefslogtreecommitdiffstats
path: root/sys/arm/at91/at91_pmc.c
diff options
context:
space:
mode:
authorimp <imp@FreeBSD.org>2006-03-24 07:37:56 +0000
committerimp <imp@FreeBSD.org>2006-03-24 07:37:56 +0000
commit378a37c96c93d2099cbdbdb4b8e09f2e3b19f1cf (patch)
treed45a0196100aa270a981281e420f4a0602309745 /sys/arm/at91/at91_pmc.c
parent0fcb126eba78d9594867bfd99ea75ca9725505b1 (diff)
downloadFreeBSD-src-378a37c96c93d2099cbdbdb4b8e09f2e3b19f1cf.zip
FreeBSD-src-378a37c96c93d2099cbdbdb4b8e09f2e3b19f1cf.tar.gz
Add the sekelton of support for the Power Management Controller.
Diffstat (limited to 'sys/arm/at91/at91_pmc.c')
-rw-r--r--sys/arm/at91/at91_pmc.c419
1 files changed, 419 insertions, 0 deletions
diff --git a/sys/arm/at91/at91_pmc.c b/sys/arm/at91/at91_pmc.c
new file mode 100644
index 0000000..987f531
--- /dev/null
+++ b/sys/arm/at91/at91_pmc.c
@@ -0,0 +1,419 @@
+/*-
+ * Copyright (c) 2006 M. Warner Losh. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/time.h>
+#include <sys/bus.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+#include <sys/timetc.h>
+
+#include <machine/bus.h>
+#include <machine/cpu.h>
+#include <machine/cpufunc.h>
+#include <machine/resource.h>
+#include <machine/frame.h>
+#include <machine/intr.h>
+#include <arm/at91/at91rm92reg.h>
+
+#include <arm/at91/at91_pmcreg.h>
+#include <arm/at91/at91_pmcvar.h>
+
+static struct at91_pmc_softc {
+ bus_space_tag_t sc_st;
+ bus_space_handle_t sc_sh;
+ struct resource *mem_res; /* Memory resource */
+ device_t dev;
+ int main_clock_hz;
+ uint32_t pllb_init;
+} *pmc_softc;
+
+static void at91_pmc_set_pllb_mode(struct at91_pmc_clock *, int);
+static void at91_pmc_set_sys_mode(struct at91_pmc_clock *, int);
+static void at91_pmc_set_periph_mode(struct at91_pmc_clock *, int);
+
+static struct at91_pmc_clock slck = {
+ .name = "slck", // 32,768 Hz slow clock
+ .hz = 32768,
+ .refcnt = 1,
+ .id = 0,
+ .primary = 1,
+};
+
+static struct at91_pmc_clock main_ck = {
+ .name = "main", // Main clock
+ .refcnt = 0,
+ .id = 1,
+ .primary = 1,
+ .pmc_mask = PMC_IER_MOSCS,
+};
+
+static struct at91_pmc_clock plla = {
+ .name = "plla", // PLLA Clock, used for CPU clocking
+ .parent = &main_ck,
+ .refcnt = 1,
+ .id = 0,
+ .primary = 1,
+ .pll = 1,
+ .pmc_mask = PMC_IER_LOCKA,
+};
+
+static struct at91_pmc_clock pllb = {
+ .name = "pllb", // PLLB Clock, used for USB functions
+ .parent = &main_ck,
+ .refcnt = 0,
+ .id = 0,
+ .primary = 1,
+ .pll = 1,
+ .pmc_mask = PMC_IER_LOCKB,
+ .set_mode = &at91_pmc_set_pllb_mode,
+};
+
+static struct at91_pmc_clock udpck = {
+ .name = "udpck",
+ .parent = &pllb,
+ .pmc_mask = PMC_SCER_UDP,
+ .set_mode = at91_pmc_set_sys_mode
+};
+
+static struct at91_pmc_clock uhpck = {
+ .name = "uhpck",
+ .parent = &pllb,
+ .pmc_mask = PMC_SCER_UHP,
+ .set_mode = at91_pmc_set_sys_mode
+};
+
+static struct at91_pmc_clock mck = {
+ .name = "mck",
+ .pmc_mask = PMC_IER_MCKRDY,
+ .refcnt = 0,
+};
+
+static struct at91_pmc_clock udc_clk = {
+ .name = "udc_clk",
+ .parent = &mck,
+ .pmc_mask = 1 << AT91RM92_IRQ_UDP,
+ .set_mode = &at91_pmc_set_periph_mode
+};
+
+static struct at91_pmc_clock ohci_clk = {
+ .name = "ohci_clk",
+ .parent = &mck,
+ .pmc_mask = 1 << AT91RM92_IRQ_UDP,
+ .set_mode = &at91_pmc_set_periph_mode
+};
+
+static struct at91_pmc_clock *const clock_list[] = {
+ &slck,
+ &main_ck,
+ &plla,
+ &pllb,
+ &udpck,
+ &uhpck,
+ &mck,
+ &udc_clk,
+ &ohci_clk
+};
+
+static inline uint32_t
+RD4(struct at91_pmc_softc *sc, bus_size_t off)
+{
+ return bus_read_4(sc->mem_res, off);
+}
+
+static inline void
+WR4(struct at91_pmc_softc *sc, bus_size_t off, uint32_t val)
+{
+ bus_write_4(sc->mem_res, off, val);
+}
+
+static void
+at91_pmc_set_pllb_mode(struct at91_pmc_clock *clk, int on)
+{
+ struct at91_pmc_softc *sc = pmc_softc;
+ uint32_t value;
+
+ printf("Turning PLLB %#x %s\n", sc->pllb_init, on ? "on" : "off");
+ if (on) {
+ on = PMC_IER_LOCKB;
+ value = sc->pllb_init;
+ } else {
+ value = 0;
+ }
+ WR4(sc, CKGR_PLLBR, value);
+ while ((RD4(sc, PMC_SR) & PMC_IER_LOCKB) != on)
+ continue;
+ printf("Done!\n");
+}
+
+static void
+at91_pmc_set_sys_mode(struct at91_pmc_clock *clk, int on)
+{
+ struct at91_pmc_softc *sc = pmc_softc;
+
+ printf("Turning SC %#x %s\n", clk->pmc_mask, on ? "on" : "off");
+ WR4(sc, on ? PMC_SCER : PMC_SCDR, clk->pmc_mask);
+ if (on)
+ while ((RD4(sc, PMC_SCSR) & clk->pmc_mask) != clk->pmc_mask)
+ continue;
+ else
+ while ((RD4(sc, PMC_SCSR) & clk->pmc_mask) == clk->pmc_mask)
+ continue;
+ printf("Done SCSR is now: %#x!\n", RD4(sc, PMC_SCSR));
+}
+
+static void
+at91_pmc_set_periph_mode(struct at91_pmc_clock *clk, int on)
+{
+ struct at91_pmc_softc *sc = pmc_softc;
+
+ printf("Turning PC %#x %s\n", clk->pmc_mask, on ? "on" : "off");
+ WR4(sc, on ? PMC_PCER : PMC_PCDR, clk->pmc_mask);
+ if (on)
+ while ((RD4(sc, PMC_PCSR) & clk->pmc_mask) != clk->pmc_mask)
+ continue;
+ else
+ while ((RD4(sc, PMC_PCSR) & clk->pmc_mask) == clk->pmc_mask)
+ continue;
+ printf("Done PCSR is now: %#x!\n", RD4(sc, PMC_PCSR));
+}
+
+struct at91_pmc_clock *
+at91_pmc_clock_ref(const char *name)
+{
+ int i;
+
+ for (i = 0; i < sizeof(clock_list) / sizeof(clock_list[0]); i++)
+ if (strcmp(name, clock_list[i]->name) == 0)
+ return (clock_list[i]);
+
+ return (NULL);
+}
+
+void
+at91_pmc_clock_deref(struct at91_pmc_clock *clk)
+{
+}
+
+void
+at91_pmc_clock_enable(struct at91_pmc_clock *clk)
+{
+ /* XXX LOCKING? XXX */
+ printf("Enable %s\n", clk->name);
+ if (clk->parent)
+ at91_pmc_clock_enable(clk->parent);
+ if (clk->refcnt++ == 0 && clk->set_mode)
+ clk->set_mode(clk, 1);
+}
+
+void
+at91_pmc_clock_disable(struct at91_pmc_clock *clk)
+{
+ /* XXX LOCKING? XXX */
+ if (--clk->refcnt == 0 && clk->set_mode)
+ clk->set_mode(clk, 0);
+ if (clk->parent)
+ at91_pmc_clock_disable(clk->parent);
+}
+
+static int
+at91_pmc_pll_rate(int freq, uint32_t reg, int is_pllb)
+{
+ uint32_t mul, div;
+
+ div = reg & 0xff;
+ mul = (reg >> 16) & 0x7ff;
+ if (div != 0 && mul != 0) {
+ freq /= div;
+ freq *= mul + 1;
+ } else {
+ freq = 0;
+ }
+ if (is_pllb && (reg & (1 << 28)))
+ freq >>= 1;
+ return (freq);
+}
+
+static uint32_t
+at91_pmc_pll_calc(uint32_t main_freq, uint32_t out_freq)
+{
+ uint32_t i, div = 0, mul = 0, diff = 1 << 30;
+ unsigned ret = (out_freq > PMC_PLL_FAST_THRESH) ? 0xbe00 : 0x3e00;
+
+ if (out_freq > PMC_PLL_MAX_OUT_FREQ)
+ goto fail;
+
+ for (i = 1; i < 256; i++) {
+ int32_t diff1;
+ uint32_t input, mul1;
+
+ input = main_freq / i;
+ if (input < PMC_PLL_MIN_IN_FREQ)
+ break;
+ if (input > PMC_PLL_MAX_IN_FREQ)
+ continue;
+
+ mul1 = out_freq / input;
+ if (mul1 > PMC_PLL_MULT_MAX)
+ continue;
+ if (mul1 < PMC_PLL_MULT_MIN)
+ break;
+
+ diff1 = out_freq - input * mul1;
+ if (diff1 < 0)
+ diff1 = -diff1;
+ if (diff > diff1) {
+ diff = diff1;
+ div = i;
+ mul = mul1;
+ if (diff == 0)
+ break;
+ }
+ }
+ if (diff > (out_freq >> PMC_PLL_SHIFT_TOL))
+ goto fail;
+ return ret | ((mul - 1) << 16) | div;
+fail:
+ return 0;
+}
+
+static void
+at91_pmc_init_clock(struct at91_pmc_softc *sc, int main_clock)
+{
+ uint32_t mckr;
+ int freq;
+
+ sc->main_clock_hz = main_clock;
+ main_ck.hz = main_clock;
+ plla.hz = at91_pmc_pll_rate(main_clock, RD4(sc, CKGR_PLLAR), 0);
+
+ /*
+ * Initialize the usb clock. This sets up pllb, but disables the
+ * actual clock.
+ */
+ sc->pllb_init = at91_pmc_pll_calc(main_clock, 48000000 * 2) |0x10000000;
+ pllb.hz = at91_pmc_pll_rate(main_clock, sc->pllb_init, 1);
+ WR4(sc, PMC_PCDR, (1 << AT91RM92_IRQ_UHP) | (1 << AT91RM92_IRQ_UDP));
+ WR4(sc, PMC_SCDR, PMC_SCER_UHP | PMC_SCER_UDP);
+ WR4(sc, CKGR_PLLBR, 0);
+ WR4(sc, PMC_SCER, PMC_SCER_MCKUDP);
+
+ /*
+ * MCK and PCU derive from one of the primary clocks. Initialize
+ * this relationship.
+ */
+ mckr = RD4(sc, PMC_MCKR);
+ mck.parent = clock_list[mckr & 0x3];
+ mck.parent->refcnt++;
+ freq = mck.parent->hz;
+ freq /= 1 << ((mckr >> 2) & 3);
+ mck.hz = freq / (1 + ((mckr >> 8) & 3));
+
+ device_printf(sc->dev,
+ "main clock: %d Hz PLLA: %d MHz CPU: %d MHz main %d MHz\n",
+ sc->main_clock_hz,
+ at91_pmc_pll_rate(main_clock, RD4(sc, CKGR_PLLAR), 0) / 1000000,
+ freq / 1000000, mck.hz / 1000000);
+ WR4(sc, PMC_SCDR, PMC_SCER_PCK0 | PMC_SCER_PCK1 | PMC_SCER_PCK2 |
+ PMC_SCER_PCK3);
+ /* XXX kludge, turn on all peripherals */
+ WR4(sc, PMC_PCER, 0xffffffff);
+ /* Disable all interrupts for PMC */
+ WR4(sc, PMC_IDR, 0xffffffff);
+}
+
+static void
+at91_pmc_deactivate(device_t dev)
+{
+ struct at91_pmc_softc *sc;
+
+ sc = device_get_softc(dev);
+ bus_generic_detach(sc->dev);
+ if (sc->mem_res)
+ bus_release_resource(dev, SYS_RES_IOPORT,
+ rman_get_rid(sc->mem_res), sc->mem_res);
+ sc->mem_res = 0;
+ return;
+}
+
+static int
+at91_pmc_activate(device_t dev)
+{
+ struct at91_pmc_softc *sc;
+ int rid;
+
+ sc = device_get_softc(dev);
+ rid = 0;
+ sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->mem_res == NULL)
+ goto errout;
+ return (0);
+errout:
+ at91_pmc_deactivate(dev);
+ return (ENOMEM);
+}
+
+static int
+at91_pmc_probe(device_t dev)
+{
+
+ device_set_desc(dev, "PMC");
+ return (0);
+}
+
+static int
+at91_pmc_attach(device_t dev)
+{
+ int err;
+
+ pmc_softc = device_get_softc(dev);
+ pmc_softc->dev = dev;
+ if ((err = at91_pmc_activate(dev)) != 0)
+ return err;
+ at91_pmc_init_clock(pmc_softc, 10000000);
+
+ return (0);
+}
+
+static device_method_t at91_pmc_methods[] = {
+ DEVMETHOD(device_probe, at91_pmc_probe),
+ DEVMETHOD(device_attach, at91_pmc_attach),
+ {0, 0},
+};
+
+static driver_t at91_pmc_driver = {
+ "at91_pmc",
+ at91_pmc_methods,
+ sizeof(struct at91_pmc_softc),
+};
+static devclass_t at91_pmc_devclass;
+
+DRIVER_MODULE(at91_pmc, atmelarm, at91_pmc_driver, at91_pmc_devclass, 0, 0);
OpenPOWER on IntegriCloud