freebsd-dev/sys/arm/at91/at91_pmc.c
Warner Losh e5db213663 Another minor re-arrangement of the code: calcualte the master clock
frequency in the at91_pmc_clock_init rather than passing it in.  Allow
for frequencies >= 21MHz by rounding to the nearest 500Hz (Idea from
Ian Lapore whose company uses a similar arrangement in their product).
at91_pmc_clock_init() is now nearly independent of the rest of the pmc
driver (which means we may be able to call it much earlier in boot
soon to eliminate the master clock config file requirement for printf
to work during early boot and also eliminate some interdependencies
with the device ordering which requires pmc to be the first device
added).
2012-05-21 04:24:58 +00:00

599 lines
14 KiB
C

/*-
* Copyright (c) 2006 M. Warner Losh. All rights reserved.
* Copyright (c) 2010 Greg Ansley. 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 AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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/malloc.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/at91reg.h>
#include <arm/at91/at91var.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;
uint32_t pllb_init;
} *pmc_softc;
MALLOC_DECLARE(M_PMC);
MALLOC_DEFINE(M_PMC, "at91_pmc_clocks", "AT91 PMC Clock descriptors");
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 void at91_pmc_clock_alias(const char *name, const char *alias);
static struct at91_pmc_clock slck = {
.name = "slck", // 32,768 Hz slow clock
.hz = 32768,
.refcnt = 1,
.id = 0,
.primary = 1,
};
/*
* NOTE: Clocks for "ordinary peripheral" devices e.g. spi0, udp0, uhp0 etc.
* are now created automatically. Only "system" clocks need be defined here.
*/
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", // Master (Peripheral) Clock
.pmc_mask = PMC_IER_MCKRDY,
.refcnt = 0,
};
static struct at91_pmc_clock cpu = {
.name = "cpu", // CPU Clock
.parent = &plla,
.pmc_mask = PMC_SCER_PCK,
.refcnt = 0,
};
/* "+32" or the automatic peripheral clocks */
static struct at91_pmc_clock *clock_list[16+32] = {
&slck,
&main_ck,
&plla,
&pllb,
&udpck,
&uhpck,
&mck,
&cpu
};
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);
}
void
at91_pmc_set_pllb_mode(struct at91_pmc_clock *clk, int on)
{
struct at91_pmc_softc *sc = pmc_softc;
uint32_t value;
if (on) {
on = PMC_IER_LOCKB;
value = sc->pllb_init;
} else
value = 0;
/* Workaround RM9200 Errata #26 */
if (at91_is_rm92() &&
((value ^ RD4(sc, CKGR_PLLBR)) & 0x03f0ff) != 0) {
WR4(sc, CKGR_PLLBR, value ^ 1);
while ((RD4(sc, PMC_SR) & PMC_IER_LOCKB) != on)
continue;
}
WR4(sc, CKGR_PLLBR, value);
while ((RD4(sc, PMC_SR) & PMC_IER_LOCKB) != on)
continue;
}
static void
at91_pmc_set_sys_mode(struct at91_pmc_clock *clk, int on)
{
struct at91_pmc_softc *sc = pmc_softc;
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;
}
static void
at91_pmc_set_periph_mode(struct at91_pmc_clock *clk, int on)
{
struct at91_pmc_softc *sc = pmc_softc;
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;
}
struct at91_pmc_clock *
at91_pmc_clock_add(const char *name, uint32_t irq,
struct at91_pmc_clock *parent)
{
struct at91_pmc_clock *clk;
int i, buflen;
clk = malloc(sizeof(*clk), M_PMC, M_NOWAIT | M_ZERO);
if (clk == NULL)
goto err;
buflen = strlen(name) + 1;
clk->name = malloc(buflen, M_PMC, M_NOWAIT);
if (clk->name == NULL)
goto err;
strlcpy(clk->name, name, buflen);
clk->pmc_mask = 1 << irq;
clk->set_mode = &at91_pmc_set_periph_mode;
if (parent == NULL)
clk->parent = &mck;
else
clk->parent = parent;
for (i = 0; i < sizeof(clock_list) / sizeof(clock_list[0]); i++) {
if (clock_list[i] == NULL) {
clock_list[i] = clk;
return (clk);
}
}
err:
if (clk != NULL) {
if (clk->name != NULL)
free(clk->name, M_PMC);
free(clk, M_PMC);
}
panic("could not allocate pmc clock '%s'", name);
return (NULL);
}
static void
at91_pmc_clock_alias(const char *name, const char *alias)
{
struct at91_pmc_clock *clk, *alias_clk;
clk = at91_pmc_clock_ref(name);
if (clk)
alias_clk = at91_pmc_clock_add(alias, 0, clk->parent);
if (clk && alias_clk) {
alias_clk->hz = clk->hz;
alias_clk->pmc_mask = clk->pmc_mask;
alias_clk->set_mode = clk->set_mode;
}
}
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 (clock_list[i] == NULL)
break;
if (strcmp(name, clock_list[i]->name) == 0)
return (clock_list[i]);
}
//printf("at91_pmc: Warning - did not find clock '%s'", name);
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 */
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(struct at91_pmc_clock *clk, uint32_t reg)
{
uint32_t mul, div, freq;
freq = clk->parent->hz;
div = (reg >> clk->pll_div_shift) & clk->pll_div_mask;
mul = (reg >> clk->pll_mul_shift) & clk->pll_mul_mask;
#if 0
printf("pll = (%d / %d) * %d = %d\n",
freq, div, mul + 1, (freq/div) * (mul+1));
#endif
if (div != 0 && mul != 0) {
freq /= div;
freq *= mul + 1;
} else
freq = 0;
clk->hz = freq;
return (freq);
}
static uint32_t
at91_pmc_pll_calc(struct at91_pmc_clock *clk, uint32_t out_freq)
{
uint32_t i, div = 0, mul = 0, diff = 1 << 30;
unsigned ret = 0x3e00;
if (out_freq > clk->pll_max_out)
goto fail;
for (i = 1; i < 256; i++) {
int32_t diff1;
uint32_t input, mul1;
input = clk->parent->hz / i;
if (input < clk->pll_min_in)
break;
if (input > clk->pll_max_in)
continue;
mul1 = out_freq / input;
if (mul1 > (clk->pll_mul_mask + 1))
continue;
if (mul1 == 0)
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;
if (clk->set_outb != NULL)
ret |= clk->set_outb(out_freq);
return (ret |
((mul - 1) << clk->pll_mul_shift) |
(div << clk->pll_div_shift));
fail:
return (0);
}
#if !defined(AT91C_MAIN_CLOCK)
static const unsigned int at91_main_clock_tbl[] = {
3000000, 3276800, 3686400, 3840000, 4000000,
4433619, 4915200, 5000000, 5242880, 6000000,
6144000, 6400000, 6553600, 7159090, 7372800,
7864320, 8000000, 9830400, 10000000, 11059200,
12000000, 12288000, 13560000, 14318180, 14745600,
16000000, 17344700, 18432000, 20000000
};
#define MAIN_CLOCK_TBL_LEN (sizeof(at91_main_clock_tbl) / sizeof(*at91_main_clock_tbl))
static unsigned int
at91_pmc_sense_main_clock(struct at91_pmc_softc *sc)
{
unsigned int ckgr_val;
unsigned int diff, matchdiff, freq;
int i;
ckgr_val = (RD4(sc, CKGR_MCFR) & CKGR_MCFR_MAINF_MASK) << 11;
/*
* Clocks up to 50MHz can be connected to some models. If
* the frequency is >= 21MHz, assume that the slow clock can
* measure it correctly, and that any error can be adequately
* compensated for by roudning to the nearest 500Hz. Users
* with fast, or odd-ball clocks will need to set
* AT91C_MASTER_CLOCK in the kernel config file.
*/
if (ckgr_val >= 21000000)
return ((ckgr_val + 250) / 500 * 500);
/*
* Try to find the standard frequency that match best.
*/
freq = at91_main_clock_tbl[0];
matchdiff = abs(ckgr_val - at91_main_clock_tbl[0]);
for (i = 1; i < MAIN_CLOCK_TBL_LEN; i++) {
diff = abs(ckgr_val - at91_main_clock_tbl[i]);
if (diff < matchdiff) {
freq = at91_main_clock_tbl[i];
matchdiff = diff;
}
}
return (freq);
}
#endif
static void
at91_pmc_init_clock(struct at91_pmc_softc *sc)
{
unsigned int main_clock;
uint32_t mckr;
uint32_t mdiv;
#if !defined(AT91C_MAIN_CLOCK)
main_clock = at91_pmc_sense_main_clock(pmc_softc);
#else
main_clock = AT91C_MAIN_CLOCK;
#endif
if (at91_is_sam9() || at91_is_sam9xe()) {
uhpck.pmc_mask = PMC_SCER_UHP_SAM9;
udpck.pmc_mask = PMC_SCER_UDP_SAM9;
}
mckr = RD4(sc, PMC_MCKR);
main_ck.hz = main_clock;
at91_pmc_pll_rate(&plla, RD4(sc, CKGR_PLLAR));
if (at91_cpu_is(AT91_CPU_SAM9G45) && (mckr & PMC_MCKR_PLLADIV2))
plla.hz /= 2;
/*
* Initialize the usb clock. This sets up pllb, but disables the
* actual clock.
*/
sc->pllb_init = at91_pmc_pll_calc(&pllb, 48000000 * 2) | 0x10000000;
at91_pmc_pll_rate(&pllb, sc->pllb_init);
#if 0
/* Turn off USB clocks */
at91_pmc_set_periph_mode(&ohci_clk, 0);
at91_pmc_set_periph_mode(&udc_clk, 0);
#endif
if (at91_is_rm92()) {
WR4(sc, PMC_SCDR, PMC_SCER_UHP | PMC_SCER_UDP);
WR4(sc, PMC_SCER, PMC_SCER_MCKUDP);
} else
WR4(sc, PMC_SCDR, PMC_SCER_UHP_SAM9 | PMC_SCER_UDP_SAM9);
WR4(sc, CKGR_PLLBR, 0);
/*
* MCK and PCU derive from one of the primary clocks. Initialize
* this relationship.
*/
mck.parent = clock_list[mckr & 0x3];
mck.parent->refcnt++;
cpu.hz = mck.hz = mck.parent->hz /
(1 << ((mckr & PMC_MCKR_PRES_MASK) >> 2));
mdiv = (mckr & PMC_MCKR_MDIV_MASK) >> 8;
if (at91_is_sam9() || at91_is_sam9xe()) {
if (mdiv > 0)
mck.hz /= mdiv * 2;
} else
mck.hz /= (1 + mdiv);
/* Only found on SAM9G20 */
if (at91_cpu_is(AT91_CPU_SAM9G20))
cpu.hz /= (mckr & PMC_MCKR_PDIV) ? 2 : 1;
at91_master_clock = mck.hz;
/* These clocks refrenced by "special" names */
at91_pmc_clock_alias("ohci0", "ohci_clk");
at91_pmc_clock_alias("udp0", "udp_clk");
/* Turn off "Progamable" clocks */
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;
}
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);
/*
* Configure main clock frequency.
*/
at91_pmc_init_clock(pmc_softc);
device_printf(dev,
"Primary: %d Hz PLLA: %d MHz CPU: %d MHz MCK: %d MHz\n",
main_ck.hz,
plla.hz / 1000000,
cpu.hz / 1000000, mck.hz / 1000000);
return (0);
}
static device_method_t at91_pmc_methods[] = {
DEVMETHOD(device_probe, at91_pmc_probe),
DEVMETHOD(device_attach, at91_pmc_attach),
DEVMETHOD_END
};
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, NULL,
NULL);