avionic design with actual uboot and tooling

submodule of avionic design uboot bootloader and with included tools to
get you started , read readme.md and readme-tk1-loader.md
This commit is contained in:
2026-03-03 21:46:32 +02:00
parent fe3ba02c96
commit 68d74d3181
11967 changed files with 2221897 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
menu "Clock"
config CLK
bool "Enable clock driver support"
depends on DM
help
This allows drivers to be provided for clock generators, including
oscillators and PLLs. Devices can use a common clock API to request
a particular clock rate and check on available clocks. Clocks can
feed into other clocks in a tree structure, with multiplexers to
choose the source for each clock.
config SPL_CLK
bool "Enable clock support in SPL"
depends on CLK
help
The clock subsystem adds a small amount of overhead to the image.
If this is acceptable and you have a need to use clock drivers in
SPL, enable this option. It might provide a cleaner interface to
setting up clocks within SPL, and allows the same drivers to be
used as U-Boot proper.
source "drivers/clk/uniphier/Kconfig"
source "drivers/clk/exynos/Kconfig"
endmenu

View File

@@ -0,0 +1,15 @@
#
# Copyright (c) 2015 Google, Inc
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
#
# SPDX-License-Identifier: GPL-2.0+
#
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_SANDBOX) += clk_sandbox_test.o
obj-$(CONFIG_MACH_PIC32) += clk_pic32.o
obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
obj-$(CONFIG_CLK_EXYNOS) += exynos/

View File

@@ -0,0 +1,200 @@
/*
* Copyright (C) 2015 Google, Inc
* Written by Simon Glass <sjg@chromium.org>
* Copyright (c) 2016, NVIDIA CORPORATION.
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <clk.h>
#include <clk-uclass.h>
#include <dm.h>
#include <errno.h>
DECLARE_GLOBAL_DATA_PTR;
static inline struct clk_ops *clk_dev_ops(struct udevice *dev)
{
return (struct clk_ops *)dev->driver->ops;
}
#if CONFIG_IS_ENABLED(OF_CONTROL)
#ifdef CONFIG_SPL_BUILD
int clk_get_by_index(struct udevice *dev, int index, struct clk *clk)
{
int ret;
u32 cell[2];
if (index != 0)
return -ENOSYS;
assert(clk);
ret = uclass_get_device(UCLASS_CLK, 0, &clk->dev);
if (ret)
return ret;
ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clocks",
cell, 2);
if (ret)
return ret;
clk->id = cell[1];
return 0;
}
int clk_get_by_name(struct udevice *dev, const char *name, struct clk *clk)
{
return -ENOSYS;
}
#else
static int clk_of_xlate_default(struct clk *clk,
struct fdtdec_phandle_args *args)
{
debug("%s(clk=%p)\n", __func__, clk);
if (args->args_count > 1) {
debug("Invaild args_count: %d\n", args->args_count);
return -EINVAL;
}
if (args->args_count)
clk->id = args->args[0];
else
clk->id = 0;
return 0;
}
int clk_get_by_index(struct udevice *dev, int index, struct clk *clk)
{
int ret;
struct fdtdec_phandle_args args;
struct udevice *dev_clk;
struct clk_ops *ops;
debug("%s(dev=%p, index=%d, clk=%p)\n", __func__, dev, index, clk);
assert(clk);
ret = fdtdec_parse_phandle_with_args(gd->fdt_blob, dev->of_offset,
"clocks", "#clock-cells", 0, index,
&args);
if (ret) {
debug("%s: fdtdec_parse_phandle_with_args failed: err=%d\n",
__func__, ret);
return ret;
}
ret = uclass_get_device_by_of_offset(UCLASS_CLK, args.node, &dev_clk);
if (ret) {
debug("%s: uclass_get_device_by_of_offset failed: err=%d\n",
__func__, ret);
return ret;
}
ops = clk_dev_ops(dev_clk);
if (ops->of_xlate)
ret = ops->of_xlate(clk, &args);
else
ret = clk_of_xlate_default(clk, &args);
if (ret) {
debug("of_xlate() failed: %d\n", ret);
return ret;
}
return clk_request(dev_clk, clk);
}
int clk_get_by_name(struct udevice *dev, const char *name, struct clk *clk)
{
int index;
debug("%s(dev=%p, name=%s, clk=%p)\n", __func__, dev, name, clk);
index = fdt_find_string(gd->fdt_blob, dev->of_offset, "clock-names",
name);
if (index < 0) {
debug("fdt_find_string() failed: %d\n", index);
return index;
}
return clk_get_by_index(dev, index, clk);
}
#endif
#endif
int clk_request(struct udevice *dev, struct clk *clk)
{
struct clk_ops *ops = clk_dev_ops(dev);
debug("%s(dev=%p, clk=%p)\n", __func__, dev, clk);
clk->dev = dev;
if (!ops->request)
return 0;
return ops->request(clk);
}
int clk_free(struct clk *clk)
{
struct clk_ops *ops = clk_dev_ops(clk->dev);
debug("%s(clk=%p)\n", __func__, clk);
if (!ops->free)
return 0;
return ops->free(clk);
}
ulong clk_get_rate(struct clk *clk)
{
struct clk_ops *ops = clk_dev_ops(clk->dev);
debug("%s(clk=%p)\n", __func__, clk);
if (!ops->get_rate)
return -ENOSYS;
return ops->get_rate(clk);
}
ulong clk_set_rate(struct clk *clk, ulong rate)
{
struct clk_ops *ops = clk_dev_ops(clk->dev);
debug("%s(clk=%p, rate=%lu)\n", __func__, clk, rate);
if (!ops->set_rate)
return -ENOSYS;
return ops->set_rate(clk, rate);
}
int clk_enable(struct clk *clk)
{
struct clk_ops *ops = clk_dev_ops(clk->dev);
debug("%s(clk=%p)\n", __func__, clk);
if (!ops->enable)
return -ENOSYS;
return ops->enable(clk);
}
int clk_disable(struct clk *clk)
{
struct clk_ops *ops = clk_dev_ops(clk->dev);
debug("%s(clk=%p)\n", __func__, clk);
if (!ops->disable)
return -ENOSYS;
return ops->disable(clk);
}
UCLASS_DRIVER(clk) = {
.id = UCLASS_CLK,
.name = "clk",
};

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2016 Masahiro Yamada <yamada.masahiro@socionext.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <clk-uclass.h>
#include <dm/device.h>
DECLARE_GLOBAL_DATA_PTR;
struct clk_fixed_rate {
unsigned long fixed_rate;
};
#define to_clk_fixed_rate(dev) ((struct clk_fixed_rate *)dev_get_platdata(dev))
static ulong clk_fixed_rate_get_rate(struct clk *clk)
{
if (clk->id != 0)
return -EINVAL;
return to_clk_fixed_rate(clk->dev)->fixed_rate;
}
const struct clk_ops clk_fixed_rate_ops = {
.get_rate = clk_fixed_rate_get_rate,
};
static int clk_fixed_rate_ofdata_to_platdata(struct udevice *dev)
{
to_clk_fixed_rate(dev)->fixed_rate =
fdtdec_get_int(gd->fdt_blob, dev->of_offset,
"clock-frequency", 0);
return 0;
}
static const struct udevice_id clk_fixed_rate_match[] = {
{
.compatible = "fixed-clock",
},
{ /* sentinel */ }
};
U_BOOT_DRIVER(clk_fixed_rate) = {
.name = "fixed_rate_clock",
.id = UCLASS_CLK,
.of_match = clk_fixed_rate_match,
.ofdata_to_platdata = clk_fixed_rate_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct clk_fixed_rate),
.ops = &clk_fixed_rate_ops,
};

View File

@@ -0,0 +1,425 @@
/*
* Copyright (C) 2015 Purna Chandra Mandal <purna.mandal@microchip.com>
*
* SPDX-License-Identifier: GPL-2.0+
*
*/
#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(__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_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, "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),
};

View File

