80d19ff404
always use 'M. Warner Losh' for consistency. 'All Rights Reserved.' was prescribed by the Buenos Aires Copyright Convention of 1910, but has been mostly dead since the early 1990's and completely meaningless since 2000 when Nicaragua ratified the Berne convention. Some files not done due to ambiguity of various types.
720 lines
16 KiB
C
720 lines
16 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2006 M. Warner Losh.
|
|
* 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 "opt_platform.h"
|
|
|
|
#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/resource.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>
|
|
|
|
#ifdef FDT
|
|
#include <dev/ofw/ofw_bus.h>
|
|
#include <dev/ofw/ofw_bus_subr.h>
|
|
#endif
|
|
|
|
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;
|
|
} *pmc_softc;
|
|
|
|
static uint32_t pllb_init;
|
|
|
|
MALLOC_DECLARE(M_PMC_CLK);
|
|
MALLOC_DEFINE(M_PMC_CLK, "at91_pmc_clocks", "AT91 PMC Clock descriptors");
|
|
|
|
#define AT91_PMC_BASE 0xffffc00
|
|
|
|
static void at91_pmc_set_pllb_mode(struct at91_pmc_clock *, int);
|
|
static void at91_pmc_set_upll_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,
|
|
};
|
|
|
|
/* Used by USB on at91sam9g45 */
|
|
static struct at91_pmc_clock upll = {
|
|
.name = "upll", /* UTMI PLL, used for USB functions on 9G45 family */
|
|
.parent = &main_ck,
|
|
.refcnt = 0,
|
|
.id = 0,
|
|
.primary = 1,
|
|
.pll = 1,
|
|
.pmc_mask = (1 << 6),
|
|
.set_mode = &at91_pmc_set_upll_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,
|
|
&upll,
|
|
&udpck,
|
|
&uhpck,
|
|
&mck,
|
|
&cpu
|
|
};
|
|
|
|
static inline uint32_t
|
|
RD4(struct at91_pmc_softc *sc, bus_size_t off)
|
|
{
|
|
|
|
if (sc == NULL) {
|
|
uint32_t *p = (uint32_t *)(AT91_BASE + AT91_PMC_BASE + off);
|
|
|
|
return *p;
|
|
}
|
|
return (bus_read_4(sc->mem_res, off));
|
|
}
|
|
|
|
static inline void
|
|
WR4(struct at91_pmc_softc *sc, bus_size_t off, uint32_t val)
|
|
{
|
|
|
|
if (sc == NULL) {
|
|
uint32_t *p = (uint32_t *)(AT91_BASE + AT91_PMC_BASE + off);
|
|
|
|
*p = val;
|
|
} else
|
|
bus_write_4(sc->mem_res, off, val);
|
|
}
|
|
|
|
/*
|
|
* The following is unused currently since we don't ever set the PLLA
|
|
* frequency of the device. If we did, we'd have to also pay attention
|
|
* to the ICPLLA bit in the PMC_PLLICPR register for frequencies lower
|
|
* than ~600MHz, which the PMC code doesn't do right now.
|
|
*/
|
|
uint32_t
|
|
at91_pmc_800mhz_plla_outb(int freq)
|
|
{
|
|
uint32_t outa;
|
|
|
|
/*
|
|
* Set OUTA, per the data sheet. See Table 46-16 titled
|
|
* PLLA Frequency Regarding ICPLLA and OUTA in the SAM9X25 doc,
|
|
* Table 46-17 in the SAM9G20 doc, or Table 46-16 in the SAM9G45 doc.
|
|
* Note: the frequencies overlap by 5MHz, so we add 3 here to
|
|
* center shoot the transition.
|
|
*/
|
|
|
|
freq /= 1000000; /* MHz */
|
|
if (freq >= 800)
|
|
freq = 800;
|
|
freq += 3; /* Allow for overlap. */
|
|
outa = 3 - ((freq / 50) & 3); /* 750 / 50 = 7, see table */
|
|
return (1 << 29)| (outa << 14);
|
|
}
|
|
|
|
uint32_t
|
|
at91_pmc_800mhz_pllb_outb(int freq)
|
|
{
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
at91_pmc_set_pllb_mode(struct at91_pmc_clock *clk, int on)
|
|
{
|
|
struct at91_pmc_softc *sc = pmc_softc;
|
|
uint32_t value;
|
|
|
|
value = on ? pllb_init : 0;
|
|
|
|
/*
|
|
* Only write to the register if the value is changing. Besides being
|
|
* good common sense, this works around RM9200 Errata #26 (CKGR_PLL[AB]R
|
|
* must not be written with the same value currently in the register).
|
|
*/
|
|
if (RD4(sc, CKGR_PLLBR) != value) {
|
|
WR4(sc, CKGR_PLLBR, value);
|
|
while (on && (RD4(sc, PMC_SR) & PMC_IER_LOCKB) != PMC_IER_LOCKB)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
static void
|
|
at91_pmc_set_upll_mode(struct at91_pmc_clock *clk, int on)
|
|
{
|
|
struct at91_pmc_softc *sc = pmc_softc;
|
|
uint32_t value;
|
|
|
|
if (on) {
|
|
on = PMC_IER_LOCKU;
|
|
value = CKGR_UCKR_UPLLEN | CKGR_UCKR_BIASEN;
|
|
} else
|
|
value = 0;
|
|
|
|
WR4(sc, CKGR_UCKR, RD4(sc, CKGR_UCKR) | value);
|
|
while ((RD4(sc, PMC_SR) & PMC_IER_LOCKU) != on)
|
|
continue;
|
|
|
|
WR4(sc, PMC_USB, PMC_USB_USBDIV(9) | PMC_USB_USBS);
|
|
WR4(sc, PMC_SCER, PMC_SCER_UHP_SAM9);
|
|
}
|
|
|
|
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_CLK, M_NOWAIT | M_ZERO);
|
|
if (clk == NULL)
|
|
goto err;
|
|
|
|
buflen = strlen(name) + 1;
|
|
clk->name = malloc(buflen, M_PMC_CLK, 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 < nitems(clock_list); 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_CLK);
|
|
free(clk, M_PMC_CLK);
|
|
}
|
|
|
|
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 < nitems(clock_list); i++) {
|
|
if (clock_list[i] == NULL)
|
|
break;
|
|
if (strcmp(name, clock_list[i]->name) == 0)
|
|
return (clock_list[i]);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
void
|
|
at91_pmc_clock_deref(struct at91_pmc_clock *clk)
|
|
{
|
|
if (clk == NULL)
|
|
return;
|
|
}
|
|
|
|
void
|
|
at91_pmc_clock_enable(struct at91_pmc_clock *clk)
|
|
{
|
|
if (clk == NULL)
|
|
return;
|
|
|
|
/* 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)
|
|
{
|
|
if (clk == NULL)
|
|
return;
|
|
|
|
/* 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 nitems(at91_main_clock_tbl)
|
|
#endif
|
|
|
|
static unsigned int
|
|
at91_pmc_sense_main_clock(void)
|
|
{
|
|
#if !defined(AT91C_MAIN_CLOCK)
|
|
unsigned int ckgr_val;
|
|
unsigned int diff, matchdiff, freq;
|
|
int i;
|
|
|
|
ckgr_val = (RD4(NULL, 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_MAIN_CLOCK in the kernel config file.
|
|
*/
|
|
if (ckgr_val >= 21000000)
|
|
return (rounddown(ckgr_val + 250, 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);
|
|
#else
|
|
return (AT91C_MAIN_CLOCK);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
at91_pmc_init_clock(void)
|
|
{
|
|
struct at91_pmc_softc *sc = NULL;
|
|
unsigned int main_clock;
|
|
uint32_t mckr;
|
|
uint32_t mdiv;
|
|
|
|
soc_info.soc_data->soc_clock_init();
|
|
|
|
main_clock = at91_pmc_sense_main_clock();
|
|
|
|
if (at91_is_sam9() || at91_is_sam9xe()) {
|
|
uhpck.pmc_mask = PMC_SCER_UHP_SAM9;
|
|
udpck.pmc_mask = PMC_SCER_UDP_SAM9;
|
|
}
|
|
|
|
/* There is no pllb on AT91SAM9G45 */
|
|
if (at91_cpu_is(AT91_T_SAM9G45)) {
|
|
uhpck.parent = &upll;
|
|
uhpck.pmc_mask = PMC_SCER_UHP_SAM9;
|
|
}
|
|
|
|
mckr = RD4(sc, PMC_MCKR);
|
|
main_ck.hz = main_clock;
|
|
|
|
/*
|
|
* Note: this means outa calc code for plla never used since
|
|
* we never change it. If we did, we'd also have to mind
|
|
* ICPLLA to get the charge pump current right.
|
|
*/
|
|
at91_pmc_pll_rate(&plla, RD4(sc, CKGR_PLLAR));
|
|
|
|
if (at91_cpu_is(AT91_T_SAM9G45) && (mckr & PMC_MCKR_PLLADIV2))
|
|
plla.hz /= 2;
|
|
|
|
/*
|
|
* Initialize the usb clock. This sets up pllb, but disables the
|
|
* actual clock. XXX except for the if 0 :(
|
|
*/
|
|
if (!at91_cpu_is(AT91_T_SAM9G45)) {
|
|
pllb_init = at91_pmc_pll_calc(&pllb, 48000000 * 2) | 0x10000000;
|
|
at91_pmc_pll_rate(&pllb, 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);
|
|
|
|
/*
|
|
* 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()) {
|
|
/*
|
|
* On AT91SAM9G45 when mdiv == 3 we need to divide
|
|
* MCK by 3 but not, for example, on 9g20.
|
|
*/
|
|
if (!at91_cpu_is(AT91_T_SAM9G45) || mdiv <= 2)
|
|
mdiv *= 2;
|
|
if (mdiv > 0)
|
|
mck.hz /= mdiv;
|
|
} else
|
|
mck.hz /= (1 + mdiv);
|
|
|
|
/* Only found on SAM9G20 */
|
|
if (at91_cpu_is(AT91_T_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 = NULL;
|
|
}
|
|
|
|
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)
|
|
{
|
|
#ifdef FDT
|
|
if (!ofw_bus_is_compatible(dev, "atmel,at91rm9200-pmc") &&
|
|
!ofw_bus_is_compatible(dev, "atmel,at91sam9260-pmc") &&
|
|
!ofw_bus_is_compatible(dev, "atmel,at91sam9g45-pmc") &&
|
|
!ofw_bus_is_compatible(dev, "atmel,at91sam9x5-pmc"))
|
|
return (ENXIO);
|
|
#endif
|
|
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();
|
|
|
|
/*
|
|
* Display info about clocks previously computed
|
|
*/
|
|
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;
|
|
|
|
#ifdef FDT
|
|
EARLY_DRIVER_MODULE(at91_pmc, simplebus, at91_pmc_driver, at91_pmc_devclass,
|
|
NULL, NULL, BUS_PASS_CPU);
|
|
#else
|
|
EARLY_DRIVER_MODULE(at91_pmc, atmelarm, at91_pmc_driver, at91_pmc_devclass,
|
|
NULL, NULL, BUS_PASS_CPU);
|
|
#endif
|