diff options
author | Purna Chandra Mandal | 2016-01-28 15:30:11 +0530 |
---|---|---|
committer | Daniel Schwierzeck | 2016-02-01 22:14:00 +0100 |
commit | a0e7908326b9dcde4c27dbbcbc52ccbf9e009255 (patch) | |
tree | fd9dec9f395fa000b97d83369a651ce0563aded4 | |
parent | 32c1a6eef8b467bb776f989caf0480fce6218598 (diff) |
drivers: clk: Add clock driver for Microchip PIC32 Microcontroller.
PIC32 clock module consists of multiple oscillators, PLLs, mutiplexers
and dividers capable of supplying clock to various controllers
on or off-chip.
Signed-off-by: Purna Chandra Mandal <purna.mandal@microchip.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Daniel Schwierzeck <daniel.schwierzeck@gmail.com>
-rw-r--r-- | doc/device-tree-bindings/clock/microchip,pic32-clock.txt | 33 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/clk_pic32.c | 433 | ||||
-rw-r--r-- | include/dt-bindings/clock/microchip,clock.h | 29 |
4 files changed, 496 insertions, 0 deletions
diff --git a/doc/device-tree-bindings/clock/microchip,pic32-clock.txt b/doc/device-tree-bindings/clock/microchip,pic32-clock.txt new file mode 100644 index 00000000000..f185ce0ae19 --- /dev/null +++ b/doc/device-tree-bindings/clock/microchip,pic32-clock.txt @@ -0,0 +1,33 @@ +* Microchip PIC32 Clock and Oscillator + +Microchip PIC32 clock tree consists of few oscillators, PLLs, +multiplexers and few divider modules capable of supplying clocks +to various controllers within SoC and also to off-chip. + +PIC32 clock controller output is defined by indices as defined +in [0] + +[0] include/dt-bindings/clock/microchip,clock.h + +Required Properties: +- compatible: should be "microchip,pic32mzda_clk" +- reg: physical base address of the controller and length of memory mapped + region. +- #clock-cells: should be 1. + +Example: Clock controller node: + + clock: clk@1f801200 { + compatible = "microchip,pic32mzda-clk"; + reg = <0x1f801200 0x1000>; + }; + +Example: UART controller node that consumes the clock generated by the clock +controller: + + uart1: serial@1f822000 { + compatible = "microchip,pic32mzda-uart"; + reg = <0xbf822000 0x50>; + interrupts = <112 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clock PB2CLK>; + }; diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 8aa81f49f4f..c9144e3e1d4 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_CLK) += clk-uclass.o clk_fixed_rate.o obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o obj-$(CONFIG_SANDBOX) += clk_sandbox.o +obj-$(CONFIG_MACH_PIC32) += clk_pic32.o diff --git a/drivers/clk/clk_pic32.c b/drivers/clk/clk_pic32.c new file mode 100644 index 00000000000..5d883544d51 --- /dev/null +++ b/drivers/clk/clk_pic32.c @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2015 Purna Chandra Mandal <purna.mandal@microchip.com> + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#include <common.h> +#include <clk.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(__func__, 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(__func__, 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(__func__, 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, propname, 0); + if (rate) + pic32_set_refclk(priv, i, pll_hz, rate, ROCLK_SRC_SPLL); + } + + /* Memory PLL */ + pic32_mpll_init(priv); +} + +static ulong pic32_clk_get_rate(struct udevice *dev) +{ + struct pic32_clk_priv *priv = dev_get_priv(dev); + + return pic32_get_cpuclk(priv); +} + +static ulong pic32_get_periph_rate(struct udevice *dev, int periph) +{ + struct pic32_clk_priv *priv = dev_get_priv(dev); + ulong rate; + + switch (periph) { + case PB1CLK ... PB7CLK: + rate = pic32_get_pbclk(priv, periph); + break; + case REF1CLK ... REF5CLK: + rate = pic32_get_refclk(priv, periph); + 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_periph_rate(struct udevice *dev, int periph, ulong rate) +{ + struct pic32_clk_priv *priv = dev_get_priv(dev); + ulong pll_hz; + + switch (periph) { + case REF1CLK ... REF5CLK: + pll_hz = pic32_get_pll_rate(priv); + pic32_set_refclk(priv, periph, pll_hz, rate, ROCLK_SRC_SPLL); + break; + default: + break; + } + + return rate; +} + +static struct clk_ops pic32_pic32_clk_ops = { + .get_rate = pic32_clk_get_rate, + .set_periph_rate = pic32_set_periph_rate, + .get_periph_rate = pic32_get_periph_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, "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, + .flags = DM_FLAG_PRE_RELOC, + .ops = &pic32_pic32_clk_ops, + .probe = pic32_clk_probe, + .priv_auto_alloc_size = sizeof(struct pic32_clk_priv), +}; diff --git a/include/dt-bindings/clock/microchip,clock.h b/include/dt-bindings/clock/microchip,clock.h new file mode 100644 index 00000000000..93c222d74fd --- /dev/null +++ b/include/dt-bindings/clock/microchip,clock.h @@ -0,0 +1,29 @@ +/* + * (c) 2015 Purna Chandra Mandal <purna.mandal@microchip.com> + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#ifndef __CLK_MICROCHIP_PIC32 +#define __CLK_MICROCHIP_PIC32 + +/* clock output indices */ +#define BASECLK 0 +#define PLLCLK 1 +#define MPLL 2 +#define SYSCLK 3 +#define PB1CLK 4 +#define PB2CLK 5 +#define PB3CLK 6 +#define PB4CLK 7 +#define PB5CLK 8 +#define PB6CLK 9 +#define PB7CLK 10 +#define REF1CLK 11 +#define REF2CLK 12 +#define REF3CLK 13 +#define REF4CLK 14 +#define REF5CLK 15 + +#endif /* __CLK_MICROCHIP_PIC32 */ |