From 5ae2fd97242d4eea97aeabe8801bb8592b37a8fe Mon Sep 17 00:00:00 2001 From: Kever Yang Date: Mon, 13 Feb 2017 17:38:56 +0800 Subject: rockchip: clk: rk3399: update driver for spl Add ddr clock setting, add rockchip_get_pmucru API, and enable of-platdata support. Signed-off-by: Kever Yang Reviewed-by: Simon Glass Added rockchip tag and fix pmuclk_init() build warning: Signed-off-by: Simon Glass --- drivers/clk/rockchip/clk_rk3399.c | 91 +++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 9 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/rockchip/clk_rk3399.c b/drivers/clk/rockchip/clk_rk3399.c index 2e87e4b62d5..922ce7e5491 100644 --- a/drivers/clk/rockchip/clk_rk3399.c +++ b/drivers/clk/rockchip/clk_rk3399.c @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -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 }; -- cgit v1.2.3 From 27326c7ee269ff351bba8c2461e19f29d66b6a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20St=C3=BCbner?= Date: Sat, 18 Feb 2017 19:46:21 +0100 Subject: dm: allow limiting pre-reloc markings to spl or tpl Right now the u-boot,dm-pre-reloc flag will make each marked node always appear in both spl and tpl. But systems needing an additional tpl might have special constraints for each, like the spl needing to be very tiny. So introduce two additional flags to mark nodes for only spl or tpl environments and introduce a function dm_fdt_pre_reloc to automate the necessary checks in code instances checking for pre-relocation flags. The behaviour of the original flag stays untouched and still marks a node for both spl and tpl. Signed-off-by: Heiko Stuebner Reviewed-by: Simon Glass Tested-by: Kever Yang --- drivers/clk/at91/pmc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c index c73156a0df1..fcd693a2f6f 100644 --- a/drivers/clk/at91/pmc.c +++ b/drivers/clk/at91/pmc.c @@ -10,6 +10,7 @@ #include #include #include +#include #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 -- cgit v1.2.3 From 6496498a626dbde4d0ed96406e4efee9ff2ab0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20St=C3=BCbner?= Date: Sat, 18 Feb 2017 19:46:22 +0100 Subject: rockchip: clk: rk3288: limit gpll and cpll init to SPL build The gpll and cpll init values are only used in rk_clk_init in the SPL and therefore produce compile time warnings in regular uboot builds. Fix that with an #ifdef. Signed-off-by: Heiko Stuebner Acked-by: Simon Glass Added rockchip tag: Signed-off-by: Simon Glass --- drivers/clk/rockchip/clk_rk3288.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/clk') diff --git a/drivers/clk/rockchip/clk_rk3288.c b/drivers/clk/rockchip/clk_rk3288.c index d15504c3aae..78356766a78 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) -- cgit v1.2.3 From dcdd32788a03bd5e6dde79384eef0217a8e14b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20St=C3=BCbner?= Date: Sat, 18 Feb 2017 19:46:34 +0100 Subject: rockchip: rk3188: Add clock driver Add a driver for setting up and modifying the various PLLs and peripheral clocks on the RK3188. Signed-off-by: Heiko Stuebner Reviewed-by: Simon Glass Tested-by: Kever Yang --- drivers/clk/rockchip/Makefile | 1 + drivers/clk/rockchip/clk_rk3188.c | 527 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 528 insertions(+) create mode 100644 drivers/clk/rockchip/clk_rk3188.c (limited to 'drivers/clk') diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index 1f8e41739d6..b66e81e810a 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -5,5 +5,6 @@ # obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o +obj-$(CONFIG_ROCKCHIP_RK3188) += clk_rk3188.o obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.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 00000000000..459649f7248 --- /dev/null +++ b/drivers/clk/rockchip/clk_rk3188.c @@ -0,0 +1,527 @@ +/* + * (C) Copyright 2015 Google, Inc + * (C) Copyright 2016 Heiko Stuebner + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, +}; -- cgit v1.2.3 From 41793000d77172a7472aad256049473c00e29dc5 Mon Sep 17 00:00:00 2001 From: Kever Yang Date: Thu, 23 Feb 2017 15:37:52 +0800 Subject: rockchip: rk3328: add clock driver Add rk3328 clock driver and cru structure definition. Signed-off-by: William Zhang Signed-off-by: Kever Yang --- drivers/clk/rockchip/Makefile | 1 + drivers/clk/rockchip/clk_rk3328.c | 581 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 582 insertions(+) create mode 100644 drivers/clk/rockchip/clk_rk3328.c (limited to 'drivers/clk') diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index b66e81e810a..1091a76f055 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -7,4 +7,5 @@ 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_rk3328.c b/drivers/clk/rockchip/clk_rk3328.c new file mode 100644 index 00000000000..0ff1e30bb59 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, +}; -- cgit v1.2.3