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>
This commit is contained in:
parent
32c1a6eef8
commit
a0e7908326
33
doc/device-tree-bindings/clock/microchip,pic32-clock.txt
Normal file
33
doc/device-tree-bindings/clock/microchip,pic32-clock.txt
Normal file
@ -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>;
|
||||
};
|
@ -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
|
||||
|
433
drivers/clk/clk_pic32.c
Normal file
433
drivers/clk/clk_pic32.c
Normal file
@ -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),
|
||||
};
|
29
include/dt-bindings/clock/microchip,clock.h
Normal file
29
include/dt-bindings/clock/microchip,clock.h
Normal file
@ -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 */
|
Loading…
Reference in New Issue
Block a user