/*- * 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 __FBSDID("$FreeBSD$"); #include #include #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; unsigned int main_clock_hz; 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 }; #if !defined(AT91C_MAIN_CLOCK) static const unsigned int at91_mainf_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 MAINF_TBL_LEN (sizeof(at91_mainf_tbl) / sizeof(*at91_mainf_tbl)) #endif 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); } static void at91_pmc_init_clock(struct at91_pmc_softc *sc, unsigned int main_clock) { uint32_t mckr; uint32_t mdiv; 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); sc->main_clock_hz = main_clock; 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; device_printf(sc->dev, "Primary: %d Hz PLLA: %d MHz CPU: %d MHz MCK: %d MHz\n", sc->main_clock_hz, plla.hz / 1000000, cpu.hz / 1000000, mck.hz / 1000000); /* 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); } #if !defined(AT91C_MAIN_CLOCK) static unsigned int at91_pmc_sense_mainf(struct at91_pmc_softc *sc) { unsigned int ckgr_val; unsigned int diff, matchdiff; int i, match; ckgr_val = (RD4(sc, CKGR_MCFR) & CKGR_MCFR_MAINF_MASK) << 11; /* * Try to find the standard frequency that match best. */ match = 0; matchdiff = abs(ckgr_val - at91_mainf_tbl[0]); for (i = 1; i < MAINF_TBL_LEN; i++) { diff = abs(ckgr_val - at91_mainf_tbl[i]); if (diff < matchdiff) { match = i; matchdiff = diff; } } return (at91_mainf_tbl[match]); } #endif static int at91_pmc_attach(device_t dev) { unsigned int mainf; 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. */ #if !defined(AT91C_MAIN_CLOCK) mainf = at91_pmc_sense_mainf(pmc_softc); #else mainf = AT91C_MAIN_CLOCK; #endif at91_pmc_init_clock(pmc_softc, mainf); /* These clocks refrenced by "special" names */ at91_pmc_clock_alias("ohci0", "ohci_clk"); at91_pmc_clock_alias("udp0", "udp_clk"); 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);