]> git.ipfire.org Git - people/ms/u-boot.git/commitdiff
drivers: clk: Add clock driver for Microchip PIC32 Microcontroller.
authorPurna Chandra Mandal <purna.mandal@microchip.com>
Thu, 28 Jan 2016 10:00:11 +0000 (15:30 +0530)
committerDaniel Schwierzeck <daniel.schwierzeck@gmail.com>
Mon, 1 Feb 2016 21:14:00 +0000 (22:14 +0100)
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>
doc/device-tree-bindings/clock/microchip,pic32-clock.txt [new file with mode: 0644]
drivers/clk/Makefile
drivers/clk/clk_pic32.c [new file with mode: 0644]
include/dt-bindings/clock/microchip,clock.h [new file with mode: 0644]

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 (file)
index 0000000..f185ce0
--- /dev/null
@@ -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>;
+       };
index 8aa81f49f4f38742f8048a04caf1d05c9f00997f..c9144e3e1d480a90ea9ecb1b0fd896eba03a83d9 100644 (file)
@@ -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 (file)
index 0000000..5d88354
--- /dev/null
@@ -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 (file)
index 0000000..93c222d
--- /dev/null
@@ -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 */