@@ -0,0 +1,386 @@
/*
* (C) Copyright 2015 Google, Inc
*
* SPDX-License-Identifier: GPL-2.0
*/
#include <common.h>
#include <clk-uclass.h>
#include <dm.h>
#include <errno.h>
#include <syscon.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/arch/cru_rk3036.h>
#include <asm/arch/hardware.h>
#include <dm/lists.h>
#include <dt-bindings/clock/rk3036-cru.h>
DECLARE_GLOBAL_DATA_PTR;
struct rk3036_clk_priv {
struct rk3036_cru *cru;
ulong rate;
};
enum {
VCO_MAX_HZ = 2400U * 1000000,
VCO_MIN_HZ = 600 * 1000000,
OUTPUT_MAX_HZ = 2400U * 1000000,
OUTPUT_MIN_HZ = 24 * 1000000,
};
#define RATE_TO_DIV(input_rate, output_rate) \
((input_rate) / (output_rate) - 1);
#define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1))
#define PLL_DIVISORS(hz, _refdiv, _postdiv1, _postdiv2) {\
.refdiv = _refdiv,\
.fbdiv = (u32)((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ),\
.postdiv1 = _postdiv1, .postdiv2 = _postdiv2};\
_Static_assert(((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ) *\
OSC_HZ / (_refdiv * _postdiv1 * _postdiv2) == hz,\
#hz "Hz cannot be hit with PLL "\
"divisors on line " __stringify(__LINE__));
/* use interge mode*/
static const struct pll_div apll_init_cfg = PLL_DIVISORS(APLL_HZ, 1, 3, 1);
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2, 1);
static inline unsigned int log2(unsigned int value)
{
return fls(value) - 1;
}
void *rockchip_get_cru(void)
{
struct udevice *dev;
fdt_addr_t addr;
int ret;
ret = uclass_get_device(UCLASS_CLK, 0, &dev);
if (ret)
return ERR_PTR(ret);
addr = dev_get_addr(dev);
if (addr == FDT_ADDR_T_NONE)
return ERR_PTR(-EINVAL);
return (void *)addr;
}
static int rkclk_set_pll(struct rk3036_cru *cru, enum rk_clk_id clk_id,
const struct pll_div *div)
{
int pll_id = rk_pll_id(clk_id);
struct rk3036_pll *pll = &cru->pll[pll_id];
/* All PLLs have same VCO and output frequency range restrictions. */
uint vco_hz = OSC_HZ / 1000 * div->fbdiv / div->refdiv * 1000;
uint output_hz = vco_hz / div->postdiv1 / div->postdiv2;
debug("PLL at %p: fbdiv=%d, refdiv=%d, postdiv1=%d, postdiv2=%d,\
vco=%u Hz, output=%u Hz\n",
pll, div->fbdiv, div->refdiv, div->postdiv1,
div->postdiv2, vco_hz, output_hz);
assert(vco_hz >= VCO_MIN_HZ && vco_hz <= VCO_MAX_HZ &&
output_hz >= OUTPUT_MIN_HZ && output_hz <= OUTPUT_MAX_HZ);
/* use interger mode */
rk_clrreg(&pll->con1, 1 << PLL_DSMPD_SHIFT);
rk_clrsetreg(&pll->con0,
PLL_POSTDIV1_MASK << PLL_POSTDIV1_SHIFT | PLL_FBDIV_MASK,
(div->postdiv1 << PLL_POSTDIV1_SHIFT) | div->fbdiv);
rk_clrsetreg(&pll->con1, PLL_POSTDIV2_MASK << PLL_POSTDIV2_SHIFT |
PLL_REFDIV_MASK << PLL_REFDIV_SHIFT,
(div->postdiv2 << PLL_POSTDIV2_SHIFT |
div->refdiv << PLL_REFDIV_SHIFT));
/* waiting for pll lock */
while (readl(&pll->con1) & (1 << PLL_LOCK_STATUS_SHIFT))
udelay(1);
return 0;
}
static void rkclk_init(struct rk3036_cru *cru)
{
u32 aclk_div;
u32 hclk_div;
u32 pclk_div;
/* pll enter slow-mode */
rk_clrsetreg(&cru->cru_mode_con,
GPLL_MODE_MASK << GPLL_MODE_SHIFT |
APLL_MODE_MASK << APLL_MODE_SHIFT,
GPLL_MODE_SLOW << GPLL_MODE_SHIFT |
APLL_MODE_SLOW << APLL_MODE_SHIFT);
/* init pll */
rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg);
rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);
/*
* select apll as core clock pll source and
* set up dependent divisors for PCLK/HCLK and ACLK clocks.
* core hz : apll = 1:1
*/
aclk_div = APLL_HZ / CORE_ACLK_HZ - 1;
assert((aclk_div + 1) * CORE_ACLK_HZ == APLL_HZ && aclk_div < 0x7);
pclk_div = APLL_HZ / CORE_PERI_HZ - 1;
assert((pclk_div + 1) * CORE_PERI_HZ == APLL_HZ && pclk_div < 0xf);
rk_clrsetreg(&cru->cru_clksel_con[0],
CORE_CLK_PLL_SEL_MASK << CORE_CLK_PLL_SEL_SHIFT |
CORE_DIV_CON_MASK << CORE_DIV_CON_SHIFT,
CORE_CLK_PLL_SEL_APLL << CORE_CLK_PLL_SEL_SHIFT |
0 << CORE_DIV_CON_SHIFT);
rk_clrsetreg(&cru->cru_clksel_con[1],
CORE_ACLK_DIV_MASK << CORE_ACLK_DIV_SHIFT |
CORE_PERI_DIV_MASK << CORE_PERI_DIV_SHIFT,
aclk_div << CORE_ACLK_DIV_SHIFT |
pclk_div << CORE_PERI_DIV_SHIFT);
/*
* select apll as cpu clock pll source and
* set up dependent divisors for PCLK/HCLK and ACLK clocks.
*/
aclk_div = APLL_HZ / CPU_ACLK_HZ - 1;
assert((aclk_div + 1) * CPU_ACLK_HZ == APLL_HZ && aclk_div < 0x1f);
pclk_div = APLL_HZ / CPU_PCLK_HZ - 1;
assert((pclk_div + 1) * CPU_PCLK_HZ == APLL_HZ && pclk_div < 0x7);
hclk_div = APLL_HZ / CPU_HCLK_HZ - 1;
assert((hclk_div + 1) * CPU_HCLK_HZ == APLL_HZ && hclk_div < 0x3);
rk_clrsetreg(&cru->cru_clksel_con[0],
CPU_CLK_PLL_SEL_MASK << CPU_CLK_PLL_SEL_SHIFT |
ACLK_CPU_DIV_MASK << ACLK_CPU_DIV_SHIFT,
CPU_CLK_PLL_SEL_APLL << CPU_CLK_PLL_SEL_SHIFT |
aclk_div << ACLK_CPU_DIV_SHIFT);
rk_clrsetreg(&cru->cru_clksel_con[1],
CPU_PCLK_DIV_MASK << CPU_PCLK_DIV_SHIFT |
CPU_HCLK_DIV_MASK << CPU_HCLK_DIV_SHIFT,
pclk_div << CPU_PCLK_DIV_SHIFT |
hclk_div << CPU_HCLK_DIV_SHIFT);
/*
* select gpll as peri clock pll source and
* set up dependent divisors for PCLK/HCLK and ACLK clocks.
*/
aclk_div = GPLL_HZ / PERI_ACLK_HZ - 1;
assert((aclk_div + 1) * PERI_ACLK_HZ == GPLL_HZ && aclk_div < 0x1f);
hclk_div = log2(PERI_ACLK_HZ / PERI_HCLK_HZ);
assert((1 << hclk_div) * PERI_HCLK_HZ ==
PERI_ACLK_HZ && (pclk_div < 0x4));
pclk_div = log2(PERI_ACLK_HZ / PERI_PCLK_HZ);
assert((1 << pclk_div) * PERI_PCLK_HZ ==
PERI_ACLK_HZ && pclk_div < 0x8);
rk_clrsetreg(&cru->cru_clksel_con[10],
PERI_PLL_SEL_MASK << PERI_PLL_SEL_SHIFT |
PERI_PCLK_DIV_MASK << PERI_PCLK_DIV_SHIFT |
PERI_HCLK_DIV_MASK << PERI_HCLK_DIV_SHIFT |
PERI_ACLK_DIV_MASK << PERI_ACLK_DIV_SHIFT,
PERI_PLL_GPLL << PERI_PLL_SEL_SHIFT |
pclk_div << PERI_PCLK_DIV_SHIFT |
hclk_div << PERI_HCLK_DIV_SHIFT |
aclk_div << PERI_ACLK_DIV_SHIFT);
/* PLL enter normal-mode */
rk_clrsetreg(&cru->cru_mode_con,
GPLL_MODE_MASK << GPLL_MODE_SHIFT |
APLL_MODE_MASK << APLL_MODE_SHIFT,
GPLL_MODE_NORM << GPLL_MODE_SHIFT |
APLL_MODE_NORM << APLL_MODE_SHIFT);
}
/* Get pll rate by id */
static uint32_t rkclk_pll_get_rate(struct rk3036_cru *cru,
enum rk_clk_id clk_id)
{
uint32_t refdiv, fbdiv, postdiv1, postdiv2;
uint32_t con;
int pll_id = rk_pll_id(clk_id);
struct rk3036_pll *pll = &cru->pll[pll_id];
static u8 clk_shift[CLK_COUNT] = {
0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, 0xff,
GPLL_MODE_SHIFT, 0xff
};
static u8 clk_mask[CLK_COUNT] = {
0xff, APLL_MODE_MASK, DPLL_MODE_MASK, 0xff,
GPLL_MODE_MASK, 0xff
};
uint shift;
uint mask;
con = readl(&cru->cru_mode_con);
shift = clk_shift[clk_id];
mask = clk_mask[clk_id];
switch ((con >> shift) & mask) {
case GPLL_MODE_SLOW:
return OSC_HZ;
case GPLL_MODE_NORM:
/* normal mode */
con = readl(&pll->con0);
postdiv1 = (con >> PLL_POSTDIV1_SHIFT) & PLL_POSTDIV1_MASK;
fbdiv = (con >> PLL_FBDIV_SHIFT) & PLL_FBDIV_MASK;
con = readl(&pll->con1);
postdiv2 = (con >> PLL_POSTDIV2_SHIFT) & PLL_POSTDIV2_MASK;
refdiv = (con >> PLL_REFDIV_SHIFT) & PLL_REFDIV_MASK;
return (24 * fbdiv / (refdiv * postdiv1 * postdiv2)) * 1000000;
case GPLL_MODE_DEEP:
default:
return 32768;
}
}
static ulong rockchip_mmc_get_clk(struct rk3036_cru *cru, uint clk_general_rate,
int periph)
{
uint src_rate;
uint div, mux;
u32 con;
switch (periph) {
case HCLK_EMMC:
con = readl(&cru->cru_clksel_con[12]);
mux = (con >> EMMC_PLL_SHIFT) & EMMC_PLL_MASK;
div = (con >> EMMC_DIV_SHIFT) & EMMC_DIV_MASK;
break;
case HCLK_SDIO:
con = readl(&cru->cru_clksel_con[12]);
mux = (con >> MMC0_PLL_SHIFT) & MMC0_PLL_MASK;
div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK;
break;
default:
return -EINVAL;
}
src_rate = mux == EMMC_SEL_24M ? OSC_HZ : clk_general_rate;
return DIV_TO_RATE(src_rate, div);
}
static ulong rockchip_mmc_set_clk(struct rk3036_cru *cru, uint clk_general_rate,
int periph, uint freq)
{
int src_clk_div;
int mux;
debug("%s: clk_general_rate=%u\n", __func__, clk_general_rate);
/* mmc clock auto divide 2 in internal */
src_clk_div = (clk_general_rate / 2 + freq - 1) / freq;
if (src_clk_div > 0x7f) {
src_clk_div = (OSC_HZ / 2 + freq - 1) / freq;
mux = EMMC_SEL_24M;
} else {
mux = EMMC_SEL_GPLL;
}
switch (periph) {
case HCLK_EMMC:
rk_clrsetreg(&cru->cru_clksel_con[12],
EMMC_PLL_MASK << EMMC_PLL_SHIFT |
EMMC_DIV_MASK << EMMC_DIV_SHIFT,
mux << EMMC_PLL_SHIFT |
(src_clk_div - 1) << EMMC_DIV_SHIFT);
break;
case HCLK_SDIO:
rk_clrsetreg(&cru->cru_clksel_con[11],
MMC0_PLL_MASK << MMC0_PLL_SHIFT |
MMC0_DIV_MASK << MMC0_DIV_SHIFT,
mux << MMC0_PLL_SHIFT |
(src_clk_div - 1) << MMC0_DIV_SHIFT);
break;
default:
return -EINVAL;
}
return rockchip_mmc_get_clk(cru, clk_general_rate, periph);
}
static ulong rk3036_clk_get_rate(struct clk *clk)
{
struct rk3036_clk_priv *priv = dev_get_priv(clk->dev);
switch (clk->id) {
case 0 ... 63:
return rkclk_pll_get_rate(priv->cru, clk->id);
default:
return -ENOENT;
}
}
static ulong rk3036_clk_set_rate(struct clk *clk, ulong rate)
{
struct rk3036_clk_priv *priv = dev_get_priv(clk->dev);
ulong new_rate, gclk_rate;
gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL);
switch (clk->id) {
case 0 ... 63:
return 0;
case HCLK_EMMC:
new_rate = rockchip_mmc_set_clk(priv->cru, gclk_rate,
clk->id, rate);
break;
default:
return -ENOENT;
}
return new_rate;
}
static struct clk_ops rk3036_clk_ops = {
.get_rate = rk3036_clk_get_rate,
.set_rate = rk3036_clk_set_rate,
};
static int rk3036_clk_probe(struct udevice *dev)
{
struct rk3036_clk_priv *priv = dev_get_priv(dev);
priv->cru = (struct rk3036_cru *)dev_get_addr(dev);
rkclk_init(priv->cru);
return 0;
}
static int rk3036_clk_bind(struct udevice *dev)
{
int ret;
/* The reset driver does not have a device node, so bind it here */
ret = device_bind_driver(gd->dm_root, "rk3036_sysreset", "reset", &dev);
if (ret)
debug("Warning: No RK3036 reset driver: ret=%d\n", ret);
return 0;
}
static const struct udevice_id rk3036_clk_ids[] = {
{ .compatible = "rockchip,rk3036-cru" },
{ }
};
U_BOOT_DRIVER(clk_rk3036) = {
.name = "clk_rk3036",
.id = UCLASS_CLK,
.of_match = rk3036_clk_ids,
.priv_auto_alloc_size = sizeof(struct rk3036_clk_priv),
.ops = &rk3036_clk_ops,
.bind = rk3036_clk_bind,
.probe = rk3036_clk_probe,
};

