From 378a37c96c93d2099cbdbdb4b8e09f2e3b19f1cf Mon Sep 17 00:00:00 2001 From: imp Date: Fri, 24 Mar 2006 07:37:56 +0000 Subject: Add the sekelton of support for the Power Management Controller. --- sys/arm/at91/at91_pmc.c | 419 +++++++++++++++++++++++++++++++++++++++++++++ sys/arm/at91/at91_pmcreg.h | 120 +++++++++++++ sys/arm/at91/at91_pmcvar.h | 49 ++++++ 3 files changed, 588 insertions(+) create mode 100644 sys/arm/at91/at91_pmc.c create mode 100644 sys/arm/at91/at91_pmcreg.h create mode 100644 sys/arm/at91/at91_pmcvar.h (limited to 'sys/arm/at91') 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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); diff --git a/sys/arm/at91/at91_pmcreg.h b/sys/arm/at91/at91_pmcreg.h new file mode 100644 index 0000000..39e7e0c --- /dev/null +++ b/sys/arm/at91/at91_pmcreg.h @@ -0,0 +1,120 @@ +/*- + * Copyright (c) 2005 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. + */ + +/* $FreeBSD$ */ + +#ifndef ARM_AT91_AT91_PMCREG_H +#define ARM_AT91_AT91_PMCREG_H + +/* Registers */ +#define PMC_SCER 0x00 /* System Clock Enable Register */ +#define PMC_SCDR 0x04 /* System Clock Disable Register */ +#define PMC_SCSR 0x08 /* System Clock Status Register */ + /* 0x0c reserved */ +#define PMC_PCER 0x10 /* Peripheral Clock Enable Register */ +#define PMC_PCDR 0x14 /* Peripheral Clock Disable Register */ +#define PMC_PCSR 0x18 /* Peripheral Clock Status Register */ + /* 0x1c reserved */ +#define CKGR_MOR 0x20 /* Main Oscillator Register */ +#define CKGR_MCFR 0x24 /* Main Clock Frequency Register */ +#define CKGR_PLLAR 0x28 /* PLL A Register */ +#define CKGR_PLLBR 0x2c /* PLL B Register */ +#define PMC_MCKR 0x30 /* Master Clock Register */ + /* 0x34 reserved */ + /* 0x38 reserved */ + /* 0x3c reserved */ +#define PMC_PCK0 0x40 /* Programmable Clock 0 Register */ +#define PMC_PCK1 0x44 /* Programmable Clock 1 Register */ +#define PMC_PCK2 0x48 /* Programmable Clock 2 Register */ +#define PMC_PCK3 0x4c /* Programmable Clock 3 Register */ + /* 0x50 reserved */ + /* 0x54 reserved */ + /* 0x58 reserved */ + /* 0x5c reserved */ +#define PMC_IER 0x60 /* Interrupt Enable Register */ +#define PMC_IDR 0x64 /* Interrupt Disable Register */ +#define PMC_SR 0x68 /* Status Register */ +#define PMC_IMR 0x6c /* Interrupt Mask Register */ + +/* PMC System Clock Enable Register */ +/* PMC System Clock Disable Register */ +/* PMC System Clock StatusRegister */ +#define PMC_SCER_PCK (1UL << 0) /* PCK: Processor Clock Enable */ +#define PMC_SCER_UDP (1UL << 1) /* UDP: USB Device Port Clock Enable */ +#define PMC_SCER_MCKUDP (1UL << 2) /* MCKUDP: Master disable susp/res */ +#define PMC_SCER_UHP (1UL << 4) /* UHP: USB Host Port Clock Enable */ +#define PMC_SCER_PCK0 (1UL << 8) /* PCK0: Programmable Clock out en */ +#define PMC_SCER_PCK1 (1UL << 10) /* PCK1: Programmable Clock out en */ +#define PMC_SCER_PCK2 (1UL << 11) /* PCK2: Programmable Clock out en */ +#define PMC_SCER_PCK3 (1UL << 12) /* PCK3: Programmable Clock out en */ + +/* PMC Peripheral Clock Enable Register */ +/* PMC Peripheral Clock Disable Register */ +/* PMC Peripheral Clock Status Register */ +/* Each bit here is 1 << peripheral number to enable/disable/status */ + +/* PMC Clock Generator Main Oscillator Register */ +#define CKGR_MOR_MOSCEN (1UL << 0) /* MOSCEN: Main Oscillator Enable */ +#define CKGR_MOR_OSCBYPASS (1UL << 1) /* Oscillator Bypass */ +#define CKGR_MOR_OSCOUNT(x) (x << 8) /* Main Oscillator Start-up Time */ + +/* PMC Clock Generator Main Clock Frequency Register */ +#define CKGR_MCFR_MAINRDY (1UL << 16) /* Main Clock Ready */ +#define CKGR_MCFR_MAINF_MASK 0xfffful /* Main Clock Frequency */ + +/* PMC Interrupt Enable Register */ +/* PMC Interrupt Disable Register */ +/* PMC Status Register */ +/* PMC Interrupt Mask Register */ +#define PMC_IER_MOSCS (1UL << 0) /* Main Oscillator Status */ +#define PMC_IER_LOCKA (1UL << 1) /* PLL A Locked */ +#define PMC_IER_LOCKB (1UL << 2) /* PLL B Locked */ +#define PMC_IER_MCKRDY (1UL << 3) /* Master Clock Status */ +#define PMC_IER_PCK0RDY (1UL << 8) /* Programmable Clock 0 Ready */ +#define PMC_IER_PCK1RDY (1UL << 9) /* Programmable Clock 1 Ready */ +#define PMC_IER_PCK2RDY (1UL << 10) /* Programmable Clock 2 Ready */ +#define PMC_IER_PCK3RDY (1UL << 11) /* Programmable Clock 3 Ready */ + +/* + * PLL input frequency spec sheet says it must be between 1MHz and 32MHz, + * but it works down as low as 100kHz, a frequency necessary for some + * output frequencies to work. + */ +#define PMC_PLL_MIN_IN_FREQ 100000 +#define PMC_PLL_MAX_IN_FREQ 32000000 + +/* + * PLL Max output frequency is 240MHz. The errata says 180MHz is the max + * for some revisions of this part. Be more permissive and optimistic. + */ +#define PMC_PLL_MAX_OUT_FREQ 240000000 + +#define PMC_PLL_MULT_MIN 2 +#define PMC_PLL_MULT_MAX 2048 + +#define PMC_PLL_SHIFT_TOL 5 /* Allow errors 1 part in 32 */ + +#define PMC_PLL_FAST_THRESH 155000000 + +#endif /* ARM_AT91_AT91_PMCREG_H */ diff --git a/sys/arm/at91/at91_pmcvar.h b/sys/arm/at91/at91_pmcvar.h new file mode 100644 index 0000000..ba85cda --- /dev/null +++ b/sys/arm/at91/at91_pmcvar.h @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2005 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. + */ + +/* $FreeBSD$ */ + +#ifndef ARM_AT91_AT91_PMCVAR_H +#define ARM_AT91_AT91_PMCVAR_H + +struct at91_pmc_clock +{ + const char *name; + uint32_t hz; + struct at91_pmc_clock *parent; + uint32_t pmc_mask; + void (*set_mode)(struct at91_pmc_clock *, int); + uint32_t refcnt; + unsigned id:2; + unsigned primary:1; + unsigned pll:1; + unsigned programmable:1; +}; + +struct at91_pmc_clock *at91_pmc_clock_ref(const char *name); +void at91_pmc_clock_deref(struct at91_pmc_clock *); +void at91_pmc_clock_enable(struct at91_pmc_clock *); +void at91_pmc_clock_disable(struct at91_pmc_clock *); + +#endif /* ARM_AT91_AT91_PMCVAR_H */ -- cgit v1.1