// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2015 Purna Chandra Mandal <purna.mandal@microchip.com> * */ #include <common.h> #include <clk-uclass.h> #include <dm.h> #include <div64.h> #include <wait_bit.h> #include <dm/lists.h> #include <asm/io.h> #include <mach/pic32.h> #include <dt-bindings/clock/microchip,clock.h> DECLARE_GLOBAL_DATA_PTR; /* Primary oscillator */ #define SYS_POSC_CLK_HZ 24000000 /* FRC clk rate */ #define SYS_FRC_CLK_HZ 8000000 /* Clock Registers */ #define OSCCON 0x0000 #define OSCTUNE 0x0010 #define SPLLCON 0x0020 #define REFO1CON 0x0080 #define REFO1TRIM 0x0090 #define PB1DIV 0x0140 /* SPLL */ #define ICLK_MASK 0x00000080 #define PLLIDIV_MASK 0x00000007 #define PLLODIV_MASK 0x00000007 #define CUROSC_MASK 0x00000007 #define PLLMUL_MASK 0x0000007F #define FRCDIV_MASK 0x00000007 /* PBCLK */ #define PBDIV_MASK 0x00000007 /* SYSCLK MUX */ #define SCLK_SRC_FRC1 0 #define SCLK_SRC_SPLL 1 #define SCLK_SRC_POSC 2 #define SCLK_SRC_FRC2 7 /* Reference Oscillator Control Reg fields */ #define REFO_SEL_MASK 0x0f #define REFO_SEL_SHIFT 0 #define REFO_ACTIVE BIT(8) #define REFO_DIVSW_EN BIT(9) #define REFO_OE BIT(12) #define REFO_ON BIT(15) #define REFO_DIV_SHIFT 16 #define REFO_DIV_MASK 0x7fff /* Reference Oscillator Trim Register Fields */ #define REFO_TRIM_REG 0x10 #define REFO_TRIM_MASK 0x1ff #define REFO_TRIM_SHIFT 23 #define REFO_TRIM_MAX 511 #define ROCLK_SRC_SCLK 0x0 #define ROCLK_SRC_SPLL 0x7 #define ROCLK_SRC_ROCLKI 0x8 /* Memory PLL */ #define MPLL_IDIV 0x3f #define MPLL_MULT 0xff #define MPLL_ODIV1 0x7 #define MPLL_ODIV2 0x7 #define MPLL_VREG_RDY BIT(23) #define MPLL_RDY BIT(31) #define MPLL_IDIV_SHIFT 0 #define MPLL_MULT_SHIFT 8 #define MPLL_ODIV1_SHIFT 24 #define MPLL_ODIV2_SHIFT 27 #define MPLL_IDIV_INIT 0x03 #define MPLL_MULT_INIT 0x32 #define MPLL_ODIV1_INIT 0x02 #define MPLL_ODIV2_INIT 0x01 struct pic32_clk_priv { void __iomem *iobase; void __iomem *syscfg_base; }; static ulong pic32_get_pll_rate(struct pic32_clk_priv *priv) { u32 iclk, idiv, odiv, mult; ulong plliclk, v; v = readl(priv->iobase + SPLLCON); iclk = (v & ICLK_MASK); idiv = ((v >> 8) & PLLIDIV_MASK) + 1; odiv = ((v >> 24) & PLLODIV_MASK); mult = ((v >> 16) & PLLMUL_MASK) + 1; plliclk = iclk ? SYS_FRC_CLK_HZ : SYS_POSC_CLK_HZ; if (odiv < 2) odiv = 2; else if (odiv < 5) odiv = (1 << odiv); else odiv = 32; return ((plliclk / idiv) * mult) / odiv; } static ulong pic32_get_sysclk(struct pic32_clk_priv *priv) { ulong v; ulong hz; ulong div, frcdiv; ulong curr_osc; /* get clk source */ v = readl(priv->iobase + OSCCON); curr_osc = (v >> 12) & CUROSC_MASK; switch (curr_osc) { case SCLK_SRC_FRC1: case SCLK_SRC_FRC2: frcdiv = ((v >> 24) & FRCDIV_MASK); div = ((1 << frcdiv) + 1) + (128 * (frcdiv == 7)); hz = SYS_FRC_CLK_HZ / div; break; case SCLK_SRC_SPLL: hz = pic32_get_pll_rate(priv); break; case SCLK_SRC_POSC: hz = SYS_POSC_CLK_HZ; break; default: hz = 0; printf("clk: unknown sclk_src.\n"); break; } return hz; } static ulong pic32_get_pbclk(struct pic32_clk_priv *priv, int periph) { void __iomem *reg; ulong div, clk_freq; WARN_ON((periph < PB1CLK) || (periph > PB7CLK)); clk_freq = pic32_get_sysclk(priv); reg = priv->iobase + PB1DIV + (periph - PB1CLK) * 0x10; div = (readl(reg) & PBDIV_MASK) + 1; return clk_freq / div; } static ulong pic32_get_cpuclk(struct pic32_clk_priv *priv) { return pic32_get_pbclk(priv, PB7CLK); } static ulong pic32_set_refclk(struct pic32_clk_priv *priv, int periph, int parent_rate, int rate, int parent_id) { void __iomem *reg; u32 div, trim, v; u64 frac; WARN_ON((periph < REF1CLK) || (periph > REF5CLK)); /* calculate dividers, * rate = parent_rate / [2 * (div + (trim / 512))] */ if (parent_rate <= rate) { div = 0; trim = 0; } else { div = parent_rate / (rate << 1); frac = parent_rate; frac <<= 8; do_div(frac, rate); frac -= (u64)(div << 9); trim = (frac >= REFO_TRIM_MAX) ? REFO_TRIM_MAX : (u32)frac; } reg = priv->iobase + REFO1CON + (periph - REF1CLK) * 0x20; /* disable clk */ writel(REFO_ON | REFO_OE, reg + _CLR_OFFSET); /* wait till previous src change is active */ wait_for_bit_le32(reg, REFO_DIVSW_EN | REFO_ACTIVE, false, CONFIG_SYS_HZ, false); /* parent_id */ v = readl(reg); v &= ~(REFO_SEL_MASK << REFO_SEL_SHIFT); v |= (parent_id << REFO_SEL_SHIFT); /* apply rodiv */ v &= ~(REFO_DIV_MASK << REFO_DIV_SHIFT); v |= (div << REFO_DIV_SHIFT); writel(v, reg); /* apply trim */ v = readl(reg + REFO_TRIM_REG); v &= ~(REFO_TRIM_MASK << REFO_TRIM_SHIFT); v |= (trim << REFO_TRIM_SHIFT); writel(v, reg + REFO_TRIM_REG); /* enable clk */ writel(REFO_ON | REFO_OE, reg + _SET_OFFSET); /* switch divider */ writel(REFO_DIVSW_EN, reg + _SET_OFFSET); /* wait for divider switching to complete */ return wait_for_bit_le32(reg, REFO_DIVSW_EN, false, CONFIG_SYS_HZ, false); } static ulong pic32_get_refclk(struct pic32_clk_priv *priv, int periph) { u32 rodiv, rotrim, rosel, v, parent_rate; void __iomem *reg; u64 rate64; WARN_ON((periph < REF1CLK) || (periph > REF5CLK)); reg = priv->iobase + REFO1CON + (periph - REF1CLK) * 0x20; v = readl(reg); /* get rosel */ rosel = (v >> REFO_SEL_SHIFT) & REFO_SEL_MASK; /* get div */ rodiv = (v >> REFO_DIV_SHIFT) & REFO_DIV_MASK; /* get trim */ v = readl(reg + REFO_TRIM_REG); rotrim = (v >> REFO_TRIM_SHIFT) & REFO_TRIM_MASK; if (!rodiv) return 0; /* get parent rate */ switch (rosel) { case ROCLK_SRC_SCLK: parent_rate = pic32_get_cpuclk(priv); break; case ROCLK_SRC_SPLL: parent_rate = pic32_get_pll_rate(priv); break; default: parent_rate = 0; break; } /* Calculation * rate = parent_rate / [2 * (div + (trim / 512))] */ if (rotrim) { rodiv <<= 9; rodiv += rotrim; rate64 = parent_rate; rate64 <<= 8; do_div(rate64, rodiv); v = (u32)rate64; } else { v = parent_rate / (rodiv << 1); } return v; } static ulong pic32_get_mpll_rate(struct pic32_clk_priv *priv) { u32 v, idiv, mul; u32 odiv1, odiv2; u64 rate; v = readl(priv->syscfg_base + CFGMPLL); idiv = v & MPLL_IDIV; mul = (v >> MPLL_MULT_SHIFT) & MPLL_MULT; odiv1 = (v >> MPLL_ODIV1_SHIFT) & MPLL_ODIV1; odiv2 = (v >> MPLL_ODIV2_SHIFT) & MPLL_ODIV2; rate = (SYS_POSC_CLK_HZ / idiv) * mul; do_div(rate, odiv1); do_div(rate, odiv2); return (ulong)rate; } static int pic32_mpll_init(struct pic32_clk_priv *priv) { u32 v, mask; /* initialize */ v = (MPLL_IDIV_INIT << MPLL_IDIV_SHIFT) | (MPLL_MULT_INIT << MPLL_MULT_SHIFT) | (MPLL_ODIV1_INIT << MPLL_ODIV1_SHIFT) | (MPLL_ODIV2_INIT << MPLL_ODIV2_SHIFT); writel(v, priv->syscfg_base + CFGMPLL); /* Wait for ready */ mask = MPLL_RDY | MPLL_VREG_RDY; return wait_for_bit_le32(priv->syscfg_base + CFGMPLL, mask, true, get_tbclk(), false); } static void pic32_clk_init(struct udevice *dev) { const void *blob = gd->fdt_blob; struct pic32_clk_priv *priv; ulong rate, pll_hz; char propname[50]; int i; priv = dev_get_priv(dev); pll_hz = pic32_get_pll_rate(priv); /* Initialize REFOs as not initialized and enabled on reset. */ for (i = REF1CLK; i <= REF5CLK; i++) { snprintf(propname, sizeof(propname), "microchip,refo%d-frequency", i - REF1CLK + 1); rate = fdtdec_get_int(blob, dev_of_offset(dev), propname, 0); if (rate) pic32_set_refclk(priv, i, pll_hz, rate, ROCLK_SRC_SPLL); } /* Memory PLL */ pic32_mpll_init(priv); } static ulong pic32_get_rate(struct clk *clk) { struct pic32_clk_priv *priv = dev_get_priv(clk->dev); ulong rate; switch (clk->id) { case PB1CLK ... PB7CLK: rate = pic32_get_pbclk(priv, clk->id); break; case REF1CLK ... REF5CLK: rate = pic32_get_refclk(priv, clk->id); break; case PLLCLK: rate = pic32_get_pll_rate(priv); break; case MPLL: rate = pic32_get_mpll_rate(priv); break; default: rate = 0; break; } return rate; } static ulong pic32_set_rate(struct clk *clk, ulong rate) { struct pic32_clk_priv *priv = dev_get_priv(clk->dev); ulong pll_hz; switch (clk->id) { case REF1CLK ... REF5CLK: pll_hz = pic32_get_pll_rate(priv); pic32_set_refclk(priv, clk->id, pll_hz, rate, ROCLK_SRC_SPLL); break; default: break; } return rate; } static struct clk_ops pic32_pic32_clk_ops = { .set_rate = pic32_set_rate, .get_rate = pic32_get_rate, }; static int pic32_clk_probe(struct udevice *dev) { struct pic32_clk_priv *priv = dev_get_priv(dev); fdt_addr_t addr; fdt_size_t size; addr = fdtdec_get_addr_size(gd->fdt_blob, dev_of_offset(dev), "reg", &size); if (addr == FDT_ADDR_T_NONE) return -EINVAL; priv->iobase = ioremap(addr, size); if (!priv->iobase) return -EINVAL; priv->syscfg_base = pic32_get_syscfg_base(); /* initialize clocks */ pic32_clk_init(dev); return 0; } static const struct udevice_id pic32_clk_ids[] = { { .compatible = "microchip,pic32mzda-clk"}, {} }; U_BOOT_DRIVER(pic32_clk) = { .name = "pic32_clk", .id = UCLASS_CLK, .of_match = pic32_clk_ids, .ops = &pic32_pic32_clk_ops, .probe = pic32_clk_probe, .priv_auto_alloc_size = sizeof(struct pic32_clk_priv), };