View File

@@ -0,0 +1,824 @@
/*
* (C) Copyright 2015 Google, Inc
*
* SPDX-License-Identifier: GPL-2.0
*/
#include <common.h>
#include <clk-uclass.h>
#include <dm.h>
#include <errno.h>
#include <syscon.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/arch/cru_rk3288.h>
#include <asm/arch/grf_rk3288.h>
#include <asm/arch/hardware.h>
#include <dt-bindings/clock/rk3288-cru.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <dm/uclass-internal.h>
DECLARE_GLOBAL_DATA_PTR;
struct rk3288_clk_priv {
struct rk3288_grf *grf;
struct rk3288_cru *cru;
ulong rate;
};
struct pll_div {
u32 nr;
u32 nf;
u32 no;
};
enum {
VCO_MAX_HZ = 2200U * 1000000,
VCO_MIN_HZ = 440 * 1000000,
OUTPUT_MAX_HZ = 2200U * 1000000,
OUTPUT_MIN_HZ = 27500000,
FREF_MAX_HZ = 2200U * 1000000,
FREF_MIN_HZ = 269 * 1000000,
};
enum {
/* PLL CON0 */
PLL_OD_MASK = 0x0f,
/* PLL CON1 */
PLL_NF_MASK = 0x1fff,
/* PLL CON2 */
PLL_BWADJ_MASK = 0x0fff,
/* PLL CON3 */
PLL_RESET_SHIFT = 5,
/* CLKSEL0 */
CORE_SEL_PLL_MASK = 1,
CORE_SEL_PLL_SHIFT = 15,
A17_DIV_MASK = 0x1f,
A17_DIV_SHIFT = 8,
MP_DIV_MASK = 0xf,
MP_DIV_SHIFT = 4,
M0_DIV_MASK = 0xf,
M0_DIV_SHIFT = 0,
/* CLKSEL1: pd bus clk pll sel: codec or general */
PD_BUS_SEL_PLL_MASK = 15,
PD_BUS_SEL_CPLL = 0,
PD_BUS_SEL_GPLL,
/* pd bus pclk div: pclk = pd_bus_aclk /(div + 1) */
PD_BUS_PCLK_DIV_SHIFT = 12,
PD_BUS_PCLK_DIV_MASK = 7,
/* pd bus hclk div: aclk_bus: hclk_bus = 1:1 or 2:1 or 4:1 */
PD_BUS_HCLK_DIV_SHIFT = 8,
PD_BUS_HCLK_DIV_MASK = 3,
/* pd bus aclk div: pd_bus_aclk = pd_bus_src_clk /(div0 * div1) */
PD_BUS_ACLK_DIV0_SHIFT = 3,
PD_BUS_ACLK_DIV0_MASK = 0x1f,
PD_BUS_ACLK_DIV1_SHIFT = 0,
PD_BUS_ACLK_DIV1_MASK = 0x7,
/*
* CLKSEL10
* peripheral bus pclk div:
* aclk_bus: pclk_bus = 1:1 or 2:1 or 4:1 or 8:1
*/
PERI_SEL_PLL_MASK = 1,
PERI_SEL_PLL_SHIFT = 15,
PERI_SEL_CPLL = 0,
PERI_SEL_GPLL,
PERI_PCLK_DIV_SHIFT = 12,
PERI_PCLK_DIV_MASK = 3,
/* peripheral bus hclk div: aclk_bus: hclk_bus = 1:1 or 2:1 or 4:1 */
PERI_HCLK_DIV_SHIFT = 8,
PERI_HCLK_DIV_MASK = 3,
/*
* peripheral bus aclk div:
* aclk_periph = periph_clk_src / (peri_aclk_div_con + 1)
*/
PERI_ACLK_DIV_SHIFT = 0,
PERI_ACLK_DIV_MASK = 0x1f,
SOCSTS_DPLL_LOCK = 1 << 5,
SOCSTS_APLL_LOCK = 1 << 6,
SOCSTS_CPLL_LOCK = 1 << 7,
SOCSTS_GPLL_LOCK = 1 << 8,
SOCSTS_NPLL_LOCK = 1 << 9,
};
#define RATE_TO_DIV(input_rate, output_rate) \
((input_rate) / (output_rate) - 1);
#define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1))
#define PLL_DIVISORS(hz, _nr, _no) {\
.nr = _nr, .nf = (u32)((u64)hz * _nr * _no / OSC_HZ), .no = _no};\
_Static_assert(((u64)hz * _nr * _no / OSC_HZ) * OSC_HZ /\
(_nr * _no) == hz, #hz "Hz cannot be hit with PLL "\
"divisors on line " __stringify(__LINE__));
/* Keep divisors as low as possible to reduce jitter and power usage */
static const struct pll_div apll_init_cfg = PLL_DIVISORS(APLL_HZ, 1, 1);
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2);
static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 1, 2);
void *rockchip_get_cru(void)
{
struct rk3288_clk_priv *priv;
struct udevice *dev;
int ret;
ret = uclass_get_device(UCLASS_CLK, 0, &dev);
if (ret)
return ERR_PTR(ret);
priv = dev_get_priv(dev);
return priv->cru;
}
static int rkclk_set_pll(struct rk3288_cru *cru, enum rk_clk_id clk_id,
const struct pll_div *div)
{
int pll_id = rk_pll_id(clk_id);
struct rk3288_pll *pll = &cru->pll[pll_id];
/* All PLLs have same VCO and output frequency range restrictions. */
uint vco_hz = OSC_HZ / 1000 * div->nf / div->nr * 1000;
uint output_hz = vco_hz / div->no;
debug("PLL at %x: nf=%d, nr=%d, no=%d, vco=%u Hz, output=%u Hz\n",
(uint)pll, div->nf, div->nr, div->no, vco_hz, output_hz);
assert(vco_hz >= VCO_MIN_HZ && vco_hz <= VCO_MAX_HZ &&
output_hz >= OUTPUT_MIN_HZ && output_hz <= OUTPUT_MAX_HZ &&
(div->no == 1 || !(div->no % 2)));
/* enter reset */
rk_setreg(&pll->con3, 1 << PLL_RESET_SHIFT);
rk_clrsetreg(&pll->con0,
CLKR_MASK << CLKR_SHIFT | PLL_OD_MASK,
((div->nr - 1) << CLKR_SHIFT) | (div->no - 1));
rk_clrsetreg(&pll->con1, CLKF_MASK, div->nf - 1);
rk_clrsetreg(&pll->con2, PLL_BWADJ_MASK, (div->nf >> 1) - 1);
udelay(10);
/* return from reset */
rk_clrreg(&pll->con3, 1 << PLL_RESET_SHIFT);
return 0;
}
static inline unsigned int log2(unsigned int value)
{
return fls(value) - 1;
}
static int rkclk_configure_ddr(struct rk3288_cru *cru, struct rk3288_grf *grf,
unsigned int hz)
{
static const struct pll_div dpll_cfg[] = {
{.nf = 25, .nr = 2, .no = 1},
{.nf = 400, .nr = 9, .no = 2},
{.nf = 500, .nr = 9, .no = 2},
{.nf = 100, .nr = 3, .no = 1},
};
int cfg;
switch (hz) {
case 300000000:
cfg = 0;
break;
case 533000000: /* actually 533.3P MHz */
cfg = 1;
break;
case 666000000: /* actually 666.6P MHz */
cfg = 2;
break;
case 800000000:
cfg = 3;
break;
default:
debug("Unsupported SDRAM frequency");
return -EINVAL;
}
/* pll enter slow-mode */
rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK << DPLL_MODE_SHIFT,
DPLL_MODE_SLOW << DPLL_MODE_SHIFT);
rkclk_set_pll(cru, CLK_DDR, &dpll_cfg[cfg]);
/* wait for pll lock */
while (!(readl(&grf->soc_status[1]) & SOCSTS_DPLL_LOCK))
udelay(1);
/* PLL enter normal-mode */
rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK << DPLL_MODE_SHIFT,
DPLL_MODE_NORMAL << DPLL_MODE_SHIFT);
return 0;
}
#ifndef CONFIG_SPL_BUILD
#define VCO_MAX_KHZ 2200000
#define VCO_MIN_KHZ 440000
#define FREF_MAX_KHZ 2200000
#define FREF_MIN_KHZ 269
static int pll_para_config(ulong freq_hz, struct pll_div *div, uint *ext_div)
{
uint ref_khz = OSC_HZ / 1000, nr, nf = 0;
uint fref_khz;
uint diff_khz, best_diff_khz;
const uint max_nr = 1 << 6, max_nf = 1 << 12, max_no = 1 << 4;
uint vco_khz;
uint no = 1;
uint freq_khz = freq_hz / 1000;
if (!freq_hz) {
printf("%s: the frequency can not be 0 Hz\n", __func__);
return -EINVAL;
}
no = DIV_ROUND_UP(VCO_MIN_KHZ, freq_khz);
if (ext_div) {
*ext_div = DIV_ROUND_UP(no, max_no);
no = DIV_ROUND_UP(no, *ext_div);
}
/* only even divisors (and 1) are supported */
if (no > 1)
no = DIV_ROUND_UP(no, 2) * 2;
vco_khz = freq_khz * no;
if (ext_div)
vco_khz *= *ext_div;
if (vco_khz < VCO_MIN_KHZ || vco_khz > VCO_MAX_KHZ || no > max_no) {
printf("%s: Cannot find out a supported VCO for Frequency (%luHz).\n",
__func__, freq_hz);
return -1;
}
div->no = no;
best_diff_khz = vco_khz;
for (nr = 1; nr < max_nr && best_diff_khz; nr++) {
fref_khz = ref_khz / nr;
if (fref_khz < FREF_MIN_KHZ)
break;
if (fref_khz > FREF_MAX_KHZ)
continue;
nf = vco_khz / fref_khz;
if (nf >= max_nf)
continue;
diff_khz = vco_khz - nf * fref_khz;
if (nf + 1 < max_nf && diff_khz > fref_khz / 2) {
nf++;
diff_khz = fref_khz - diff_khz;
}
if (diff_khz >= best_diff_khz)
continue;
best_diff_khz = diff_khz;
div->nr = nr;
div->nf = nf;
}
if (best_diff_khz > 4 * 1000) {
printf("%s: Failed to match output frequency %lu, difference is %u Hz, exceed 4MHZ\n",
__func__, freq_hz, best_diff_khz * 1000);
return -EINVAL;
}
return 0;
}
static int rockchip_mac_set_clk(struct rk3288_cru *cru,
int periph, uint freq)
{
/* Assuming mac_clk is fed by an external clock */
rk_clrsetreg(&cru->cru_clksel_con[21],
RMII_EXTCLK_MASK << RMII_EXTCLK_SHIFT,
RMII_EXTCLK_SELECT_EXT_CLK << RMII_EXTCLK_SHIFT);
return 0;
}
static int rockchip_vop_set_clk(struct rk3288_cru *cru, struct rk3288_grf *grf,
int periph, unsigned int rate_hz)
{
struct pll_div npll_config = {0};
u32 lcdc_div;
int ret;
ret = pll_para_config(rate_hz, &npll_config, &lcdc_div);
if (ret)
return ret;
rk_clrsetreg(&cru->cru_mode_con, NPLL_MODE_MASK << NPLL_MODE_SHIFT,
NPLL_MODE_SLOW << NPLL_MODE_SHIFT);
rkclk_set_pll(cru, CLK_NEW, &npll_config);
/* waiting for pll lock */
while (1) {
if (readl(&grf->soc_status[1]) & SOCSTS_NPLL_LOCK)
break;
udelay(1);
}
rk_clrsetreg(&cru->cru_mode_con, NPLL_MODE_MASK << NPLL_MODE_SHIFT,
NPLL_MODE_NORMAL << NPLL_MODE_SHIFT);
/* vop dclk source clk: npll,dclk_div: 1 */
switch (periph) {
case DCLK_VOP0:
rk_clrsetreg(&cru->cru_clksel_con[27], 0xff << 8 | 3 << 0,
(lcdc_div - 1) << 8 | 2 << 0);
break;
case DCLK_VOP1:
rk_clrsetreg(&cru->cru_clksel_con[29], 0xff << 8 | 3 << 6,
(lcdc_div - 1) << 8 | 2 << 6);
break;
}
return 0;
}
#endif
#ifdef CONFIG_SPL_BUILD
static void rkclk_init(struct rk3288_cru *cru, struct rk3288_grf *grf)
{
u32 aclk_div;
u32 hclk_div;
u32 pclk_div;
/* pll enter slow-mode */
rk_clrsetreg(&cru->cru_mode_con,
GPLL_MODE_MASK << GPLL_MODE_SHIFT |
CPLL_MODE_MASK << CPLL_MODE_SHIFT,
GPLL_MODE_SLOW << GPLL_MODE_SHIFT |
CPLL_MODE_SLOW << CPLL_MODE_SHIFT);
/* init pll */
rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);
rkclk_set_pll(cru, CLK_CODEC, &cpll_init_cfg);
/* waiting for pll lock */
while ((readl(&grf->soc_status[1]) &
(SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) !=
(SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK))
udelay(1);
/*
* pd_bus clock pll source selection and
* set up dependent divisors for PCLK/HCLK and ACLK clocks.
*/
aclk_div = GPLL_HZ / PD_BUS_ACLK_HZ - 1;
assert((aclk_div + 1) * PD_BUS_ACLK_HZ == GPLL_HZ && aclk_div < 0x1f);
hclk_div = PD_BUS_ACLK_HZ / PD_BUS_HCLK_HZ - 1;
assert((hclk_div + 1) * PD_BUS_HCLK_HZ ==
PD_BUS_ACLK_HZ && (hclk_div < 0x4) && (hclk_div != 0x2));
pclk_div = PD_BUS_ACLK_HZ / PD_BUS_PCLK_HZ - 1;
assert((pclk_div + 1) * PD_BUS_PCLK_HZ ==
PD_BUS_ACLK_HZ && pclk_div < 0x7);
rk_clrsetreg(&cru->cru_clksel_con[1],
PD_BUS_PCLK_DIV_MASK << PD_BUS_PCLK_DIV_SHIFT |
PD_BUS_HCLK_DIV_MASK << PD_BUS_HCLK_DIV_SHIFT |
PD_BUS_ACLK_DIV0_MASK << PD_BUS_ACLK_DIV0_SHIFT |
PD_BUS_ACLK_DIV1_MASK << PD_BUS_ACLK_DIV1_SHIFT,
pclk_div << PD_BUS_PCLK_DIV_SHIFT |
hclk_div << PD_BUS_HCLK_DIV_SHIFT |
aclk_div << PD_BUS_ACLK_DIV0_SHIFT |
0 << 0);
/*
* peri clock pll source selection and
* set up dependent divisors for PCLK/HCLK and ACLK clocks.
*/
aclk_div = GPLL_HZ / PERI_ACLK_HZ - 1;
assert((aclk_div + 1) * PERI_ACLK_HZ == GPLL_HZ && aclk_div < 0x1f);
hclk_div = log2(PERI_ACLK_HZ / PERI_HCLK_HZ);
assert((1 << hclk_div) * PERI_HCLK_HZ ==
PERI_ACLK_HZ && (hclk_div < 0x4));
pclk_div = log2(PERI_ACLK_HZ / PERI_PCLK_HZ);
assert((1 << pclk_div) * PERI_PCLK_HZ ==
PERI_ACLK_HZ && (pclk_div < 0x4));
rk_clrsetreg(&cru->cru_clksel_con[10],
PERI_PCLK_DIV_MASK << PERI_PCLK_DIV_SHIFT |
PERI_HCLK_DIV_MASK << PERI_HCLK_DIV_SHIFT |
PERI_ACLK_DIV_MASK << PERI_ACLK_DIV_SHIFT,
PERI_SEL_GPLL << PERI_SEL_PLL_SHIFT |
pclk_div << PERI_PCLK_DIV_SHIFT |
hclk_div << PERI_HCLK_DIV_SHIFT |
aclk_div << PERI_ACLK_DIV_SHIFT);
/* PLL enter normal-mode */
rk_clrsetreg(&cru->cru_mode_con,
GPLL_MODE_MASK << GPLL_MODE_SHIFT |
CPLL_MODE_MASK << CPLL_MODE_SHIFT,
GPLL_MODE_NORMAL << GPLL_MODE_SHIFT |
CPLL_MODE_NORMAL << CPLL_MODE_SHIFT);
}
#endif
void rkclk_configure_cpu(struct rk3288_cru *cru, struct rk3288_grf *grf)
{
/* pll enter slow-mode */
rk_clrsetreg(&cru->cru_mode_con,
APLL_MODE_MASK << APLL_MODE_SHIFT,
APLL_MODE_SLOW << APLL_MODE_SHIFT);
rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg);
/* waiting for pll lock */
while (!(readl(&grf->soc_status[1]) & SOCSTS_APLL_LOCK))
udelay(1);
/*
* core clock pll source selection and
* set up dependent divisors for MPAXI/M0AXI and ARM clocks.
* core clock select apll, apll clk = 1800MHz
* arm clk = 1800MHz, mpclk = 450MHz, m0clk = 900MHz
*/
rk_clrsetreg(&cru->cru_clksel_con[0],
CORE_SEL_PLL_MASK << CORE_SEL_PLL_SHIFT |
A17_DIV_MASK << A17_DIV_SHIFT |
MP_DIV_MASK << MP_DIV_SHIFT |
M0_DIV_MASK << M0_DIV_SHIFT,
0 << A17_DIV_SHIFT |
3 << MP_DIV_SHIFT |
1 << M0_DIV_SHIFT);
/*
* set up dependent divisors for L2RAM/ATCLK and PCLK clocks.
* l2ramclk = 900MHz, atclk = 450MHz, pclk_dbg = 450MHz
*/
rk_clrsetreg(&cru->cru_clksel_con[37],
CLK_L2RAM_DIV_MASK << CLK_L2RAM_DIV_SHIFT |
ATCLK_CORE_DIV_CON_MASK << ATCLK_CORE_DIV_CON_SHIFT |
PCLK_CORE_DBG_DIV_MASK >> PCLK_CORE_DBG_DIV_SHIFT,
1 << CLK_L2RAM_DIV_SHIFT |
3 << ATCLK_CORE_DIV_CON_SHIFT |
3 << PCLK_CORE_DBG_DIV_SHIFT);
/* PLL enter normal-mode */
rk_clrsetreg(&cru->cru_mode_con,
APLL_MODE_MASK << APLL_MODE_SHIFT,
APLL_MODE_NORMAL << APLL_MODE_SHIFT);
}
/* Get pll rate by id */
static uint32_t rkclk_pll_get_rate(struct rk3288_cru *cru,
enum rk_clk_id clk_id)
{
uint32_t nr, no, nf;
uint32_t con;
int pll_id = rk_pll_id(clk_id);
struct rk3288_pll *pll = &cru->pll[pll_id];
static u8 clk_shift[CLK_COUNT] = {
0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, CPLL_MODE_SHIFT,
GPLL_MODE_SHIFT, NPLL_MODE_SHIFT
};
uint shift;
con = readl(&cru->cru_mode_con);
shift = clk_shift[clk_id];
switch ((con >> shift) & APLL_MODE_MASK) {
case APLL_MODE_SLOW:
return OSC_HZ;
case APLL_MODE_NORMAL:
/* normal mode */
con = readl(&pll->con0);
no = ((con >> CLKOD_SHIFT) & CLKOD_MASK) + 1;
nr = ((con >> CLKR_SHIFT) & CLKR_MASK) + 1;
con = readl(&pll->con1);
nf = ((con >> CLKF_SHIFT) & CLKF_MASK) + 1;
return (24 * nf / (nr * no)) * 1000000;
case APLL_MODE_DEEP:
default:
return 32768;
}
}
static ulong rockchip_mmc_get_clk(struct rk3288_cru *cru, uint gclk_rate,
int periph)
{
uint src_rate;
uint div, mux;
u32 con;
switch (periph) {
case HCLK_EMMC:
con = readl(&cru->cru_clksel_con[12]);
mux = (con >> EMMC_PLL_SHIFT) & EMMC_PLL_MASK;
div = (con >> EMMC_DIV_SHIFT) & EMMC_DIV_MASK;
break;
case HCLK_SDMMC:
con = readl(&cru->cru_clksel_con[11]);
mux = (con >> MMC0_PLL_SHIFT) & MMC0_PLL_MASK;
div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK;
break;
case HCLK_SDIO0:
con = readl(&cru->cru_clksel_con[12]);
mux = (con >> SDIO0_PLL_SHIFT) & SDIO0_PLL_MASK;
div = (con >> SDIO0_DIV_SHIFT) & SDIO0_DIV_MASK;
break;
default:
return -EINVAL;
}
src_rate = mux == EMMC_PLL_SELECT_24MHZ ? OSC_HZ : gclk_rate;
return DIV_TO_RATE(src_rate, div);
}
static ulong rockchip_mmc_set_clk(struct rk3288_cru *cru, uint gclk_rate,
int periph, uint freq)
{
int src_clk_div;
int mux;
debug("%s: gclk_rate=%u\n", __func__, gclk_rate);
src_clk_div = RATE_TO_DIV(gclk_rate, freq);
if (src_clk_div > 0x3f) {
src_clk_div = RATE_TO_DIV(OSC_HZ, freq);
mux = EMMC_PLL_SELECT_24MHZ;
assert((int)EMMC_PLL_SELECT_24MHZ ==
(int)MMC0_PLL_SELECT_24MHZ);
} else {
mux = EMMC_PLL_SELECT_GENERAL;
assert((int)EMMC_PLL_SELECT_GENERAL ==
(int)MMC0_PLL_SELECT_GENERAL);
}
switch (periph) {
case HCLK_EMMC:
rk_clrsetreg(&cru->cru_clksel_con[12],
EMMC_PLL_MASK << EMMC_PLL_SHIFT |
EMMC_DIV_MASK << EMMC_DIV_SHIFT,
mux << EMMC_PLL_SHIFT |
(src_clk_div - 1) << EMMC_DIV_SHIFT);
break;
case HCLK_SDMMC:
rk_clrsetreg(&cru->cru_clksel_con[11],
MMC0_PLL_MASK << MMC0_PLL_SHIFT |
MMC0_DIV_MASK << MMC0_DIV_SHIFT,
mux << MMC0_PLL_SHIFT |
(src_clk_div - 1) << MMC0_DIV_SHIFT);
break;
case HCLK_SDIO0:
rk_clrsetreg(&cru->cru_clksel_con[12],
SDIO0_PLL_MASK << SDIO0_PLL_SHIFT |
SDIO0_DIV_MASK << SDIO0_DIV_SHIFT,
mux << SDIO0_PLL_SHIFT |
(src_clk_div - 1) << SDIO0_DIV_SHIFT);
break;
default:
return -EINVAL;
}
return rockchip_mmc_get_clk(cru, gclk_rate, periph);
}
static ulong rockchip_spi_get_clk(struct rk3288_cru *cru, uint gclk_rate,
int periph)
{
uint div, mux;
u32 con;
switch (periph) {
case SCLK_SPI0:
con = readl(&cru->cru_clksel_con[25]);
mux = (con >> SPI0_PLL_SHIFT) & SPI0_PLL_MASK;
div = (con >> SPI0_DIV_SHIFT) & SPI0_DIV_MASK;
break;
case SCLK_SPI1:
con = readl(&cru->cru_clksel_con[25]);
mux = (con >> SPI1_PLL_SHIFT) & SPI1_PLL_MASK;
div = (con >> SPI1_DIV_SHIFT) & SPI1_DIV_MASK;
break;
case SCLK_SPI2:
con = readl(&cru->cru_clksel_con[39]);
mux = (con >> SPI2_PLL_SHIFT) & SPI2_PLL_MASK;
div = (con >> SPI2_DIV_SHIFT) & SPI2_DIV_MASK;
break;
default:
return -EINVAL;
}
assert(mux == SPI0_PLL_SELECT_GENERAL);
return DIV_TO_RATE(gclk_rate, div);
}
static ulong rockchip_spi_set_clk(struct rk3288_cru *cru, uint gclk_rate,
int periph, uint freq)
{
int src_clk_div;
debug("%s: clk_general_rate=%u\n", __func__, gclk_rate);
src_clk_div = RATE_TO_DIV(gclk_rate, freq);
switch (periph) {
case SCLK_SPI0:
rk_clrsetreg(&cru->cru_clksel_con[25],
SPI0_PLL_MASK << SPI0_PLL_SHIFT |
SPI0_DIV_MASK << SPI0_DIV_SHIFT,
SPI0_PLL_SELECT_GENERAL << SPI0_PLL_SHIFT |
src_clk_div << SPI0_DIV_SHIFT);
break;
case SCLK_SPI1:
rk_clrsetreg(&cru->cru_clksel_con[25],
SPI1_PLL_MASK << SPI1_PLL_SHIFT |
SPI1_DIV_MASK << SPI1_DIV_SHIFT,
SPI1_PLL_SELECT_GENERAL << SPI1_PLL_SHIFT |
src_clk_div << SPI1_DIV_SHIFT);
break;
case SCLK_SPI2:
rk_clrsetreg(&cru->cru_clksel_con[39],
SPI2_PLL_MASK << SPI2_PLL_SHIFT |
SPI2_DIV_MASK << SPI2_DIV_SHIFT,
SPI2_PLL_SELECT_GENERAL << SPI2_PLL_SHIFT |
src_clk_div << SPI2_DIV_SHIFT);
break;
default:
return -EINVAL;
}
return rockchip_spi_get_clk(cru, gclk_rate, periph);
}
static ulong rk3288_clk_get_rate(struct clk *clk)
{
struct rk3288_clk_priv *priv = dev_get_priv(clk->dev);
ulong new_rate, gclk_rate;
gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL);
switch (clk->id) {
case 0 ... 63:
new_rate = rkclk_pll_get_rate(priv->cru, clk->id);
break;
case HCLK_EMMC:
case HCLK_SDMMC:
case HCLK_SDIO0:
new_rate = rockchip_mmc_get_clk(priv->cru, gclk_rate, clk->id);
break;
case SCLK_SPI0:
case SCLK_SPI1:
case SCLK_SPI2:
new_rate = rockchip_spi_get_clk(priv->cru, gclk_rate, clk->id);
break;
case PCLK_I2C0:
case PCLK_I2C1:
case PCLK_I2C2:
case PCLK_I2C3:
case PCLK_I2C4:
case PCLK_I2C5:
return gclk_rate;
default:
return -ENOENT;
}
return new_rate;
}
static ulong rk3288_clk_set_rate(struct clk *clk, ulong rate)
{
struct rk3288_clk_priv *priv = dev_get_priv(clk->dev);
struct rk3288_cru *cru = priv->cru;
ulong new_rate, gclk_rate;
gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL);
switch (clk->id) {
case CLK_DDR:
new_rate = rkclk_configure_ddr(priv->cru, priv->grf, rate);
break;
case HCLK_EMMC:
case HCLK_SDMMC:
case HCLK_SDIO0:
new_rate = rockchip_mmc_set_clk(cru, gclk_rate, clk->id, rate);
break;
case SCLK_SPI0:
case SCLK_SPI1:
case SCLK_SPI2:
new_rate = rockchip_spi_set_clk(cru, gclk_rate, clk->id, rate);
break;
#ifndef CONFIG_SPL_BUILD
case SCLK_MAC:
new_rate = rockchip_mac_set_clk(priv->cru, clk->id, rate);
break;
case DCLK_VOP0:
case DCLK_VOP1:
new_rate = rockchip_vop_set_clk(cru, priv->grf, clk->id, rate);
break;
case SCLK_EDP_24M:
/* clk_edp_24M source: 24M */
rk_setreg(&cru->cru_clksel_con[28], 1 << 15);
/* rst edp */
rk_setreg(&cru->cru_clksel_con[6], 1 << 15);
udelay(1);
rk_clrreg(&cru->cru_clksel_con[6], 1 << 15);
new_rate = rate;
break;
case ACLK_VOP0:
case ACLK_VOP1: {
u32 div;
/* vop aclk source clk: cpll */
div = CPLL_HZ / rate;
assert((div - 1 < 64) && (div * rate == CPLL_HZ));
switch (clk->id) {
case ACLK_VOP0:
rk_clrsetreg(&cru->cru_clksel_con[31],
3 << 6 | 0x1f << 0,
0 << 6 | (div - 1) << 0);
break;
case ACLK_VOP1:
rk_clrsetreg(&cru->cru_clksel_con[31],
3 << 14 | 0x1f << 8,
0 << 14 | (div - 1) << 8);
break;
}
new_rate = rate;
break;
}
case PCLK_HDMI_CTRL:
/* enable pclk hdmi ctrl */
rk_clrreg(&cru->cru_clkgate_con[16], 1 << 9);
/* software reset hdmi */
rk_setreg(&cru->cru_clkgate_con[7], 1 << 9);
udelay(1);
rk_clrreg(&cru->cru_clkgate_con[7], 1 << 9);
new_rate = rate;
break;
#endif
default:
return -ENOENT;
}
return new_rate;
}
static struct clk_ops rk3288_clk_ops = {
.get_rate = rk3288_clk_get_rate,
.set_rate = rk3288_clk_set_rate,
};
static int rk3288_clk_probe(struct udevice *dev)
{
struct rk3288_clk_priv *priv = dev_get_priv(dev);
priv->cru = (struct rk3288_cru *)dev_get_addr(dev);
priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
#ifdef CONFIG_SPL_BUILD
rkclk_init(priv->cru, priv->grf);
#endif
return 0;
}
static int rk3288_clk_bind(struct udevice *dev)
{
int ret;
/* The reset driver does not have a device node, so bind it here */
ret = device_bind_driver(gd->dm_root, "rk3288_sysreset", "reset", &dev);
if (ret)
debug("Warning: No RK3288 reset driver: ret=%d\n", ret);
return 0;
}
static const struct udevice_id rk3288_clk_ids[] = {
{ .compatible = "rockchip,rk3288-cru" },
{ }
};
U_BOOT_DRIVER(clk_rk3288) = {
.name = "clk_rk3288",
.id = UCLASS_CLK,
.of_match = rk3288_clk_ids,
.priv_auto_alloc_size = sizeof(struct rk3288_clk_priv),
.ops = &rk3288_clk_ops,
.bind = rk3288_clk_bind,
.probe = rk3288_clk_probe,
};

