freebsd-nq/sys/arm/at91/at91_pmc.c
Pedro F. Giffuni af3dc4a7ca sys/arm: further adoption of SPDX licensing ID tags.
Mainly focus on files that use BSD 2-Clause license, however the tool I
was using misidentified many licenses so this was mostly a manual - error
prone - task.

The Software Package Data Exchange (SPDX) group provides a specification
to make it easier for automated tools to detect and summarize well known
opensource licenses. We are gradually adopting the specification, noting
that the tags are considered only advisory and do not, in any way,
superceed or replace the license texts.
2017-11-27 15:04:10 +00:00

720 lines
16 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* 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 "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