From: Andy Yan Date: Mon, 15 May 2017 09:49:56 +0000 (+0800) Subject: rockchip: rk3368: Add clock driver X-Git-Url: http://git.dujemihanovic.xyz/img/sics.gif?a=commitdiff_plain;h=d1dcf8527ececd1595d7ae6dc56c19bbcf0c537d;p=u-boot.git rockchip: rk3368: Add clock driver Add driver to setup the various PLLs and peripheral clocks on the RK3368. Signed-off-by: Andy Yan Reviewed-by: Simon Glass --- diff --git a/arch/arm/include/asm/arch-rockchip/cru_rk3368.h b/arch/arm/include/asm/arch-rockchip/cru_rk3368.h new file mode 100644 index 0000000000..4910ee7387 --- /dev/null +++ b/arch/arm/include/asm/arch-rockchip/cru_rk3368.h @@ -0,0 +1,124 @@ +/* + * (C) Copyright 2017 Rockchip Electronics Co., Ltd + * Author: Andy Yan + * SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef _ASM_ARCH_CRU_RK3368_H +#define _ASM_ARCH_CRU_RK3368_H + +#include + + +/* RK3368 clock numbers */ +enum rk3368_pll_id { + APLLB, + APLLL, + DPLL, + CPLL, + GPLL, + NPLL, + PLL_COUNT, +}; + +struct rk3368_cru { + struct rk3368_pll { + unsigned int con0; + unsigned int con1; + unsigned int con2; + unsigned int con3; + } pll[6]; + unsigned int reserved[0x28]; + unsigned int clksel_con[56]; + unsigned int reserved1[8]; + unsigned int clkgate_con[25]; + unsigned int reserved2[7]; + unsigned int glb_srst_fst_val; + unsigned int glb_srst_snd_val; + unsigned int reserved3[0x1e]; + unsigned int softrst_con[15]; + unsigned int reserved4[0x11]; + unsigned int misc_con; + unsigned int glb_cnt_th; + unsigned int glb_rst_con; + unsigned int glb_rst_st; + unsigned int reserved5[0x1c]; + unsigned int sdmmc_con[2]; + unsigned int sdio0_con[2]; + unsigned int sdio1_con[2]; + unsigned int emmc_con[2]; +}; +check_member(rk3368_cru, emmc_con[1], 0x41c); + +struct rk3368_clk_priv { + struct rk3368_cru *cru; + ulong rate; + bool has_bwadj; +}; + +enum { + /* PLL CON0 */ + PLL_NR_SHIFT = 8, + PLL_NR_MASK = GENMASK(13, 8), + PLL_OD_SHIFT = 0, + PLL_OD_MASK = GENMASK(3, 0), + + /* PLL CON1 */ + PLL_LOCK_STA = BIT(31), + PLL_NF_SHIFT = 0, + PLL_NF_MASK = GENMASK(12, 0), + + /* PLL CON2 */ + PLL_BWADJ_SHIFT = 0, + PLL_BWADJ_MASK = GENMASK(11, 0), + + /* PLL CON3 */ + PLL_MODE_SHIFT = 8, + PLL_MODE_MASK = GENMASK(9, 8), + PLL_MODE_SLOW = 0, + PLL_MODE_NORMAL = 1, + PLL_MODE_DEEP_SLOW = 3, + PLL_RESET_SHIFT = 5, + PLL_RESET = 1, + PLL_RESET_MASK = GENMASK(5, 5), + + /* CLKSEL12_CON */ + MCU_STCLK_DIV_SHIFT = 8, + MCU_STCLK_DIV_MASK = GENMASK(10, 8), + MCU_PLL_SEL_SHIFT = 7, + MCU_PLL_SEL_MASK = BIT(7), + MCU_PLL_SEL_CPLL = 0, + MCU_PLL_SEL_GPLL = 1, + MCU_CLK_DIV_SHIFT = 0, + MCU_CLK_DIV_MASK = GENMASK(4, 0), + + /* CLKSEL51_CON */ + MMC_PLL_SEL_SHIFT = 8, + MMC_PLL_SEL_MASK = GENMASK(9, 8), + MMC_PLL_SEL_CPLL = 0, + MMC_PLL_SEL_GPLL, + MMC_PLL_SEL_USBPHY_480M, + MMC_PLL_SEL_24M, + MMC_CLK_DIV_SHIFT = 0, + MMC_CLK_DIV_MASK = GENMASK(6, 0), + + /* SOFTRST1_CON */ + MCU_PO_SRST_MASK = BIT(13), + MCU_SYS_SRST_MASK = BIT(12), + + /* GLB_RST_CON */ + PMU_GLB_SRST_CTRL_SHIFT = 2, + PMU_GLB_SRST_CTRL_MASK = GENMASK(3, 2), + PMU_RST_BY_FST_GLB_SRST = 0, + PMU_RST_BY_SND_GLB_SRST = 1, + PMU_RST_DISABLE = 2, + WDT_GLB_SRST_CTRL_SHIFT = 1, + WDT_GLB_SRST_CTRL_MASK = BIT(1), + WDT_TRIGGER_SND_GLB_SRST = 0, + WDT_TRIGGER_FST_GLB_SRST = 1, + TSADC_GLB_SRST_CTRL_SHIFT = 0, + TSADC_GLB_SRST_CTRL_MASK = BIT(0), + TSADC_TRIGGER_SND_GLB_SRST = 0, + TSADC_TRIGGER_FST_GLB_SRST = 1, + +}; +#endif diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index 1091a76f05..8dc60f8923 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -8,4 +8,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_RK3368) += clk_rk3368.o obj-$(CONFIG_ROCKCHIP_RK3399) += clk_rk3399.o diff --git a/drivers/clk/rockchip/clk_rk3368.c b/drivers/clk/rockchip/clk_rk3368.c new file mode 100644 index 0000000000..52cad38446 --- /dev/null +++ b/drivers/clk/rockchip/clk_rk3368.c @@ -0,0 +1,291 @@ +/* + * (C) Copyright 2017 Rockchip Electronics Co., Ltd + * Author: Andy Yan + * 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 nr; + u32 nf; + u32 no; +}; + +#define OSC_HZ (24 * 1000 * 1000) +#define APLL_L_HZ (800 * 1000 * 1000) +#define APLL_B_HZ (816 * 1000 * 1000) +#define GPLL_HZ (576 * 1000 * 1000) +#define CPLL_HZ (400 * 1000 * 1000) + +#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__)); + +static const struct pll_div apll_l_init_cfg = PLL_DIVISORS(APLL_L_HZ, 12, 2); +static const struct pll_div apll_b_init_cfg = PLL_DIVISORS(APLL_B_HZ, 1, 2); +static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 1, 2); +static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 1, 6); + +/* Get pll rate by id */ +static uint32_t rkclk_pll_get_rate(struct rk3368_cru *cru, + enum rk3368_pll_id pll_id) +{ + uint32_t nr, no, nf; + uint32_t con; + struct rk3368_pll *pll = &cru->pll[pll_id]; + + con = readl(&pll->con3); + + switch ((con & PLL_MODE_MASK) >> PLL_MODE_SHIFT) { + case PLL_MODE_SLOW: + return OSC_HZ; + case PLL_MODE_NORMAL: + con = readl(&pll->con0); + no = ((con & PLL_OD_MASK) >> PLL_OD_SHIFT) + 1; + nr = ((con & PLL_NR_MASK) >> PLL_NR_SHIFT) + 1; + con = readl(&pll->con1); + nf = ((con & PLL_NF_MASK) >> PLL_NF_SHIFT) + 1; + + return (24 * nf / (nr * no)) * 1000000; + case PLL_MODE_DEEP_SLOW: + default: + return 32768; + } +} + +static int rkclk_set_pll(struct rk3368_cru *cru, enum rk3368_pll_id pll_id, + const struct pll_div *div, bool has_bwadj) +{ + struct rk3368_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 %p: nf=%d, nr=%d, no=%d, vco=%u Hz, output=%u Hz\n", + pll, div->nf, div->nr, div->no, vco_hz, output_hz); + + /* enter slow mode and reset pll */ + rk_clrsetreg(&pll->con3, PLL_MODE_MASK | PLL_RESET_MASK, + PLL_RESET << PLL_RESET_SHIFT); + + rk_clrsetreg(&pll->con0, PLL_NR_MASK | PLL_OD_MASK, + ((div->nr - 1) << PLL_NR_SHIFT) | + ((div->no - 1) << PLL_OD_SHIFT)); + writel((div->nf - 1) << PLL_NF_SHIFT, &pll->con1); + udelay(10); + + /* return from reset */ + rk_clrreg(&pll->con3, PLL_RESET_MASK); + + /* waiting for pll lock */ + while (!(readl(&pll->con1) & PLL_LOCK_STA)) + udelay(1); + + rk_clrsetreg(&pll->con3, PLL_MODE_MASK, + PLL_MODE_NORMAL << PLL_MODE_SHIFT); + + return 0; +} + +static void rkclk_init(struct rk3368_cru *cru) +{ + u32 apllb, aplll, dpll, cpll, gpll; + + rkclk_set_pll(cru, APLLB, &apll_b_init_cfg, false); + rkclk_set_pll(cru, APLLL, &apll_l_init_cfg, false); + rkclk_set_pll(cru, GPLL, &gpll_init_cfg, false); + rkclk_set_pll(cru, CPLL, &cpll_init_cfg, false); + + apllb = rkclk_pll_get_rate(cru, APLLB); + aplll = rkclk_pll_get_rate(cru, APLLL); + dpll = rkclk_pll_get_rate(cru, DPLL); + cpll = rkclk_pll_get_rate(cru, CPLL); + gpll = rkclk_pll_get_rate(cru, GPLL); + + debug("%s apllb(%d) apll(%d) dpll(%d) cpll(%d) gpll(%d)\n", + __func__, apllb, aplll, dpll, cpll, gpll); +} + +static ulong rk3368_mmc_get_clk(struct rk3368_cru *cru, uint clk_id) +{ + u32 div, con, con_id, rate; + u32 pll_rate; + + switch (clk_id) { + case SCLK_SDMMC: + con_id = 50; + break; + case SCLK_EMMC: + con_id = 51; + break; + case SCLK_SDIO0: + con_id = 48; + break; + default: + return -EINVAL; + } + + con = readl(&cru->clksel_con[con_id]); + switch ((con & MMC_PLL_SEL_MASK) >> MMC_PLL_SEL_SHIFT) { + case MMC_PLL_SEL_GPLL: + pll_rate = rkclk_pll_get_rate(cru, GPLL); + break; + case MMC_PLL_SEL_24M: + pll_rate = OSC_HZ; + break; + case MMC_PLL_SEL_CPLL: + case MMC_PLL_SEL_USBPHY_480M: + default: + return -EINVAL; + } + div = (con & MMC_CLK_DIV_MASK) >> MMC_CLK_DIV_SHIFT; + rate = DIV_TO_RATE(pll_rate, div); + + return rate >> 1; +} + +static ulong rk3368_mmc_set_clk(struct rk3368_cru *cru, + ulong clk_id, ulong rate) +{ + u32 div; + u32 con_id; + u32 gpll_rate = rkclk_pll_get_rate(cru, GPLL); + + div = RATE_TO_DIV(gpll_rate, rate << 1); + + switch (clk_id) { + case SCLK_SDMMC: + con_id = 50; + break; + case SCLK_EMMC: + con_id = 51; + break; + case SCLK_SDIO0: + con_id = 48; + break; + default: + return -EINVAL; + } + + if (div > 0x3f) { + div = RATE_TO_DIV(OSC_HZ, rate); + rk_clrsetreg(&cru->clksel_con[con_id], + MMC_PLL_SEL_MASK | MMC_CLK_DIV_MASK, + (MMC_PLL_SEL_24M << MMC_PLL_SEL_SHIFT) | + (div << MMC_CLK_DIV_SHIFT)); + } else { + rk_clrsetreg(&cru->clksel_con[con_id], + MMC_PLL_SEL_MASK | MMC_CLK_DIV_MASK, + (MMC_PLL_SEL_GPLL << MMC_PLL_SEL_SHIFT) | + div << MMC_CLK_DIV_SHIFT); + } + + return rk3368_mmc_get_clk(cru, clk_id); +} + +static ulong rk3368_clk_get_rate(struct clk *clk) +{ + struct rk3368_clk_priv *priv = dev_get_priv(clk->dev); + ulong rate = 0; + + debug("%s id:%ld\n", __func__, clk->id); + switch (clk->id) { + case HCLK_SDMMC: + case HCLK_EMMC: + rate = rk3368_mmc_get_clk(priv->cru, clk->id); + break; + default: + return -ENOENT; + } + + return rate; +} + +static ulong rk3368_clk_set_rate(struct clk *clk, ulong rate) +{ + struct rk3368_clk_priv *priv = dev_get_priv(clk->dev); + ulong ret = 0; + + debug("%s id:%ld rate:%ld\n", __func__, clk->id, rate); + switch (clk->id) { + case SCLK_SDMMC: + case SCLK_EMMC: + ret = rk3368_mmc_set_clk(priv->cru, clk->id, rate); + break; + default: + return -ENOENT; + } + + return ret; +} + +static struct clk_ops rk3368_clk_ops = { + .get_rate = rk3368_clk_get_rate, + .set_rate = rk3368_clk_set_rate, +}; + +static int rk3368_clk_probe(struct udevice *dev) +{ + struct rk3368_clk_priv *priv = dev_get_priv(dev); + + rkclk_init(priv->cru); + + return 0; +} + +static int rk3368_clk_ofdata_to_platdata(struct udevice *dev) +{ + struct rk3368_clk_priv *priv = dev_get_priv(dev); + + priv->cru = (struct rk3368_cru *)devfdt_get_addr(dev); + + return 0; +} + +static int rk3368_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, "rk3368_sysreset", "reset", &dev); + if (ret) + error("bind RK3368 reset driver failed: ret=%d\n", ret); + + return ret; +} + +static const struct udevice_id rk3368_clk_ids[] = { + { .compatible = "rockchip,rk3368-cru" }, + { } +}; + +U_BOOT_DRIVER(rockchip_rk3368_cru) = { + .name = "rockchip_rk3368_cru", + .id = UCLASS_CLK, + .of_match = rk3368_clk_ids, + .priv_auto_alloc_size = sizeof(struct rk3368_cru), + .ofdata_to_platdata = rk3368_clk_ofdata_to_platdata, + .ops = &rk3368_clk_ops, + .bind = rk3368_clk_bind, + .probe = rk3368_clk_probe, +};