From patchwork Fri Jul 22 21:51:10 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Heiko_St=C3=BCbner?= X-Patchwork-Id: 651774 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id 3rx4BV1MZQz9sdg for ; Sat, 23 Jul 2016 07:52:34 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id EDEC3B38C8; Fri, 22 Jul 2016 23:52:11 +0200 (CEST) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id gnG8cKjhMmaI; Fri, 22 Jul 2016 23:52:11 +0200 (CEST) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 4D285B38CC; Fri, 22 Jul 2016 23:51:55 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 4DF8BA76EB for ; Fri, 22 Jul 2016 23:51:31 +0200 (CEST) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Rl0H1a1JeZtT for ; Fri, 22 Jul 2016 23:51:31 +0200 (CEST) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from gloria.sntech.de (gloria.sntech.de [95.129.55.99]) by theia.denx.de (Postfix) with ESMTPS id 07379B38A4 for ; Fri, 22 Jul 2016 23:51:28 +0200 (CEST) Received: from ip9234aa3a.dynamic.kabel-deutschland.de ([146.52.170.58] helo=diego.sntech) by gloria.sntech.de with esmtpsa (TLS1.1:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.80) (envelope-from ) id 1bQiLv-0008C6-Ed; Fri, 22 Jul 2016 23:51:28 +0200 From: Heiko Stuebner To: sjg@chromium.org Date: Fri, 22 Jul 2016 23:51:10 +0200 Message-Id: <1469224272-17220-7-git-send-email-heiko@sntech.de> X-Mailer: git-send-email 2.8.1 In-Reply-To: <1469224272-17220-1-git-send-email-heiko@sntech.de> References: <1469224272-17220-1-git-send-email-heiko@sntech.de> Cc: u-boot@lists.denx.de Subject: [U-Boot] [PATCH v2 6/8] rockchip: rk3188: Add clock driver X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.15 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" Add a driver for setting up and modifying the various PLLs and peripheral clocks on the RK3188. Signed-off-by: Heiko Stuebner Acked-by: Simon Glass --- arch/arm/include/asm/arch-rockchip/cru_rk3188.h | 183 +++++++++ drivers/clk/rockchip/Makefile | 1 + drivers/clk/rockchip/clk_rk3188.c | 493 ++++++++++++++++++++++++ 3 files changed, 677 insertions(+) create mode 100644 arch/arm/include/asm/arch-rockchip/cru_rk3188.h create mode 100644 drivers/clk/rockchip/clk_rk3188.c diff --git a/arch/arm/include/asm/arch-rockchip/cru_rk3188.h b/arch/arm/include/asm/arch-rockchip/cru_rk3188.h new file mode 100644 index 0000000..8d1daec --- /dev/null +++ b/arch/arm/include/asm/arch-rockchip/cru_rk3188.h @@ -0,0 +1,183 @@ +/* + * (C) Copyright 2016 Heiko Stuebner + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef _ASM_ARCH_CRU_RK3188_H +#define _ASM_ARCH_CRU_RK3188_H + +#define OSC_HZ (24 * 1000 * 1000) + +#define APLL_HZ (1608 * 1000000) +#define GPLL_HZ (594 * 1000000) +#define CPLL_HZ (384 * 1000000) + +/* The SRAM is clocked off aclk_cpu, so we want to max it out for boot speed */ +#define CPU_ACLK_HZ 297000000 +#define CPU_HCLK_HZ 148500000 +#define CPU_PCLK_HZ 74250000 +#define CPU_H2P_HZ 74250000 + +#define PERI_ACLK_HZ 148500000 +#define PERI_HCLK_HZ 148500000 +#define PERI_PCLK_HZ 74250000 + +struct rk3188_cru { + struct rk3188_pll { + u32 con0; + u32 con1; + u32 con2; + u32 con3; + } pll[4]; + u32 cru_mode_con; + u32 cru_clksel_con[35]; + u32 cru_clkgate_con[10]; + u32 reserved1[2]; + u32 cru_glb_srst_fst_value; + u32 cru_glb_srst_snd_value; + u32 reserved2[2]; + u32 cru_softrst_con[9]; + u32 cru_misc_con; + u32 reserved3[2]; + u32 cru_glb_cnt_th; +}; +check_member(rk3188_cru, cru_glb_cnt_th, 0x0140); + +/* CRU_CLKSEL0_CON */ +enum { + /* a9_core_div: core = core_src / (a9_core_div + 1) */ + A9_CORE_DIV_SHIFT = 9, + A9_CORE_DIV_MASK = 0x1f, + CORE_PLL_SHIFT = 8, + CORE_PLL_MASK = 1, + CORE_PLL_SELECT_APLL = 0, + CORE_PLL_SELECT_GPLL, + + /* core peri div: core:core_peri = 2:1, 4:1, 8:1 or 16:1 */ + CORE_PERI_DIV_SHIFT = 6, + CORE_PERI_DIV_MASK = 3, + + /* aclk_cpu pll selection */ + CPU_ACLK_PLL_SHIFT = 5, + CPU_ACLK_PLL_MASK = 1, + CPU_ACLK_PLL_SELECT_APLL = 0, + CPU_ACLK_PLL_SELECT_GPLL, + + /* a9_cpu_div: aclk_cpu = cpu_src / (a9_cpu_div + 1) */ + A9_CPU_DIV_SHIFT = 0, + A9_CPU_DIV_MASK = 0x1f, +}; + +/* CRU_CLKSEL1_CON */ +enum { + /* ahb2apb_pclk_div: hclk_cpu:pclk_cpu = 1:1, 2:1 or 4:1 */ + AHB2APB_DIV_SHIFT = 14, + AHB2APB_DIV_MASK = 3, + + /* cpu_pclk_div: aclk_cpu:pclk_cpu = 1:1, 2:1, 4:1 or 8:1 */ + CPU_PCLK_DIV_SHIFT = 12, + CPU_PCLK_DIV_MASK = 3, + + /* cpu_hclk_div: aclk_cpu:hclk_cpu = 1:1, 2:1 or 4:1 */ + CPU_HCLK_DIV_SHIFT = 8, + CPU_HCLK_DIV_MASK = 3, + + /* core_aclk_div: cire:aclk_core = 1:1, 2:1, 3:1, 4:1 or 8:1 */ + CORE_ACLK_DIV_SHIFT = 3, + CORE_ACLK_DIV_MASK = 7, +}; + +/* CRU_CLKSEL10_CON */ +enum { + PERI_SEL_PLL_MASK = 1, + PERI_SEL_PLL_SHIFT = 15, + PERI_SEL_CPLL = 0, + PERI_SEL_GPLL, + + /* peri pclk div: aclk_bus:pclk_bus = 1:1, 2:1, 4:1 or 8:1 */ + PERI_PCLK_DIV_SHIFT = 12, + PERI_PCLK_DIV_MASK = 3, + + /* peripheral bus hclk div:aclk_bus: hclk_bus = 1:1, 2:1 or 4:1 */ + PERI_HCLK_DIV_SHIFT = 8, + PERI_HCLK_DIV_MASK = 3, + + /* peri aclk div: aclk_peri = periph_src / (peri_aclk_div + 1) */ + PERI_ACLK_DIV_SHIFT = 0, + PERI_ACLK_DIV_MASK = 0x1f, +}; +/* CRU_CLKSEL11_CON */ +enum { + HSICPHY_DIV_SHIFT = 8, + HSICPHY_DIV_MASK = 0x3f, + + MMC0_DIV_SHIFT = 0, + MMC0_DIV_MASK = 0x3f, +}; + +/* CRU_CLKSEL12_CON */ +enum { + UART_PLL_SHIFT = 15, + UART_PLL_MASK = 1, + UART_PLL_SELECT_GENERAL = 0, + UART_PLL_SELECT_CODEC, + + EMMC_DIV_SHIFT = 8, + EMMC_DIV_MASK = 0x3f, + + SDIO_DIV_SHIFT = 0, + SDIO_DIV_MASK = 0x3f, +}; + +/* CRU_CLKSEL25_CON */ +enum { + SPI1_DIV_SHIFT = 8, + SPI1_DIV_MASK = 0x7f, + + SPI0_DIV_SHIFT = 0, + SPI0_DIV_MASK = 0x7f, +}; + +/* CRU_MODE_CON */ +enum { + GPLL_MODE_SHIFT = 12, + GPLL_MODE_MASK = 3, + GPLL_MODE_SLOW = 0, + GPLL_MODE_NORMAL, + GPLL_MODE_DEEP, + + CPLL_MODE_SHIFT = 8, + CPLL_MODE_MASK = 3, + CPLL_MODE_SLOW = 0, + CPLL_MODE_NORMAL, + CPLL_MODE_DEEP, + + DPLL_MODE_SHIFT = 4, + DPLL_MODE_MASK = 3, + DPLL_MODE_SLOW = 0, + DPLL_MODE_NORMAL, + DPLL_MODE_DEEP, + + APLL_MODE_SHIFT = 0, + APLL_MODE_MASK = 3, + APLL_MODE_SLOW = 0, + APLL_MODE_NORMAL, + APLL_MODE_DEEP, +}; + +/* CRU_APLL_CON0 */ +enum { + CLKR_SHIFT = 8, + CLKR_MASK = 0x3f, + + CLKOD_SHIFT = 0, + CLKOD_MASK = 0x3f, +}; + +/* CRU_APLL_CON1 */ +enum { + CLKF_SHIFT = 0, + CLKF_MASK = 0x1fff, +}; + +#endif diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index acfd858..cf922b4 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -5,4 +5,5 @@ # obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o +obj-$(CONFIG_ROCKCHIP_RK3188) += clk_rk3188.o obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o diff --git a/drivers/clk/rockchip/clk_rk3188.c b/drivers/clk/rockchip/clk_rk3188.c new file mode 100644 index 0000000..6a6f4c2 --- /dev/null +++ b/drivers/clk/rockchip/clk_rk3188.c @@ -0,0 +1,493 @@ +/* + * (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 rk3188_clk_priv { + struct rk3188_grf *grf; + struct rk3188_cru *cru; + ulong rate; + bool has_bwadj; +}; + +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 */ +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 rk3188_clk_priv *priv; + struct udevice *dev; + int ret; + + ret = rockchip_get_clk(&dev); + if (ret) + return ERR_PTR(ret); + + priv = dev_get_priv(dev); + + return priv->cru; +} + +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 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); +} + +/* 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); +} + +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 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; + +#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 + + /* we don't have a spl yet, so call rkclk_init at the regular time */ + rkclk_init(priv->cru, priv->grf, priv->has_bwadj); + + 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, +};