diff options
author | Tom Rini <trini@konsulko.com> | 2017-03-17 09:11:12 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2017-03-17 14:15:17 -0400 |
commit | f9515756b6d76cde99b385dda905dfb20d31ea48 (patch) | |
tree | b2cd0007fb90a43992cb51f1a02d4e191e3dde10 /drivers/clk | |
parent | e245f1a5db086d676cbd97371046ea5c5e554326 (diff) | |
parent | 520c174b3564ae183f0e7c118dc8ce3770ae20b0 (diff) |
Merge git://git.denx.de/u-boot-rockchip
This includes support for rk3188 from Heiko Stübner and and rk3328 from
Kever Yang. Also included is SPL support for rk3399 and a fix for
rk3288 to get it booting again (spl_early_init()).
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/at91/pmc.c | 3 | ||||
-rw-r--r-- | drivers/clk/rockchip/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk_rk3188.c | 527 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk_rk3288.c | 2 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk_rk3328.c | 581 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk_rk3399.c | 91 |
6 files changed, 1196 insertions, 10 deletions
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c index c73156a0df..fcd693a2f6 100644 --- a/drivers/clk/at91/pmc.c +++ b/drivers/clk/at91/pmc.c @@ -10,6 +10,7 @@ #include <dm/device.h> #include <dm/lists.h> #include <dm/root.h> +#include <dm/util.h> #include "pmc.h" DECLARE_GLOBAL_DATA_PTR; @@ -56,7 +57,7 @@ int at91_clk_sub_device_bind(struct udevice *dev, const char *drv_name) offset > 0; offset = fdt_next_subnode(fdt, offset)) { if (pre_reloc_only && - !fdt_getprop(fdt, offset, "u-boot,dm-pre-reloc", NULL)) + !dm_fdt_pre_reloc(fdt, offset)) continue; /* * If this node has "compatible" property, this is not diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index 1f8e41739d..1091a76f05 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -5,5 +5,7 @@ # obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o +obj-$(CONFIG_ROCKCHIP_RK3188) += clk_rk3188.o obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o +obj-$(CONFIG_ROCKCHIP_RK3328) += clk_rk3328.o obj-$(CONFIG_ROCKCHIP_RK3399) += clk_rk3399.o diff --git a/drivers/clk/rockchip/clk_rk3188.c b/drivers/clk/rockchip/clk_rk3188.c new file mode 100644 index 0000000000..459649f724 --- /dev/null +++ b/drivers/clk/rockchip/clk_rk3188.c @@ -0,0 +1,527 @@ +/* + * (C) Copyright 2015 Google, Inc + * (C) Copyright 2016 Heiko Stuebner <heiko@sntech.de> + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <common.h> +#include <clk-uclass.h> +#include <dm.h> +#include <dt-structs.h> +#include <errno.h> +#include <mapmem.h> +#include <syscon.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/cru_rk3188.h> +#include <asm/arch/grf_rk3188.h> +#include <asm/arch/hardware.h> +#include <dt-bindings/clock/rk3188-cru.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/uclass-internal.h> +#include <linux/log2.h> + +DECLARE_GLOBAL_DATA_PTR; + +enum rk3188_clk_type { + RK3188_CRU, + RK3188A_CRU, +}; + +struct rk3188_clk_plat { +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_rockchip_rk3188_cru dtd; +#endif +}; + +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 = 30 * 1000000, + FREF_MAX_HZ = 2200U * 1000000, + FREF_MIN_HZ = 30 * 1000, +}; + +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, + + /* GRF_SOC_STATUS0 */ + SOCSTS_DPLL_LOCK = 1 << 5, + SOCSTS_APLL_LOCK = 1 << 6, + SOCSTS_CPLL_LOCK = 1 << 7, + SOCSTS_GPLL_LOCK = 1 << 8, +}; + +#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 */ +#ifdef CONFIG_SPL_BUILD +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); +#endif + +static int rkclk_set_pll(struct rk3188_cru *cru, enum rk_clk_id clk_id, + const struct pll_div *div, bool has_bwadj) +{ + int pll_id = rk_pll_id(clk_id); + struct rk3188_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); + + if (has_bwadj) + 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 int rkclk_configure_ddr(struct rk3188_cru *cru, struct rk3188_grf *grf, + unsigned int hz, bool has_bwadj) +{ + 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], has_bwadj); + + /* wait for pll lock */ + while (!(readl(&grf->soc_status0) & 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; +} + +/* Get pll rate by id */ +static uint32_t rkclk_pll_get_rate(struct rk3188_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 rk3188_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 + }; + 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 rk3188_cru *cru, uint gclk_rate, + int periph) +{ + uint div; + u32 con; + + switch (periph) { + case HCLK_EMMC: + con = readl(&cru->cru_clksel_con[12]); + div = (con >> EMMC_DIV_SHIFT) & EMMC_DIV_MASK; + break; + case HCLK_SDMMC: + con = readl(&cru->cru_clksel_con[11]); + div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK; + break; + case HCLK_SDIO: + con = readl(&cru->cru_clksel_con[12]); + div = (con >> SDIO_DIV_SHIFT) & SDIO_DIV_MASK; + break; + default: + return -EINVAL; + } + + return DIV_TO_RATE(gclk_rate, div); +} + +static ulong rockchip_mmc_set_clk(struct rk3188_cru *cru, uint gclk_rate, + int periph, uint freq) +{ + int src_clk_div; + + debug("%s: gclk_rate=%u\n", __func__, gclk_rate); + src_clk_div = RATE_TO_DIV(gclk_rate, freq); + assert(src_clk_div <= 0x3f); + + switch (periph) { + case HCLK_EMMC: + rk_clrsetreg(&cru->cru_clksel_con[12], + EMMC_DIV_MASK << EMMC_DIV_SHIFT, + src_clk_div << EMMC_DIV_SHIFT); + break; + case HCLK_SDMMC: + rk_clrsetreg(&cru->cru_clksel_con[11], + MMC0_DIV_MASK << MMC0_DIV_SHIFT, + src_clk_div << MMC0_DIV_SHIFT); + break; + case HCLK_SDIO: + rk_clrsetreg(&cru->cru_clksel_con[12], + SDIO_DIV_MASK << SDIO_DIV_SHIFT, + src_clk_div << SDIO_DIV_SHIFT); + break; + default: + return -EINVAL; + } + + return rockchip_mmc_get_clk(cru, gclk_rate, periph); +} + +static ulong rockchip_spi_get_clk(struct rk3188_cru *cru, uint gclk_rate, + int periph) +{ + uint div; + u32 con; + + switch (periph) { + case SCLK_SPI0: + con = readl(&cru->cru_clksel_con[25]); + div = (con >> SPI0_DIV_SHIFT) & SPI0_DIV_MASK; + break; + case SCLK_SPI1: + con = readl(&cru->cru_clksel_con[25]); + div = (con >> SPI1_DIV_SHIFT) & SPI1_DIV_MASK; + break; + default: + return -EINVAL; + } + + return DIV_TO_RATE(gclk_rate, div); +} + +static ulong rockchip_spi_set_clk(struct rk3188_cru *cru, uint gclk_rate, + int periph, uint freq) +{ + int src_clk_div = RATE_TO_DIV(gclk_rate, freq); + + switch (periph) { + case SCLK_SPI0: + assert(src_clk_div <= SPI0_DIV_MASK); + rk_clrsetreg(&cru->cru_clksel_con[25], + SPI0_DIV_MASK << SPI0_DIV_SHIFT, + src_clk_div << SPI0_DIV_SHIFT); + break; + case SCLK_SPI1: + assert(src_clk_div <= SPI1_DIV_MASK); + rk_clrsetreg(&cru->cru_clksel_con[25], + SPI1_DIV_MASK << SPI1_DIV_SHIFT, + src_clk_div << SPI1_DIV_SHIFT); + break; + default: + return -EINVAL; + } + + return rockchip_spi_get_clk(cru, gclk_rate, periph); +} + +#ifdef CONFIG_SPL_BUILD +static void rkclk_init(struct rk3188_cru *cru, struct rk3188_grf *grf, + bool has_bwadj) +{ + u32 aclk_div, hclk_div, pclk_div, h2p_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, has_bwadj); + rkclk_set_pll(cru, CLK_CODEC, &cpll_init_cfg, has_bwadj); + + /* waiting for pll lock */ + while ((readl(&grf->soc_status0) & + (SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) != + (SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) + udelay(1); + + /* + * cpu clock pll source selection and + * reparent aclk_cpu_pre from apll to gpll + * set up dependent divisors for PCLK/HCLK and ACLK clocks. + */ + aclk_div = RATE_TO_DIV(GPLL_HZ, CPU_ACLK_HZ); + assert((aclk_div + 1) * CPU_ACLK_HZ == GPLL_HZ && aclk_div < 0x1f); + + rk_clrsetreg(&cru->cru_clksel_con[0], + CPU_ACLK_PLL_MASK << CPU_ACLK_PLL_SHIFT | + A9_CPU_DIV_MASK << A9_CPU_DIV_SHIFT, + CPU_ACLK_PLL_SELECT_GPLL << CPU_ACLK_PLL_SHIFT | + aclk_div << A9_CPU_DIV_SHIFT); + + hclk_div = ilog2(CPU_ACLK_HZ / CPU_HCLK_HZ); + assert((1 << hclk_div) * CPU_HCLK_HZ == CPU_ACLK_HZ && hclk_div < 0x3); + pclk_div = ilog2(CPU_ACLK_HZ / CPU_PCLK_HZ); + assert((1 << pclk_div) * CPU_PCLK_HZ == CPU_ACLK_HZ && pclk_div < 0x4); + h2p_div = ilog2(CPU_HCLK_HZ / CPU_H2P_HZ); + assert((1 << h2p_div) * CPU_H2P_HZ == CPU_HCLK_HZ && pclk_div < 0x3); + + rk_clrsetreg(&cru->cru_clksel_con[1], + AHB2APB_DIV_MASK << AHB2APB_DIV_SHIFT | + CPU_PCLK_DIV_MASK << CPU_PCLK_DIV_SHIFT | + CPU_HCLK_DIV_MASK << CPU_HCLK_DIV_SHIFT, + h2p_div << AHB2APB_DIV_SHIFT | + pclk_div << CPU_PCLK_DIV_SHIFT | + hclk_div << CPU_HCLK_DIV_SHIFT); + + /* + * 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 = ilog2(PERI_ACLK_HZ / PERI_HCLK_HZ); + assert((1 << hclk_div) * PERI_HCLK_HZ == + PERI_ACLK_HZ && (hclk_div < 0x4)); + + pclk_div = ilog2(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); + + rockchip_mmc_set_clk(cru, PERI_HCLK_HZ, HCLK_SDMMC, 16000000); +} +#endif + +static ulong rk3188_clk_get_rate(struct clk *clk) +{ + struct rk3188_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 1 ... 4: + new_rate = rkclk_pll_get_rate(priv->cru, clk->id); + break; + case HCLK_EMMC: + case HCLK_SDMMC: + case HCLK_SDIO: + new_rate = rockchip_mmc_get_clk(priv->cru, PERI_HCLK_HZ, + clk->id); + break; + case SCLK_SPI0: + case SCLK_SPI1: + new_rate = rockchip_spi_get_clk(priv->cru, PERI_PCLK_HZ, + clk->id); + break; + case PCLK_I2C0: + case PCLK_I2C1: + case PCLK_I2C2: + case PCLK_I2C3: + case PCLK_I2C4: + return gclk_rate; + default: + return -ENOENT; + } + + return new_rate; +} + +static ulong rk3188_clk_set_rate(struct clk *clk, ulong rate) +{ + struct rk3188_clk_priv *priv = dev_get_priv(clk->dev); + struct rk3188_cru *cru = priv->cru; + ulong new_rate; + + switch (clk->id) { + case CLK_DDR: + new_rate = rkclk_configure_ddr(priv->cru, priv->grf, rate, + priv->has_bwadj); + break; + case HCLK_EMMC: + case HCLK_SDMMC: + case HCLK_SDIO: + new_rate = rockchip_mmc_set_clk(cru, PERI_HCLK_HZ, + clk->id, rate); + break; + case SCLK_SPI0: + case SCLK_SPI1: + new_rate = rockchip_spi_set_clk(cru, PERI_PCLK_HZ, + clk->id, rate); + break; + default: + return -ENOENT; + } + + return new_rate; +} + +static struct clk_ops rk3188_clk_ops = { + .get_rate = rk3188_clk_get_rate, + .set_rate = rk3188_clk_set_rate, +}; + +static int rk3188_clk_ofdata_to_platdata(struct udevice *dev) +{ +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + struct rk3188_clk_priv *priv = dev_get_priv(dev); + + priv->cru = (struct rk3188_cru *)dev_get_addr(dev); +#endif + + return 0; +} + +static int rk3188_clk_probe(struct udevice *dev) +{ + struct rk3188_clk_priv *priv = dev_get_priv(dev); + enum rk3188_clk_type type = dev_get_driver_data(dev); + + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + if (IS_ERR(priv->grf)) + return PTR_ERR(priv->grf); + priv->has_bwadj = (type == RK3188A_CRU) ? 1 : 0; + +#ifdef CONFIG_SPL_BUILD +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct rk3188_clk_plat *plat = dev_get_platdata(dev); + + priv->cru = map_sysmem(plat->dtd.reg[0], plat->dtd.reg[1]); +#endif + + rkclk_init(priv->cru, priv->grf, priv->has_bwadj); +#endif + + return 0; +} + +static int rk3188_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, "rk3188_sysreset", "reset", &dev); + if (ret) + debug("Warning: No rk3188 reset driver: ret=%d\n", ret); + + return 0; +} + +static const struct udevice_id rk3188_clk_ids[] = { + { .compatible = "rockchip,rk3188-cru", .data = RK3188_CRU }, + { .compatible = "rockchip,rk3188a-cru", .data = RK3188A_CRU }, + { } +}; + +U_BOOT_DRIVER(rockchip_rk3188_cru) = { + .name = "rockchip_rk3188_cru", + .id = UCLASS_CLK, + .of_match = rk3188_clk_ids, + .priv_auto_alloc_size = sizeof(struct rk3188_clk_priv), + .platdata_auto_alloc_size = sizeof(struct rk3188_clk_plat), + .ops = &rk3188_clk_ops, + .bind = rk3188_clk_bind, + .ofdata_to_platdata = rk3188_clk_ofdata_to_platdata, + .probe = rk3188_clk_probe, +}; diff --git a/drivers/clk/rockchip/clk_rk3288.c b/drivers/clk/rockchip/clk_rk3288.c index d15504c3aa..78356766a7 100644 --- a/drivers/clk/rockchip/clk_rk3288.c +++ b/drivers/clk/rockchip/clk_rk3288.c @@ -131,8 +131,10 @@ enum { /* 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); +#ifdef CONFIG_SPL_BUILD 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); +#endif static int rkclk_set_pll(struct rk3288_cru *cru, enum rk_clk_id clk_id, const struct pll_div *div) diff --git a/drivers/clk/rockchip/clk_rk3328.c b/drivers/clk/rockchip/clk_rk3328.c new file mode 100644 index 0000000000..0ff1e30bb5 --- /dev/null +++ b/drivers/clk/rockchip/clk_rk3328.c @@ -0,0 +1,581 @@ +/* + * (C) Copyright 2017 Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <common.h> +#include <clk-uclass.h> +#include <dm.h> +#include <errno.h> +#include <syscon.h> +#include <asm/arch/clock.h> +#include <asm/arch/cru_rk3328.h> +#include <asm/arch/hardware.h> +#include <asm/io.h> +#include <dm/lists.h> +#include <dt-bindings/clock/rk3328-cru.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct pll_div { + u32 refdiv; + u32 fbdiv; + u32 postdiv1; + u32 postdiv2; + u32 frac; +}; + +#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 const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 1, 4, 1); +static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 2, 2, 1); + +static const struct pll_div apll_816_cfg = PLL_DIVISORS(816 * MHz, 1, 2, 1); +static const struct pll_div apll_600_cfg = PLL_DIVISORS(600 * MHz, 1, 3, 1); + +static const struct pll_div *apll_cfgs[] = { + [APLL_816_MHZ] = &apll_816_cfg, + [APLL_600_MHZ] = &apll_600_cfg, +}; + +enum { + /* PLL_CON0 */ + PLL_POSTDIV1_SHIFT = 12, + PLL_POSTDIV1_MASK = 0x7 << PLL_POSTDIV1_SHIFT, + PLL_FBDIV_SHIFT = 0, + PLL_FBDIV_MASK = 0xfff, + + /* PLL_CON1 */ + PLL_DSMPD_SHIFT = 12, + PLL_DSMPD_MASK = 1 << PLL_DSMPD_SHIFT, + PLL_INTEGER_MODE = 1, + PLL_LOCK_STATUS_SHIFT = 10, + PLL_LOCK_STATUS_MASK = 1 << PLL_LOCK_STATUS_SHIFT, + PLL_POSTDIV2_SHIFT = 6, + PLL_POSTDIV2_MASK = 0x7 << PLL_POSTDIV2_SHIFT, + PLL_REFDIV_SHIFT = 0, + PLL_REFDIV_MASK = 0x3f, + + /* PLL_CON2 */ + PLL_FRACDIV_SHIFT = 0, + PLL_FRACDIV_MASK = 0xffffff, + + /* MODE_CON */ + APLL_MODE_SHIFT = 0, + NPLL_MODE_SHIFT = 1, + DPLL_MODE_SHIFT = 4, + CPLL_MODE_SHIFT = 8, + GPLL_MODE_SHIFT = 12, + PLL_MODE_SLOW = 0, + PLL_MODE_NORM, + + /* CLKSEL_CON0 */ + CLK_CORE_PLL_SEL_APLL = 0, + CLK_CORE_PLL_SEL_GPLL, + CLK_CORE_PLL_SEL_DPLL, + CLK_CORE_PLL_SEL_NPLL, + CLK_CORE_PLL_SEL_SHIFT = 6, + CLK_CORE_PLL_SEL_MASK = 3 << CLK_CORE_PLL_SEL_SHIFT, + CLK_CORE_DIV_SHIFT = 0, + CLK_CORE_DIV_MASK = 0x1f, + + /* CLKSEL_CON1 */ + ACLKM_CORE_DIV_SHIFT = 4, + ACLKM_CORE_DIV_MASK = 0x7 << ACLKM_CORE_DIV_SHIFT, + PCLK_DBG_DIV_SHIFT = 0, + PCLK_DBG_DIV_MASK = 0xF << PCLK_DBG_DIV_SHIFT, + + /* CLKSEL_CON28 */ + ACLK_PERIHP_PLL_SEL_CPLL = 0, + ACLK_PERIHP_PLL_SEL_GPLL, + ACLK_PERIHP_PLL_SEL_HDMIPHY, + ACLK_PERIHP_PLL_SEL_SHIFT = 6, + ACLK_PERIHP_PLL_SEL_MASK = 3 << ACLK_PERIHP_PLL_SEL_SHIFT, + ACLK_PERIHP_DIV_CON_SHIFT = 0, + ACLK_PERIHP_DIV_CON_MASK = 0x1f, + + /* CLKSEL_CON29 */ + PCLK_PERIHP_DIV_CON_SHIFT = 4, + PCLK_PERIHP_DIV_CON_MASK = 0x7 << PCLK_PERIHP_DIV_CON_SHIFT, + HCLK_PERIHP_DIV_CON_SHIFT = 0, + HCLK_PERIHP_DIV_CON_MASK = 3 << HCLK_PERIHP_DIV_CON_SHIFT, + + /* CLKSEL_CON22 */ + CLK_TSADC_DIV_CON_SHIFT = 0, + CLK_TSADC_DIV_CON_MASK = 0x3ff, + + /* CLKSEL_CON23 */ + CLK_SARADC_DIV_CON_SHIFT = 0, + CLK_SARADC_DIV_CON_MASK = 0x3ff << CLK_SARADC_DIV_CON_SHIFT, + + /* CLKSEL_CON24 */ + CLK_PWM_PLL_SEL_CPLL = 0, + CLK_PWM_PLL_SEL_GPLL, + CLK_PWM_PLL_SEL_SHIFT = 15, + CLK_PWM_PLL_SEL_MASK = 1 << CLK_PWM_PLL_SEL_SHIFT, + CLK_PWM_DIV_CON_SHIFT = 8, + CLK_PWM_DIV_CON_MASK = 0x7f << CLK_PWM_DIV_CON_SHIFT, + + CLK_SPI_PLL_SEL_CPLL = 0, + CLK_SPI_PLL_SEL_GPLL, + CLK_SPI_PLL_SEL_SHIFT = 7, + CLK_SPI_PLL_SEL_MASK = 1 << CLK_SPI_PLL_SEL_SHIFT, + CLK_SPI_DIV_CON_SHIFT = 0, + CLK_SPI_DIV_CON_MASK = 0x7f << CLK_SPI_DIV_CON_SHIFT, + + /* CLKSEL_CON30 */ + CLK_SDMMC_PLL_SEL_CPLL = 0, + CLK_SDMMC_PLL_SEL_GPLL, + CLK_SDMMC_PLL_SEL_24M, + CLK_SDMMC_PLL_SEL_USBPHY, + CLK_SDMMC_PLL_SHIFT = 8, + CLK_SDMMC_PLL_MASK = 0x3 << CLK_SDMMC_PLL_SHIFT, + CLK_SDMMC_DIV_CON_SHIFT = 0, + CLK_SDMMC_DIV_CON_MASK = 0xff << CLK_SDMMC_DIV_CON_SHIFT, + + /* CLKSEL_CON32 */ + CLK_EMMC_PLL_SEL_CPLL = 0, + CLK_EMMC_PLL_SEL_GPLL, + CLK_EMMC_PLL_SEL_24M, + CLK_EMMC_PLL_SEL_USBPHY, + CLK_EMMC_PLL_SHIFT = 8, + CLK_EMMC_PLL_MASK = 0x3 << CLK_EMMC_PLL_SHIFT, + CLK_EMMC_DIV_CON_SHIFT = 0, + CLK_EMMC_DIV_CON_MASK = 0xff << CLK_EMMC_DIV_CON_SHIFT, + + /* CLKSEL_CON34 */ + CLK_I2C_PLL_SEL_CPLL = 0, + CLK_I2C_PLL_SEL_GPLL, + CLK_I2C_DIV_CON_MASK = 0x7f, + CLK_I2C_PLL_SEL_MASK = 1, + CLK_I2C1_PLL_SEL_SHIFT = 15, + CLK_I2C1_DIV_CON_SHIFT = 8, + CLK_I2C0_PLL_SEL_SHIFT = 7, + CLK_I2C0_DIV_CON_SHIFT = 0, + + /* CLKSEL_CON35 */ + CLK_I2C3_PLL_SEL_SHIFT = 15, + CLK_I2C3_DIV_CON_SHIFT = 8, + CLK_I2C2_PLL_SEL_SHIFT = 7, + CLK_I2C2_DIV_CON_SHIFT = 0, +}; + +#define VCO_MAX_KHZ (3200 * (MHz / KHz)) +#define VCO_MIN_KHZ (800 * (MHz / KHz)) +#define OUTPUT_MAX_KHZ (3200 * (MHz / KHz)) +#define OUTPUT_MIN_KHZ (16 * (MHz / KHz)) + +/* + * the div restructions of pll in integer mode, these are defined in + * * CRU_*PLL_CON0 or PMUCRU_*PLL_CON0 + */ +#define PLL_DIV_MIN 16 +#define PLL_DIV_MAX 3200 + +/* + * How to calculate the PLL(from TRM V0.3 Part 1 Page 63): + * Formulas also embedded within the Fractional PLL Verilog model: + * If DSMPD = 1 (DSM is disabled, "integer mode") + * FOUTVCO = FREF / REFDIV * FBDIV + * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2 + * Where: + * FOUTVCO = Fractional PLL non-divided output frequency + * FOUTPOSTDIV = Fractional PLL divided output frequency + * (output of second post divider) + * FREF = Fractional PLL input reference frequency, (the OSC_HZ 24MHz input) + * REFDIV = Fractional PLL input reference clock divider + * FBDIV = Integer value programmed into feedback divide + * + */ +static void rkclk_set_pll(struct rk3328_cru *cru, enum rk_clk_id clk_id, + const struct pll_div *div) +{ + u32 *pll_con; + u32 mode_shift, mode_mask; + + pll_con = NULL; + mode_shift = 0; + switch (clk_id) { + case CLK_ARM: + pll_con = cru->apll_con; + mode_shift = APLL_MODE_SHIFT; + break; + case CLK_DDR: + pll_con = cru->dpll_con; + mode_shift = DPLL_MODE_SHIFT; + break; + case CLK_CODEC: + pll_con = cru->cpll_con; + mode_shift = CPLL_MODE_SHIFT; + break; + case CLK_GENERAL: + pll_con = cru->gpll_con; + mode_shift = GPLL_MODE_SHIFT; + break; + case CLK_NEW: + pll_con = cru->npll_con; + mode_shift = NPLL_MODE_SHIFT; + break; + default: + break; + } + mode_mask = 1 << mode_shift; + + /* All 8 PLLs have same VCO and output frequency range restrictions. */ + u32 vco_khz = OSC_HZ / 1000 * div->fbdiv / div->refdiv; + u32 output_khz = vco_khz / div->postdiv1 / div->postdiv2; + + debug("PLL at %p: fbdiv=%d, refdiv=%d, postdiv1=%d, \ + postdiv2=%d, vco=%u khz, output=%u khz\n", + pll_con, div->fbdiv, div->refdiv, div->postdiv1, + div->postdiv2, vco_khz, output_khz); + assert(vco_khz >= VCO_MIN_KHZ && vco_khz <= VCO_MAX_KHZ && + output_khz >= OUTPUT_MIN_KHZ && output_khz <= OUTPUT_MAX_KHZ && + div->fbdiv >= PLL_DIV_MIN && div->fbdiv <= PLL_DIV_MAX); + + /* + * When power on or changing PLL setting, + * we must force PLL into slow mode to ensure output stable clock. + */ + rk_clrsetreg(&cru->mode_con, mode_mask, PLL_MODE_SLOW << mode_shift); + + /* use integer mode */ + rk_clrsetreg(&pll_con[1], PLL_DSMPD_MASK, + PLL_INTEGER_MODE << PLL_DSMPD_SHIFT); + + rk_clrsetreg(&pll_con[0], + PLL_FBDIV_MASK | PLL_POSTDIV1_MASK, + (div->fbdiv << PLL_FBDIV_SHIFT) | + (div->postdiv1 << PLL_POSTDIV1_SHIFT)); + rk_clrsetreg(&pll_con[1], + PLL_POSTDIV2_MASK | PLL_REFDIV_MASK, + (div->postdiv2 << PLL_POSTDIV2_SHIFT) | + (div->refdiv << PLL_REFDIV_SHIFT)); + + /* waiting for pll lock */ + while (!(readl(&pll_con[1]) & (1 << PLL_LOCK_STATUS_SHIFT))) + udelay(1); + + /* pll enter normal mode */ + rk_clrsetreg(&cru->mode_con, mode_mask, PLL_MODE_NORM << mode_shift); +} + +static void rkclk_init(struct rk3328_cru *cru) +{ + u32 aclk_div; + u32 hclk_div; + u32 pclk_div; + + /* configure gpll cpll */ + rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg); + rkclk_set_pll(cru, CLK_CODEC, &cpll_init_cfg); + + /* configure perihp aclk, hclk, pclk */ + aclk_div = GPLL_HZ / PERIHP_ACLK_HZ - 1; + hclk_div = PERIHP_ACLK_HZ / PERIHP_HCLK_HZ - 1; + pclk_div = PERIHP_ACLK_HZ / PERIHP_PCLK_HZ - 1; + + rk_clrsetreg(&cru->clksel_con[28], + ACLK_PERIHP_PLL_SEL_MASK | ACLK_PERIHP_DIV_CON_MASK, + ACLK_PERIHP_PLL_SEL_GPLL << ACLK_PERIHP_PLL_SEL_SHIFT | + aclk_div << ACLK_PERIHP_DIV_CON_SHIFT); + rk_clrsetreg(&cru->clksel_con[29], + PCLK_PERIHP_DIV_CON_MASK | HCLK_PERIHP_DIV_CON_MASK, + pclk_div << PCLK_PERIHP_DIV_CON_SHIFT | + hclk_div << HCLK_PERIHP_DIV_CON_SHIFT); +} + +void rk3328_configure_cpu(struct rk3328_cru *cru, + enum apll_frequencies apll_freq) +{ + u32 clk_core_div; + u32 aclkm_div; + u32 pclk_dbg_div; + + rkclk_set_pll(cru, CLK_ARM, apll_cfgs[apll_freq]); + + clk_core_div = APLL_HZ / CLK_CORE_HZ - 1; + aclkm_div = APLL_HZ / ACLKM_CORE_HZ / (clk_core_div + 1) - 1; + pclk_dbg_div = APLL_HZ / PCLK_DBG_HZ / (clk_core_div + 1) - 1; + + rk_clrsetreg(&cru->clksel_con[0], + CLK_CORE_PLL_SEL_MASK | CLK_CORE_DIV_MASK, + CLK_CORE_PLL_SEL_APLL << CLK_CORE_PLL_SEL_SHIFT | + clk_core_div << CLK_CORE_DIV_SHIFT); + + rk_clrsetreg(&cru->clksel_con[1], + PCLK_DBG_DIV_MASK | ACLKM_CORE_DIV_MASK, + pclk_dbg_div << PCLK_DBG_DIV_SHIFT | + aclkm_div << ACLKM_CORE_DIV_SHIFT); +} + + +static ulong rk3328_i2c_get_clk(struct rk3328_cru *cru, ulong clk_id) +{ + u32 div, con; + + switch (clk_id) { + case SCLK_I2C0: + con = readl(&cru->clksel_con[34]); + div = con >> CLK_I2C0_DIV_CON_SHIFT & CLK_I2C_DIV_CON_MASK; + break; + case SCLK_I2C1: + con = readl(&cru->clksel_con[34]); + div = con >> CLK_I2C1_DIV_CON_SHIFT & CLK_I2C_DIV_CON_MASK; + break; + case SCLK_I2C2: + con = readl(&cru->clksel_con[35]); + div = con >> CLK_I2C2_DIV_CON_SHIFT & CLK_I2C_DIV_CON_MASK; + break; + case SCLK_I2C3: + con = readl(&cru->clksel_con[35]); + div = con >> CLK_I2C3_DIV_CON_SHIFT & CLK_I2C_DIV_CON_MASK; + break; + default: + printf("do not support this i2c bus\n"); + return -EINVAL; + } + + return DIV_TO_RATE(GPLL_HZ, div); +} + +static ulong rk3328_i2c_set_clk(struct rk3328_cru *cru, ulong clk_id, uint hz) +{ + int src_clk_div; + + src_clk_div = GPLL_HZ / hz; + assert(src_clk_div - 1 < 127); + + switch (clk_id) { + case SCLK_I2C0: + rk_clrsetreg(&cru->clksel_con[34], + CLK_I2C_DIV_CON_MASK << CLK_I2C0_DIV_CON_SHIFT | + CLK_I2C_PLL_SEL_MASK << CLK_I2C0_PLL_SEL_SHIFT, + (src_clk_div - 1) << CLK_I2C0_DIV_CON_SHIFT | + CLK_I2C_PLL_SEL_GPLL << CLK_I2C0_PLL_SEL_SHIFT); + break; + case SCLK_I2C1: + rk_clrsetreg(&cru->clksel_con[34], + CLK_I2C_DIV_CON_MASK << CLK_I2C1_DIV_CON_SHIFT | + CLK_I2C_PLL_SEL_MASK << CLK_I2C1_PLL_SEL_SHIFT, + (src_clk_div - 1) << CLK_I2C1_DIV_CON_SHIFT | + CLK_I2C_PLL_SEL_GPLL << CLK_I2C1_PLL_SEL_SHIFT); + break; + case SCLK_I2C2: + rk_clrsetreg(&cru->clksel_con[35], + CLK_I2C_DIV_CON_MASK << CLK_I2C2_DIV_CON_SHIFT | + CLK_I2C_PLL_SEL_MASK << CLK_I2C2_PLL_SEL_SHIFT, + (src_clk_div - 1) << CLK_I2C2_DIV_CON_SHIFT | + CLK_I2C_PLL_SEL_GPLL << CLK_I2C2_PLL_SEL_SHIFT); + break; + case SCLK_I2C3: + rk_clrsetreg(&cru->clksel_con[35], + CLK_I2C_DIV_CON_MASK << CLK_I2C3_DIV_CON_SHIFT | + CLK_I2C_PLL_SEL_MASK << CLK_I2C3_PLL_SEL_SHIFT, + (src_clk_div - 1) << CLK_I2C3_DIV_CON_SHIFT | + CLK_I2C_PLL_SEL_GPLL << CLK_I2C3_PLL_SEL_SHIFT); + break; + default: + printf("do not support this i2c bus\n"); + return -EINVAL; + } + + return DIV_TO_RATE(GPLL_HZ, src_clk_div); +} + +static ulong rk3328_mmc_get_clk(struct rk3328_cru *cru, uint clk_id) +{ + u32 div, con, con_id; + + switch (clk_id) { + case HCLK_SDMMC: + con_id = 30; + break; + case HCLK_EMMC: + con_id = 32; + break; + default: + return -EINVAL; + } + con = readl(&cru->clksel_con[con_id]); + div = (con & CLK_EMMC_DIV_CON_MASK) >> CLK_EMMC_DIV_CON_SHIFT; + + if ((con & CLK_EMMC_PLL_MASK) >> CLK_EMMC_PLL_SHIFT + == CLK_EMMC_PLL_SEL_24M) + return DIV_TO_RATE(OSC_HZ, div); + else + return DIV_TO_RATE(GPLL_HZ, div); +} + +static ulong rk3328_mmc_set_clk(struct rk3328_cru *cru, + ulong clk_id, ulong set_rate) +{ + int src_clk_div; + u32 con_id; + + switch (clk_id) { + case HCLK_SDMMC: + con_id = 30; + break; + case HCLK_EMMC: + con_id = 32; + break; + default: + return -EINVAL; + } + /* Select clk_sdmmc/emmc source from GPLL by default */ + src_clk_div = GPLL_HZ / set_rate; + + if (src_clk_div > 127) { + /* use 24MHz source for 400KHz clock */ + src_clk_div = OSC_HZ / set_rate; + rk_clrsetreg(&cru->clksel_con[con_id], + CLK_EMMC_PLL_MASK | CLK_EMMC_DIV_CON_MASK, + CLK_EMMC_PLL_SEL_24M << CLK_EMMC_PLL_SHIFT | + (src_clk_div - 1) << CLK_EMMC_DIV_CON_SHIFT); + } else { + rk_clrsetreg(&cru->clksel_con[con_id], + CLK_EMMC_PLL_MASK | CLK_EMMC_DIV_CON_MASK, + CLK_EMMC_PLL_SEL_GPLL << CLK_EMMC_PLL_SHIFT | + (src_clk_div - 1) << CLK_EMMC_DIV_CON_SHIFT); + } + + return rk3328_mmc_get_clk(cru, clk_id); +} + +static ulong rk3328_pwm_get_clk(struct rk3328_cru *cru) +{ + u32 div, con; + + con = readl(&cru->clksel_con[24]); + div = (con & CLK_PWM_DIV_CON_MASK) >> CLK_PWM_DIV_CON_SHIFT; + + return DIV_TO_RATE(GPLL_HZ, div); +} + +static ulong rk3328_pwm_set_clk(struct rk3328_cru *cru, uint hz) +{ + u32 div = GPLL_HZ / hz; + + rk_clrsetreg(&cru->clksel_con[24], + CLK_PWM_PLL_SEL_MASK | CLK_PWM_DIV_CON_MASK, + CLK_PWM_PLL_SEL_GPLL << CLK_PWM_PLL_SEL_SHIFT | + (div - 1) << CLK_PWM_DIV_CON_SHIFT); + + return DIV_TO_RATE(GPLL_HZ, div); +} + +static ulong rk3328_clk_get_rate(struct clk *clk) +{ + struct rk3328_clk_priv *priv = dev_get_priv(clk->dev); + ulong rate = 0; + + switch (clk->id) { + case 0 ... 29: + return 0; + case HCLK_SDMMC: + case HCLK_EMMC: + rate = rk3328_mmc_get_clk(priv->cru, clk->id); + break; + case SCLK_I2C0: + case SCLK_I2C1: + case SCLK_I2C2: + case SCLK_I2C3: + rate = rk3328_i2c_get_clk(priv->cru, clk->id); + break; + case SCLK_PWM: + rate = rk3328_pwm_get_clk(priv->cru); + break; + default: + return -ENOENT; + } + + return rate; +} + +static ulong rk3328_clk_set_rate(struct clk *clk, ulong rate) +{ + struct rk3328_clk_priv *priv = dev_get_priv(clk->dev); + ulong ret = 0; + + switch (clk->id) { + case 0 ... 29: + return 0; + case HCLK_SDMMC: + case HCLK_EMMC: + ret = rk3328_mmc_set_clk(priv->cru, clk->id, rate); + break; + case SCLK_I2C0: + case SCLK_I2C1: + case SCLK_I2C2: + case SCLK_I2C3: + ret = rk3328_i2c_set_clk(priv->cru, clk->id, rate); + break; + case SCLK_PWM: + ret = rk3328_pwm_set_clk(priv->cru, rate); + break; + default: + return -ENOENT; + } + + return ret; +} + +static struct clk_ops rk3328_clk_ops = { + .get_rate = rk3328_clk_get_rate, + .set_rate = rk3328_clk_set_rate, +}; + +static int rk3328_clk_probe(struct udevice *dev) +{ + struct rk3328_clk_priv *priv = dev_get_priv(dev); + + rkclk_init(priv->cru); + + return 0; +} + +static int rk3328_clk_ofdata_to_platdata(struct udevice *dev) +{ + struct rk3328_clk_priv *priv = dev_get_priv(dev); + + priv->cru = (struct rk3328_cru *)dev_get_addr(dev); + + return 0; +} + +static int rk3328_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, "rk3328_sysreset", "reset", &dev); + if (ret) + printf("Warning: No RK3328 reset driver: ret=%d\n", ret); + + return ret; +} + +static const struct udevice_id rk3328_clk_ids[] = { + { .compatible = "rockchip,rk3328-cru" }, + { } +}; + +U_BOOT_DRIVER(rockchip_rk3328_cru) = { + .name = "rockchip_rk3328_cru", + .id = UCLASS_CLK, + .of_match = rk3328_clk_ids, + .priv_auto_alloc_size = sizeof(struct rk3328_clk_priv), + .ofdata_to_platdata = rk3328_clk_ofdata_to_platdata, + .ops = &rk3328_clk_ops, + .bind = rk3328_clk_bind, + .probe = rk3328_clk_probe, +}; diff --git a/drivers/clk/rockchip/clk_rk3399.c b/drivers/clk/rockchip/clk_rk3399.c index 2e87e4b62d..922ce7e549 100644 --- a/drivers/clk/rockchip/clk_rk3399.c +++ b/drivers/clk/rockchip/clk_rk3399.c @@ -7,7 +7,9 @@ #include <common.h> #include <clk-uclass.h> #include <dm.h> +#include <dt-structs.h> #include <errno.h> +#include <mapmem.h> #include <syscon.h> #include <asm/io.h> #include <asm/arch/clock.h> @@ -18,10 +20,16 @@ DECLARE_GLOBAL_DATA_PTR; -struct rk3399_pmuclk_priv { - struct rk3399_pmucru *pmucru; +#if CONFIG_IS_ENABLED(OF_PLATDATA) +struct rk3399_clk_plat { + struct dtd_rockchip_rk3399_cru dtd; }; +struct rk3399_pmuclk_plat { + struct dtd_rockchip_rk3399_pmucru dtd; +}; +#endif + struct pll_div { u32 refdiv; u32 fbdiv; @@ -381,6 +389,7 @@ static int pll_para_config(u32 freq_hz, struct pll_div *div) return 0; } +#ifdef CONFIG_SPL_BUILD static void rkclk_init(struct rk3399_cru *cru) { u32 aclk_div; @@ -456,6 +465,7 @@ static void rkclk_init(struct rk3399_cru *cru) hclk_div << HCLK_PERILP1_DIV_CON_SHIFT | HCLK_PERILP1_PLL_SEL_GPLL << HCLK_PERILP1_PLL_SEL_SHIFT); } +#endif void rk3399_configure_cpu(struct rk3399_cru *cru, enum apll_l_frequencies apll_l_freq) @@ -709,6 +719,44 @@ static ulong rk3399_mmc_set_clk(struct rk3399_cru *cru, return rk3399_mmc_get_clk(cru, clk_id); } +#define PMUSGRF_DDR_RGN_CON16 0xff330040 +static ulong rk3399_ddr_set_clk(struct rk3399_cru *cru, + ulong set_rate) +{ + struct pll_div dpll_cfg; + + /* IC ECO bug, need to set this register */ + writel(0xc000c000, PMUSGRF_DDR_RGN_CON16); + + /* clk_ddrc == DPLL = 24MHz / refdiv * fbdiv / postdiv1 / postdiv2 */ + switch (set_rate) { + case 200*MHz: + dpll_cfg = (struct pll_div) + {.refdiv = 1, .fbdiv = 50, .postdiv1 = 6, .postdiv2 = 1}; + break; + case 300*MHz: + dpll_cfg = (struct pll_div) + {.refdiv = 2, .fbdiv = 100, .postdiv1 = 4, .postdiv2 = 1}; + break; + case 666*MHz: + dpll_cfg = (struct pll_div) + {.refdiv = 2, .fbdiv = 111, .postdiv1 = 2, .postdiv2 = 1}; + break; + case 800*MHz: + dpll_cfg = (struct pll_div) + {.refdiv = 1, .fbdiv = 100, .postdiv1 = 3, .postdiv2 = 1}; + break; + case 933*MHz: + dpll_cfg = (struct pll_div) + {.refdiv = 1, .fbdiv = 116, .postdiv1 = 3, .postdiv2 = 1}; + break; + default: + error("Unsupported SDRAM frequency!,%ld\n", set_rate); + } + rkclk_set_pll(&cru->dpll_con[0], &dpll_cfg); + + return set_rate; +} static ulong rk3399_clk_get_rate(struct clk *clk) { struct rk3399_clk_priv *priv = dev_get_priv(clk->dev); @@ -763,6 +811,9 @@ static ulong rk3399_clk_set_rate(struct clk *clk, ulong rate) case DCLK_VOP1: ret = rk3399_vop_set_clk(priv->cru, clk->id, rate); break; + case SCLK_DDRCLK: + ret = rk3399_ddr_set_clk(priv->cru, rate); + break; default: return -ENOENT; } @@ -777,19 +828,26 @@ static struct clk_ops rk3399_clk_ops = { static int rk3399_clk_probe(struct udevice *dev) { +#ifdef CONFIG_SPL_BUILD struct rk3399_clk_priv *priv = dev_get_priv(dev); - rkclk_init(priv->cru); +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct rk3399_clk_plat *plat = dev_get_platdata(dev); + priv->cru = map_sysmem(plat->dtd.reg[1], plat->dtd.reg[3]); +#endif + rkclk_init(priv->cru); +#endif return 0; } static int rk3399_clk_ofdata_to_platdata(struct udevice *dev) { +#if !CONFIG_IS_ENABLED(OF_PLATDATA) struct rk3399_clk_priv *priv = dev_get_priv(dev); priv->cru = (struct rk3399_cru *)dev_get_addr(dev); - +#endif return 0; } @@ -811,7 +869,7 @@ static const struct udevice_id rk3399_clk_ids[] = { }; U_BOOT_DRIVER(clk_rk3399) = { - .name = "clk_rk3399", + .name = "rockchip_rk3399_cru", .id = UCLASS_CLK, .of_match = rk3399_clk_ids, .priv_auto_alloc_size = sizeof(struct rk3399_clk_priv), @@ -819,6 +877,9 @@ U_BOOT_DRIVER(clk_rk3399) = { .ops = &rk3399_clk_ops, .bind = rk3399_clk_bind, .probe = rk3399_clk_probe, +#if CONFIG_IS_ENABLED(OF_PLATDATA) + .platdata_auto_alloc_size = sizeof(struct rk3399_clk_plat), +#endif }; static ulong rk3399_i2c_get_pmuclk(struct rk3399_pmucru *pmucru, ulong clk_id) @@ -930,6 +991,7 @@ static struct clk_ops rk3399_pmuclk_ops = { .set_rate = rk3399_pmuclk_set_rate, }; +#ifndef CONFIG_SPL_BUILD static void pmuclk_init(struct rk3399_pmucru *pmucru) { u32 pclk_div; @@ -939,27 +1001,35 @@ static void pmuclk_init(struct rk3399_pmucru *pmucru) /* configure pmu pclk */ pclk_div = PPLL_HZ / PMU_PCLK_HZ - 1; - assert((pclk_div + 1) * PMU_PCLK_HZ == PPLL_HZ && pclk_div < 0x1f); rk_clrsetreg(&pmucru->pmucru_clksel[0], PMU_PCLK_DIV_CON_MASK, pclk_div << PMU_PCLK_DIV_CON_SHIFT); } +#endif static int rk3399_pmuclk_probe(struct udevice *dev) { struct rk3399_pmuclk_priv *priv = dev_get_priv(dev); - pmuclk_init(priv->pmucru); +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct rk3399_pmuclk_plat *plat = dev_get_platdata(dev); + + priv->pmucru = map_sysmem(plat->dtd.reg[1], plat->dtd.reg[3]); +#endif +#ifndef CONFIG_SPL_BUILD + pmuclk_init(priv->pmucru); +#endif return 0; } static int rk3399_pmuclk_ofdata_to_platdata(struct udevice *dev) { +#if !CONFIG_IS_ENABLED(OF_PLATDATA) struct rk3399_pmuclk_priv *priv = dev_get_priv(dev); priv->pmucru = (struct rk3399_pmucru *)dev_get_addr(dev); - +#endif return 0; } @@ -969,11 +1039,14 @@ static const struct udevice_id rk3399_pmuclk_ids[] = { }; U_BOOT_DRIVER(rockchip_rk3399_pmuclk) = { - .name = "pmuclk_rk3399", + .name = "rockchip_rk3399_pmucru", .id = UCLASS_CLK, .of_match = rk3399_pmuclk_ids, .priv_auto_alloc_size = sizeof(struct rk3399_pmuclk_priv), .ofdata_to_platdata = rk3399_pmuclk_ofdata_to_platdata, .ops = &rk3399_pmuclk_ops, .probe = rk3399_pmuclk_probe, +#if CONFIG_IS_ENABLED(OF_PLATDATA) + .platdata_auto_alloc_size = sizeof(struct rk3399_pmuclk_plat), +#endif }; |