View File

@@ -0,0 +1,107 @@
/*
* (C) Copyright 2015 Google, Inc
*
* SPDX-License-Identifier: GPL-2.0
*/
#include <common.h>
#include <clk-uclass.h>
#include <dm.h>
#include <errno.h>
#include <asm/clk.h>
struct sandbox_clk_priv {
ulong rate[SANDBOX_CLK_ID_COUNT];
bool enabled[SANDBOX_CLK_ID_COUNT];
};
static ulong sandbox_clk_get_rate(struct clk *clk)
{
struct sandbox_clk_priv *priv = dev_get_priv(clk->dev);
if (clk->id >= SANDBOX_CLK_ID_COUNT)
return -EINVAL;
return priv->rate[clk->id];
}
static ulong sandbox_clk_set_rate(struct clk *clk, ulong rate)
{
struct sandbox_clk_priv *priv = dev_get_priv(clk->dev);
ulong old_rate;
if (clk->id >= SANDBOX_CLK_ID_COUNT)
return -EINVAL;
if (!rate)
return -EINVAL;
old_rate = priv->rate[clk->id];
priv->rate[clk->id] = rate;
return old_rate;
}
static int sandbox_clk_enable(struct clk *clk)
{
struct sandbox_clk_priv *priv = dev_get_priv(clk->dev);
if (clk->id >= SANDBOX_CLK_ID_COUNT)
return -EINVAL;
priv->enabled[clk->id] = true;
return 0;
}
static int sandbox_clk_disable(struct clk *clk)
{
struct sandbox_clk_priv *priv = dev_get_priv(clk->dev);
if (clk->id >= SANDBOX_CLK_ID_COUNT)
return -EINVAL;
priv->enabled[clk->id] = false;
return 0;
}
static struct clk_ops sandbox_clk_ops = {
.get_rate = sandbox_clk_get_rate,
.set_rate = sandbox_clk_set_rate,
.enable = sandbox_clk_enable,
.disable = sandbox_clk_disable,
};
static const struct udevice_id sandbox_clk_ids[] = {
{ .compatible = "sandbox,clk" },
{ }
};
U_BOOT_DRIVER(clk_sandbox) = {
.name = "clk_sandbox",
.id = UCLASS_CLK,
.of_match = sandbox_clk_ids,
.ops = &sandbox_clk_ops,
.priv_auto_alloc_size = sizeof(struct sandbox_clk_priv),
};
ulong sandbox_clk_query_rate(struct udevice *dev, int id)
{
struct sandbox_clk_priv *priv = dev_get_priv(dev);
if (id < 0 || id >= SANDBOX_CLK_ID_COUNT)
return -EINVAL;
return priv->rate[id];
}
int sandbox_clk_query_enable(struct udevice *dev, int id)
{
struct sandbox_clk_priv *priv = dev_get_priv(dev);
if (id < 0 || id >= SANDBOX_CLK_ID_COUNT)
return -EINVAL;
return priv->enabled[id];
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2016, NVIDIA CORPORATION.
*
* SPDX-License-Identifier: GPL-2.0
*/
#include <common.h>
#include <dm.h>
#include <clk.h>
#include <asm/clk.h>
struct sandbox_clk_test {
struct clk clks[SANDBOX_CLK_TEST_ID_COUNT];
};
static const char * const sandbox_clk_test_names[] = {
[SANDBOX_CLK_TEST_ID_FIXED] = "fixed",
[SANDBOX_CLK_TEST_ID_SPI] = "spi",
[SANDBOX_CLK_TEST_ID_I2C] = "i2c",
};
int sandbox_clk_test_get(struct udevice *dev)
{
struct sandbox_clk_test *sbct = dev_get_priv(dev);
int i, ret;
for (i = 0; i < SANDBOX_CLK_TEST_ID_COUNT; i++) {
ret = clk_get_by_name(dev, sandbox_clk_test_names[i],
&sbct->clks[i]);
if (ret)
return ret;
}
return 0;
}
ulong sandbox_clk_test_get_rate(struct udevice *dev, int id)
{
struct sandbox_clk_test *sbct = dev_get_priv(dev);
if (id < 0 || id >= SANDBOX_CLK_TEST_ID_COUNT)
return -EINVAL;
return clk_get_rate(&sbct->clks[id]);
}
ulong sandbox_clk_test_set_rate(struct udevice *dev, int id, ulong rate)
{
struct sandbox_clk_test *sbct = dev_get_priv(dev);
if (id < 0 || id >= SANDBOX_CLK_TEST_ID_COUNT)
return -EINVAL;
return clk_set_rate(&sbct->clks[id], rate);
}
int sandbox_clk_test_enable(struct udevice *dev, int id)
{
struct sandbox_clk_test *sbct = dev_get_priv(dev);
if (id < 0 || id >= SANDBOX_CLK_TEST_ID_COUNT)
return -EINVAL;
return clk_enable(&sbct->clks[id]);
}
int sandbox_clk_test_disable(struct udevice *dev, int id)
{
struct sandbox_clk_test *sbct = dev_get_priv(dev);
if (id < 0 || id >= SANDBOX_CLK_TEST_ID_COUNT)
return -EINVAL;
return clk_disable(&sbct->clks[id]);
}
int sandbox_clk_test_free(struct udevice *dev)
{
struct sandbox_clk_test *sbct = dev_get_priv(dev);
int i, ret;
for (i = 0; i < SANDBOX_CLK_TEST_ID_COUNT; i++) {
ret = clk_free(&sbct->clks[i]);
if (ret)
return ret;
}
return 0;
}
static const struct udevice_id sandbox_clk_test_ids[] = {
{ .compatible = "sandbox,clk-test" },
{ }
};
U_BOOT_DRIVER(sandbox_clk_test) = {
.name = "sandbox_clk_test",
.id = UCLASS_MISC,
.of_match = sandbox_clk_test_ids,
.priv_auto_alloc_size = sizeof(struct sandbox_clk_test),
};

View File

@@ -0,0 +1,18 @@
config CLK_EXYNOS
bool
select CLK
help
This enables support for common clock driver API on Samsung
Exynos SoCs.
menu "Clock drivers for Exynos SoCs"
depends on CLK_EXYNOS
config CLK_EXYNOS7420
bool "Clock driver for Samsung's Exynos7420 SoC"
default y
help
This enables common clock driver support for platforms based
on Samsung Exynos7420 SoC.
endmenu

View File

@@ -0,0 +1,9 @@
#
# Copyright (C) 2016 Samsung Electronics
# Thomas Abraham <thomas.ab@samsung.com>
#
# SPDX-License-Identifier: GPL-2.0+
#
obj-y += clk-pll.o
obj-$(CONFIG_CLK_EXYNOS7420) += clk-exynos7420.o

View File

@@ -0,0 +1,236 @@
/*
* Samsung Exynos7420 clock driver.
* Copyright (C) 2016 Samsung Electronics
* Thomas Abraham <thomas.ab@samsung.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <dm.h>
#include <errno.h>
#include <clk-uclass.h>
#include <asm/io.h>
#include <dt-bindings/clock/exynos7420-clk.h>
#include "clk-pll.h"
DECLARE_GLOBAL_DATA_PTR;
#define DIVIDER(reg, shift, mask) \
(((readl(reg) >> shift) & mask) + 1)
/* CMU TOPC block device structure */
struct exynos7420_clk_cmu_topc {
unsigned int rsvd1[68];
unsigned int bus0_pll_con[2];
unsigned int rsvd2[2];
unsigned int bus1_pll_con[2];
unsigned int rsvd3[54];
unsigned int mux_sel[6];
unsigned int rsvd4[250];
unsigned int div[4];
};
/* CMU TOP0 block device structure */
struct exynos7420_clk_cmu_top0 {
unsigned int rsvd0[128];
unsigned int mux_sel[7];
unsigned int rsvd1[261];
unsigned int div_peric[5];
};
/**
* struct exynos7420_clk_topc_priv - private data for CMU topc clock driver.
*
* @topc: base address of the memory mapped CMU TOPC controller.
* @fin_freq: frequency of the Oscillator clock.
* @sclk_bus0_pll_a: frequency of sclk_bus0_pll_a clock.
* @sclk_bus1_pll_a: frequency of sclk_bus1_pll_a clock.
*/
struct exynos7420_clk_topc_priv {
struct exynos7420_clk_cmu_topc *topc;
unsigned long fin_freq;
unsigned long sclk_bus0_pll_a;
unsigned long sclk_bus1_pll_a;
};
/**
* struct exynos7420_clk_top0_priv - private data for CMU top0 clock driver.
*
* @top0: base address of the memory mapped CMU TOP0 controller.
* @mout_top0_bus0_pll_half: frequency of mout_top0_bus0_pll_half clock
* @sclk_uart2: frequency of sclk_uart2 clock.
*/
struct exynos7420_clk_top0_priv {
struct exynos7420_clk_cmu_top0 *top0;
unsigned long mout_top0_bus0_pll_half;
unsigned long sclk_uart2;
};
static ulong exynos7420_topc_get_rate(struct clk *clk)
{
struct exynos7420_clk_topc_priv *priv = dev_get_priv(clk->dev);
switch (clk->id) {
case DOUT_SCLK_BUS0_PLL:
case SCLK_BUS0_PLL_A:
case SCLK_BUS0_PLL_B:
return priv->sclk_bus0_pll_a;
case DOUT_SCLK_BUS1_PLL:
case SCLK_BUS1_PLL_A:
case SCLK_BUS1_PLL_B:
return priv->sclk_bus1_pll_a;
default:
return 0;
}
}
static struct clk_ops exynos7420_clk_topc_ops = {
.get_rate = exynos7420_topc_get_rate,
};
static int exynos7420_clk_topc_probe(struct udevice *dev)
{
struct exynos7420_clk_topc_priv *priv = dev_get_priv(dev);
struct exynos7420_clk_cmu_topc *topc;
struct clk in_clk;
unsigned long rate;
fdt_addr_t base;
int ret;
base = dev_get_addr(dev);
if (base == FDT_ADDR_T_NONE)
return -EINVAL;
topc = (struct exynos7420_clk_cmu_topc *)base;
priv->topc = topc;
ret = clk_get_by_index(dev, 0, &in_clk);
if (ret >= 0)
priv->fin_freq = clk_get_rate(&in_clk);
rate = pll145x_get_rate(&topc->bus0_pll_con[0], priv->fin_freq);
if (readl(&topc->mux_sel[1]) & (1 << 16))
rate >>= 1;
rate /= DIVIDER(&topc->div[3], 0, 0xf);
priv->sclk_bus0_pll_a = rate;
rate = pll145x_get_rate(&topc->bus1_pll_con[0], priv->fin_freq) /
DIVIDER(&topc->div[3], 8, 0xf);
priv->sclk_bus1_pll_a = rate;
return 0;
}
static ulong exynos7420_top0_get_rate(struct clk *clk)
{
struct exynos7420_clk_top0_priv *priv = dev_get_priv(clk->dev);
struct exynos7420_clk_cmu_top0 *top0 = priv->top0;
switch (clk->id) {
case CLK_SCLK_UART2:
return priv->mout_top0_bus0_pll_half /
DIVIDER(&top0->div_peric[3], 8, 0xf);
default:
return 0;
}
}
static struct clk_ops exynos7420_clk_top0_ops = {
.get_rate = exynos7420_top0_get_rate,
};
static int exynos7420_clk_top0_probe(struct udevice *dev)
{
struct exynos7420_clk_top0_priv *priv;
struct exynos7420_clk_cmu_top0 *top0;
struct clk in_clk;
fdt_addr_t base;
int ret;
priv = dev_get_priv(dev);
if (!priv)
return -EINVAL;
base = dev_get_addr(dev);
if (base == FDT_ADDR_T_NONE)
return -EINVAL;
top0 = (struct exynos7420_clk_cmu_top0 *)base;
priv->top0 = top0;
ret = clk_get_by_index(dev, 1, &in_clk);
if (ret >= 0) {
priv->mout_top0_bus0_pll_half =
clk_get_rate(&in_clk);
if (readl(&top0->mux_sel[1]) & (1 << 16))
priv->mout_top0_bus0_pll_half >>= 1;
}
return 0;
}
static ulong exynos7420_peric1_get_rate(struct clk *clk)
{
struct clk in_clk;
unsigned int ret;
unsigned long freq = 0;
switch (clk->id) {
case SCLK_UART2:
ret = clk_get_by_index(clk->dev, 3, &in_clk);
if (ret < 0)
return ret;
freq = clk_get_rate(&in_clk);
break;
}
return freq;
}
static struct clk_ops exynos7420_clk_peric1_ops = {
.get_rate = exynos7420_peric1_get_rate,
};
static const struct udevice_id exynos7420_clk_topc_compat[] = {
{ .compatible = "samsung,exynos7-clock-topc" },
{ }
};
U_BOOT_DRIVER(exynos7420_clk_topc) = {
.name = "exynos7420-clock-topc",
.id = UCLASS_CLK,
.of_match = exynos7420_clk_topc_compat,
.probe = exynos7420_clk_topc_probe,
.priv_auto_alloc_size = sizeof(struct exynos7420_clk_topc_priv),
.ops = &exynos7420_clk_topc_ops,
.flags = DM_FLAG_PRE_RELOC,
};
static const struct udevice_id exynos7420_clk_top0_compat[] = {
{ .compatible = "samsung,exynos7-clock-top0" },
{ }
};
U_BOOT_DRIVER(exynos7420_clk_top0) = {
.name = "exynos7420-clock-top0",
.id = UCLASS_CLK,
.of_match = exynos7420_clk_top0_compat,
.probe = exynos7420_clk_top0_probe,
.priv_auto_alloc_size = sizeof(struct exynos7420_clk_top0_priv),
.ops = &exynos7420_clk_top0_ops,
.flags = DM_FLAG_PRE_RELOC,
};
static const struct udevice_id exynos7420_clk_peric1_compat[] = {
{ .compatible = "samsung,exynos7-clock-peric1" },
{ }
};
U_BOOT_DRIVER(exynos7420_clk_peric1) = {
.name = "exynos7420-clock-peric1",
.id = UCLASS_CLK,
.of_match = exynos7420_clk_peric1_compat,
.ops = &exynos7420_clk_peric1_ops,
.flags = DM_FLAG_PRE_RELOC,
};

View File

@@ -0,0 +1,33 @@
/*
* Exynos PLL helper functions for clock drivers.
* Copyright (C) 2016 Samsung Electronics
* Thomas Abraham <thomas.ab@samsung.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <asm/io.h>
#include <div64.h>
#define PLL145X_MDIV_SHIFT 16
#define PLL145X_MDIV_MASK 0x3ff
#define PLL145X_PDIV_SHIFT 8
#define PLL145X_PDIV_MASK 0x3f
#define PLL145X_SDIV_SHIFT 0
#define PLL145X_SDIV_MASK 0x7
unsigned long pll145x_get_rate(unsigned int *con1, unsigned long fin_freq)
{
unsigned long pll_con1 = readl(con1);
unsigned long mdiv, sdiv, pdiv;
uint64_t fvco = fin_freq;
mdiv = (pll_con1 >> PLL145X_MDIV_SHIFT) & PLL145X_MDIV_MASK;
pdiv = (pll_con1 >> PLL145X_PDIV_SHIFT) & PLL145X_PDIV_MASK;
sdiv = (pll_con1 >> PLL145X_SDIV_SHIFT) & PLL145X_SDIV_MASK;
fvco *= mdiv;
do_div(fvco, (pdiv << sdiv));
return (unsigned long)fvco;
}

View File

@@ -0,0 +1,9 @@
/*
* Exynos PLL helper functions for clock drivers.
* Copyright (C) 2016 Samsung Electronics
* Thomas Abraham <thomas.ab@samsung.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
unsigned long pll145x_get_rate(unsigned int *con1, unsigned long fin_freq);

View File

@@ -0,0 +1,13 @@
config CLK_UNIPHIER
bool
select CLK
select SPL_CLK
menu "Clock drivers for UniPhier SoCs"
depends on CLK_UNIPHIER
config CLK_UNIPHIER_MIO
bool "Clock driver for UniPhier Media I/O block"
default y
endmenu

View File

@@ -0,0 +1,3 @@
obj-y += clk-uniphier-core.o
obj-$(CONFIG_CLK_UNIPHIER_MIO) += clk-uniphier-mio.o

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2016 Masahiro Yamada <yamada.masahiro@socionext.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <mapmem.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/sizes.h>
#include <clk-uclass.h>
#include <dm/device.h>
#include "clk-uniphier.h"
static int uniphier_clk_enable(struct clk *clk)
{
struct uniphier_clk_priv *priv = dev_get_priv(clk->dev);
struct uniphier_clk_gate_data *gate = priv->socdata->gate;
unsigned int nr_gate = priv->socdata->nr_gate;
void __iomem *reg;
u32 mask, data, tmp;
int i;
for (i = 0; i < nr_gate; i++) {
if (gate[i].index != clk->id)
continue;
reg = priv->base + gate[i].reg;
mask = gate[i].mask;
data = gate[i].data & mask;
tmp = readl(reg);
tmp &= ~mask;
tmp |= data & mask;
debug("%s: %p: %08x\n", __func__, reg, tmp);
writel(tmp, reg);
}
return 0;
}
static ulong uniphier_clk_get_rate(struct clk *clk)
{
struct uniphier_clk_priv *priv = dev_get_priv(clk->dev);
struct uniphier_clk_rate_data *rdata = priv->socdata->rate;
unsigned int nr_rdata = priv->socdata->nr_rate;
void __iomem *reg;
u32 mask, data;
ulong matched_rate = 0;
int i;
for (i = 0; i < nr_rdata; i++) {
if (rdata[i].index != clk->id)
continue;
if (rdata[i].reg == UNIPHIER_CLK_RATE_IS_FIXED)
return rdata[i].rate;
reg = priv->base + rdata[i].reg;
mask = rdata[i].mask;
data = rdata[i].data & mask;
if ((readl(reg) & mask) == data) {
if (matched_rate && rdata[i].rate != matched_rate) {
printf("failed to get clk rate for insane register values\n");
return -EINVAL;
}
matched_rate = rdata[i].rate;
}
}
debug("%s: rate = %lu\n", __func__, matched_rate);
return matched_rate;
}
static ulong uniphier_clk_set_rate(struct clk *clk, ulong rate)
{
struct uniphier_clk_priv *priv = dev_get_priv(clk->dev);
struct uniphier_clk_rate_data *rdata = priv->socdata->rate;
unsigned int nr_rdata = priv->socdata->nr_rate;
void __iomem *reg;
u32 mask, data, tmp;
ulong best_rate = 0;
int i;
/* first, decide the best match rate */
for (i = 0; i < nr_rdata; i++) {
if (rdata[i].index != clk->id)
continue;
if (rdata[i].reg == UNIPHIER_CLK_RATE_IS_FIXED)
return 0;
if (rdata[i].rate > best_rate && rdata[i].rate <= rate)
best_rate = rdata[i].rate;
}
if (!best_rate)
return -ENODEV;
debug("%s: requested rate = %lu, set rate = %lu\n", __func__,
rate, best_rate);
/* second, really set registers */
for (i = 0; i < nr_rdata; i++) {
if (rdata[i].index != clk->id || rdata[i].rate != best_rate)
continue;
reg = priv->base + rdata[i].reg;
mask = rdata[i].mask;
data = rdata[i].data & mask;
tmp = readl(reg);
tmp &= ~mask;
tmp |= data;
debug("%s: %p: %08x\n", __func__, reg, tmp);
writel(tmp, reg);
}
return best_rate;
}
const struct clk_ops uniphier_clk_ops = {
.enable = uniphier_clk_enable,
.get_rate = uniphier_clk_get_rate,
.set_rate = uniphier_clk_set_rate,
};
int uniphier_clk_probe(struct udevice *dev)
{
struct uniphier_clk_priv *priv = dev_get_priv(dev);
fdt_addr_t addr;
addr = dev_get_addr(dev);
if (addr == FDT_ADDR_T_NONE)
return -EINVAL;
priv->base = map_sysmem(addr, SZ_4K);
if (!priv->base)
return -ENOMEM;
priv->socdata = (void *)dev_get_driver_data(dev);
return 0;
}
int uniphier_clk_remove(struct udevice *dev)
{
struct uniphier_clk_priv *priv = dev_get_priv(dev);
unmap_sysmem(priv->base);
return 0;
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright (C) 2016 Masahiro Yamada <yamada.masahiro@socionext.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <dm/device.h>
#include "clk-uniphier.h"
#define UNIPHIER_MIO_CLK_GATE_SD(ch, idx) \
{ \
.index = (idx), \
.reg = 0x20 + 0x200 * (ch), \
.mask = 0x00000100, \
.data = 0x00000100, \
}, \
{ \
.index = (idx), \
.reg = 0x110 + 0x200 * (ch), \
.mask = 0x00000001, \
.data = 0x00000001, \
}
#define UNIPHIER_MIO_CLK_RATE_SD(ch, idx) \
{ \
.index = (idx), \
.reg = 0x30 + 0x200 * (ch), \
.mask = 0x00031300, \
.data = 0x00000000, \
.rate = 44444444, \
}, \
{ \
.index = (idx), \
.reg = 0x30 + 0x200 * (ch), \
.mask = 0x00031300, \
.data = 0x00010000, \
.rate = 33333333, \
}, \
{ \
.index = (idx), \
.reg = 0x30 + 0x200 * (ch), \
.mask = 0x00031300, \
.data = 0x00020000, \
.rate = 50000000, \
}, \
{ \
.index = (idx), \
.reg = 0x30 + 0x200 * (ch), \
.mask = 0x00031300, \
.data = 0x00020000, \
.rate = 66666666, \
}, \
{ \
.index = (idx), \
.reg = 0x30 + 0x200 * (ch), \
.mask = 0x00031300, \
.data = 0x00001000, \
.rate = 100000000, \
}, \
{ \
.index = (idx), \
.reg = 0x30 + 0x200 * (ch), \
.mask = 0x00031300, \
.data = 0x00001100, \
.rate = 40000000, \
}, \
{ \
.index = (idx), \
.reg = 0x30 + 0x200 * (ch), \
.mask = 0x00031300, \
.data = 0x00001200, \
.rate = 25000000, \
}, \
{ \
.index = (idx), \
.reg = 0x30 + 0x200 * (ch), \
.mask = 0x00031300, \
.data = 0x00001300, \
.rate = 22222222, \
}
#define UNIPHIER_MIO_CLK_GATE_USB(ch, idx) \
{ \
.index = (idx), \
.reg = 0x20 + 0x200 * (ch), \
.mask = 0x30000000, \
.data = 0x30000000, \
}, \
{ \
.index = (idx), \
.reg = 0x110 + 0x200 * (ch), \
.mask = 0x01000000, \
.data = 0x01000000, \
}, \
{ \
.index = (idx), \
.reg = 0x114 + 0x200 * (ch), \
.mask = 0x00000001, \
.data = 0x00000001, \
}
#define UNIPHIER_MIO_CLK_GATE_DMAC(idx) \
{ \
.index = (idx), \
.reg = 0x20, \
.mask = 0x02000000, \
.data = 0x02000000, \
}, \
{ \
.index = (idx), \
.reg = 0x110, \
.mask = 0x00020000, \
.data = 0x00020000, \
}
static struct uniphier_clk_gate_data uniphier_mio_clk_gate[] = {
UNIPHIER_MIO_CLK_GATE_SD(0, 0),
UNIPHIER_MIO_CLK_GATE_SD(1, 1),
UNIPHIER_MIO_CLK_GATE_SD(2, 2), /* for PH1-Pro4 only */
UNIPHIER_MIO_CLK_GATE_USB(0, 3),
UNIPHIER_MIO_CLK_GATE_USB(1, 4),
UNIPHIER_MIO_CLK_GATE_USB(2, 5),
UNIPHIER_MIO_CLK_GATE_DMAC(6),
UNIPHIER_MIO_CLK_GATE_USB(3, 7), /* for PH1-sLD3 only */
};
static struct uniphier_clk_rate_data uniphier_mio_clk_rate[] = {
UNIPHIER_MIO_CLK_RATE_SD(0, 0),
UNIPHIER_MIO_CLK_RATE_SD(1, 1),
UNIPHIER_MIO_CLK_RATE_SD(2, 2), /* for PH1-Pro4 only */
};
static struct uniphier_clk_soc_data uniphier_mio_clk_data = {
.gate = uniphier_mio_clk_gate,
.nr_gate = ARRAY_SIZE(uniphier_mio_clk_gate),
.rate = uniphier_mio_clk_rate,
.nr_rate = ARRAY_SIZE(uniphier_mio_clk_rate),
};
static const struct udevice_id uniphier_mio_clk_match[] = {
{
.compatible = "socionext,ph1-sld3-mioctrl",
.data = (ulong)&uniphier_mio_clk_data,
},
{
.compatible = "socionext,ph1-ld4-mioctrl",
.data = (ulong)&uniphier_mio_clk_data,
},
{
.compatible = "socionext,ph1-pro4-mioctrl",
.data = (ulong)&uniphier_mio_clk_data,
},
{
.compatible = "socionext,ph1-sld8-mioctrl",
.data = (ulong)&uniphier_mio_clk_data,
},
{
.compatible = "socionext,ph1-pro5-mioctrl",
.data = (ulong)&uniphier_mio_clk_data,
},
{
.compatible = "socionext,proxstream2-mioctrl",
.data = (ulong)&uniphier_mio_clk_data,
},
{
.compatible = "socionext,ph1-ld11-mioctrl",
.data = (ulong)&uniphier_mio_clk_data,
},
{
.compatible = "socionext,ph1-ld20-mioctrl",
.data = (ulong)&uniphier_mio_clk_data,
},
{ /* sentinel */ }
};
U_BOOT_DRIVER(uniphier_mio_clk) = {
.name = "uniphier-mio-clk",
.id = UCLASS_CLK,
.of_match = uniphier_mio_clk_match,
.probe = uniphier_clk_probe,
.remove = uniphier_clk_remove,
.priv_auto_alloc_size = sizeof(struct uniphier_clk_priv),
.ops = &uniphier_clk_ops,
};

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2016 Masahiro Yamada <yamada.masahiro@socionext.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef __CLK_UNIPHIER_H__
#define __CLK_UNIPHIER_H__
#include <linux/kernel.h>
struct uniphier_clk_gate_data {
int index;
unsigned int reg;
u32 mask;
u32 data;
};
struct uniphier_clk_rate_data {
int index;
unsigned int reg;
#define UNIPHIER_CLK_RATE_IS_FIXED UINT_MAX
u32 mask;
u32 data;
unsigned long rate;
};
struct uniphier_clk_soc_data {
struct uniphier_clk_gate_data *gate;
unsigned int nr_gate;
struct uniphier_clk_rate_data *rate;
unsigned int nr_rate;
};
#define UNIPHIER_CLK_FIXED_RATE(i, f) \
{ \
.index = i, \
.reg = UNIPHIER_CLK_RATE_IS_FIXED, \
.rate = f, \
}
/**
* struct uniphier_clk_priv - private data for UniPhier clock driver
*
* @base: base address of the clock provider
* @socdata: SoC specific data
*/
struct uniphier_clk_priv {
void __iomem *base;
struct uniphier_clk_soc_data *socdata;
};
extern const struct clk_ops uniphier_clk_ops;
int uniphier_clk_probe(struct udevice *dev);
int uniphier_clk_remove(struct udevice *dev);
#endif /* __CLK_UNIPHIER_H__ */