diff mbox

[U-Boot,9/9] rockchip: rk3188: Add clock driver

Message ID 1468621041-11487-10-git-send-email-heiko@sntech.de
State Superseded
Delegated to: Simon Glass
Headers show

Commit Message

Heiko Stuebner July 15, 2016, 10:17 p.m. UTC
Add a driver for setting up and modifying the various PLLs and peripheral
clocks on the RK3188.

Signed-off-by: Heiko Stuebner <heiko@sntech.de>
---
 arch/arm/include/asm/arch-rockchip/cru_rk3188.h | 186 ++++++++++
 drivers/clk/Makefile                            |   1 +
 drivers/clk/clk_rk3188.c                        | 464 ++++++++++++++++++++++++
 3 files changed, 651 insertions(+)
 create mode 100644 arch/arm/include/asm/arch-rockchip/cru_rk3188.h
 create mode 100644 drivers/clk/clk_rk3188.c

Comments

Simon Glass July 17, 2016, 2:13 p.m. UTC | #1
Hi Heiko,

On 15 July 2016 at 16:17, Heiko Stuebner <heiko@sntech.de> wrote:
> Add a driver for setting up and modifying the various PLLs and peripheral
> clocks on the RK3188.
>
> Signed-off-by: Heiko Stuebner <heiko@sntech.de>
> ---
>  arch/arm/include/asm/arch-rockchip/cru_rk3188.h | 186 ++++++++++
>  drivers/clk/Makefile                            |   1 +
>  drivers/clk/clk_rk3188.c                        | 464 ++++++++++++++++++++++++
>  3 files changed, 651 insertions(+)
>  create mode 100644 arch/arm/include/asm/arch-rockchip/cru_rk3188.h
>  create mode 100644 drivers/clk/clk_rk3188.c

Could you add a patch to move these files into a drivers/clk/rockchip directory?

> new file mode 100644
> index 0000000..4c28393
> --- /dev/null
> +++ b/drivers/clk/clk_rk3188.c
> @@ -0,0 +1,465 @@
> +/*
> + * (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 <errno.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>
> +
> +DECLARE_GLOBAL_DATA_PTR;
> +
> +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 = uclass_get_device_by_name(UCLASS_CLK,
> +                                       "clock-controller@20000000", &dev);

This seems odd. Could you use uclass_get_device(UCLASS_CLK, 0, .&dev) ?

> +       if (ret)
> +               return ERR_PTR(ret);
> +
> +       priv = dev_get_priv(dev);
> +
> +       return priv->cru;
> +}
> +
> +static int rkclk_set_pll(struct 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 inline unsigned int log2(unsigned int value)

Hmm this should go in a common file. Perhaps bitfield.h or common.h?

> +{
> +       return fls(value) - 1;
> +}
> +
> +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 = log2(CPU_ACLK_HZ / CPU_HCLK_HZ);
> +       assert((1 << hclk_div) * CPU_HCLK_HZ == CPU_ACLK_HZ && hclk_div < 0x3);
> +       pclk_div = log2(CPU_ACLK_HZ / CPU_PCLK_HZ);
> +       assert((1 << pclk_div) * CPU_PCLK_HZ == CPU_ACLK_HZ && pclk_div < 0x4);
> +       h2p_div = log2(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 = log2(PERI_ACLK_HZ / PERI_HCLK_HZ);
> +       assert((1 << hclk_div) * PERI_HCLK_HZ ==
> +               PERI_ACLK_HZ && (hclk_div < 0x4));
> +
> +       pclk_div = log2(PERI_ACLK_HZ / PERI_PCLK_HZ);
> +       assert((1 << pclk_div) * PERI_PCLK_HZ ==
> +               PERI_ACLK_HZ && (pclk_div < 0x4));
> +
> +       rk_clrsetreg(&cru->cru_clksel_con[10],
> +                    PERI_PCLK_DIV_MASK << PERI_PCLK_DIV_SHIFT |
> +                    PERI_HCLK_DIV_MASK << PERI_HCLK_DIV_SHIFT |
> +                    PERI_ACLK_DIV_MASK << PERI_ACLK_DIV_SHIFT,
> +                    PERI_SEL_GPLL << PERI_SEL_PLL_SHIFT |
> +                    pclk_div << PERI_PCLK_DIV_SHIFT |
> +                    hclk_div << PERI_HCLK_DIV_SHIFT |
> +                    aclk_div << PERI_ACLK_DIV_SHIFT);
> +
> +       /* PLL enter normal-mode */
> +       rk_clrsetreg(&cru->cru_mode_con,
> +                    GPLL_MODE_MASK << GPLL_MODE_SHIFT |
> +                    CPLL_MODE_MASK << CPLL_MODE_SHIFT,
> +                    GPLL_MODE_NORMAL << GPLL_MODE_SHIFT |
> +                    CPLL_MODE_NORMAL << CPLL_MODE_SHIFT);
> +}
> +
> +/* 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_probe(struct udevice *dev)
> +{
> +       struct rk3188_clk_priv *priv = dev_get_priv(dev);
> +
> +       priv->cru = (struct rk3188_cru *)dev_get_addr(dev);
> +       priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
> +       priv->has_bwadj = of_device_is_compatible(dev, "rockchip,rk3188a-cru")
> +                       ? 1 : 0;

You should add a .data member to your udevice_id array below using a
two-member enum, and check dev_get_driver_data() here.

> +
> +       /* 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" },
> +       { .compatible = "rockchip,rk3188a-cru" },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(clk_rk3188) = {
> +       .name           = "clk_rk3188",
> +       .id             = UCLASS_CLK,
> +       .of_match       = rk3188_clk_ids,
> +       .priv_auto_alloc_size = sizeof(struct rk3188_clk_priv),
> +       .ops            = &rk3188_clk_ops,
> +       .bind           = rk3188_clk_bind,
> +       .probe          = rk3188_clk_probe,
> +};
> --
> 2.8.0.rc3
>
Heiko Stuebner July 17, 2016, 3:33 p.m. UTC | #2
Am Sonntag, 17. Juli 2016, 08:13:58 schrieb Simon Glass:
> Hi Heiko,
> 
> On 15 July 2016 at 16:17, Heiko Stuebner <heiko@sntech.de> wrote:
> > Add a driver for setting up and modifying the various PLLs and peripheral
> > clocks on the RK3188.
> > 
> > Signed-off-by: Heiko Stuebner <heiko@sntech.de>
> > ---
> > 
> >  arch/arm/include/asm/arch-rockchip/cru_rk3188.h | 186 ++++++++++
> >  drivers/clk/Makefile                            |   1 +
> >  drivers/clk/clk_rk3188.c                        | 464
> >  ++++++++++++++++++++++++ 3 files changed, 651 insertions(+)
> >  create mode 100644 arch/arm/include/asm/arch-rockchip/cru_rk3188.h
> >  create mode 100644 drivers/clk/clk_rk3188.c
> 
> Could you add a patch to move these files into a drivers/clk/rockchip
> directory?

ok

> > new file mode 100644
> > index 0000000..4c28393
> > --- /dev/null
> > +++ b/drivers/clk/clk_rk3188.c
> > @@ -0,0 +1,465 @@
> > +/*
> > + * (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 <errno.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>
> > +
> > +DECLARE_GLOBAL_DATA_PTR;
> > +
> > +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 = uclass_get_device_by_name(UCLASS_CLK,
> > +                                       "clock-controller@20000000",
> > &dev);
> 
> This seems odd. Could you use uclass_get_device(UCLASS_CLK, 0, .&dev) ?

Index 0 actually gets me the 24MHz oscillator fixed clock :-), which is why I 
switched to the by-name variant to not depend on some dts or uclass ordering.
I'm wondering how that works on the other socs.

> 
> > +       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 inline unsigned int log2(unsigned int value)
> 
> Hmm this should go in a common file. Perhaps bitfield.h or common.h?

it looks like uboot is already carrying ilog2() in include/linux/log2.h ?

[...]

> > +static int rk3188_clk_probe(struct udevice *dev)
> > +{
> > +       struct rk3188_clk_priv *priv = dev_get_priv(dev);
> > +
> > +       priv->cru = (struct rk3188_cru *)dev_get_addr(dev);
> > +       priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
> > +       priv->has_bwadj = of_device_is_compatible(dev,
> > "rockchip,rk3188a-cru") +                       ? 1 : 0;
> 
> You should add a .data member to your udevice_id array below using a
> two-member enum, and check dev_get_driver_data() here.

ah, nice to know. I was wondering if and how uboot was handling .data stuff 
but my short skimming through the sources didn't reveal that. Will change.


Heiko
Simon Glass July 17, 2016, 3:48 p.m. UTC | #3
Hi Heiko,

On 17 July 2016 at 09:33, Heiko Stübner <heiko@sntech.de> wrote:
> Am Sonntag, 17. Juli 2016, 08:13:58 schrieb Simon Glass:
>> Hi Heiko,
>>
>> On 15 July 2016 at 16:17, Heiko Stuebner <heiko@sntech.de> wrote:
>> > Add a driver for setting up and modifying the various PLLs and peripheral
>> > clocks on the RK3188.
>> >
>> > Signed-off-by: Heiko Stuebner <heiko@sntech.de>
>> > ---
>> >
>> >  arch/arm/include/asm/arch-rockchip/cru_rk3188.h | 186 ++++++++++
>> >  drivers/clk/Makefile                            |   1 +
>> >  drivers/clk/clk_rk3188.c                        | 464
>> >  ++++++++++++++++++++++++ 3 files changed, 651 insertions(+)
>> >  create mode 100644 arch/arm/include/asm/arch-rockchip/cru_rk3188.h
>> >  create mode 100644 drivers/clk/clk_rk3188.c
>>
>> Could you add a patch to move these files into a drivers/clk/rockchip
>> directory?
>
> ok
>
>> > new file mode 100644
>> > index 0000000..4c28393
>> > --- /dev/null
>> > +++ b/drivers/clk/clk_rk3188.c
>> > @@ -0,0 +1,465 @@
>> > +/*
>> > + * (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 <errno.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>
>> > +
>> > +DECLARE_GLOBAL_DATA_PTR;
>> > +
>> > +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 = uclass_get_device_by_name(UCLASS_CLK,
>> > +                                       "clock-controller@20000000",
>> > &dev);
>>
>> This seems odd. Could you use uclass_get_device(UCLASS_CLK, 0, .&dev) ?
>
> Index 0 actually gets me the 24MHz oscillator fixed clock :-), which is why I
> switched to the by-name variant to not depend on some dts or uclass ordering.
> I'm wondering how that works on the other socs.

I suspect this might have become broken by Stephen's clock changes. I
had a bit of a look at this but have not resolved it yet. For example
on firefly, HDMI does not work now.

There is this:

enum rk_clk_id {
CLK_OSC,
CLK_ARM,
CLK_DDR,
CLK_CODEC,
CLK_GENERAL,
CLK_NEW,

CLK_COUNT,
};

and it used to check against the platform data (see rkclk_get_clk() in
v2016.05). I'll have a think about it.

>
>>
>> > +       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 inline unsigned int log2(unsigned int value)
>>
>> Hmm this should go in a common file. Perhaps bitfield.h or common.h?
>
> it looks like uboot is already carrying ilog2() in include/linux/log2.h ?

Yes.

>
> [...]
>
>> > +static int rk3188_clk_probe(struct udevice *dev)
>> > +{
>> > +       struct rk3188_clk_priv *priv = dev_get_priv(dev);
>> > +
>> > +       priv->cru = (struct rk3188_cru *)dev_get_addr(dev);
>> > +       priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
>> > +       priv->has_bwadj = of_device_is_compatible(dev,
>> > "rockchip,rk3188a-cru") +                       ? 1 : 0;
>>
>> You should add a .data member to your udevice_id array below using a
>> two-member enum, and check dev_get_driver_data() here.
>
> ah, nice to know. I was wondering if and how uboot was handling .data stuff
> but my short skimming through the sources didn't reveal that. Will change.

I'm AFK for a few hours now.

Regards,
Simon
Simon Glass July 18, 2016, 1:31 p.m. UTC | #4
Hi Heiko,

On 17 July 2016 at 09:48, Simon Glass <sjg@chromium.org> wrote:
> Hi Heiko,
>
> On 17 July 2016 at 09:33, Heiko Stübner <heiko@sntech.de> wrote:
>> Am Sonntag, 17. Juli 2016, 08:13:58 schrieb Simon Glass:
>>> Hi Heiko,
>>>
>>> On 15 July 2016 at 16:17, Heiko Stuebner <heiko@sntech.de> wrote:
>>> > Add a driver for setting up and modifying the various PLLs and peripheral
>>> > clocks on the RK3188.
>>> >
>>> > Signed-off-by: Heiko Stuebner <heiko@sntech.de>
>>> > ---
>>> >
>>> >  arch/arm/include/asm/arch-rockchip/cru_rk3188.h | 186 ++++++++++
>>> >  drivers/clk/Makefile                            |   1 +
>>> >  drivers/clk/clk_rk3188.c                        | 464
>>> >  ++++++++++++++++++++++++ 3 files changed, 651 insertions(+)
>>> >  create mode 100644 arch/arm/include/asm/arch-rockchip/cru_rk3188.h
>>> >  create mode 100644 drivers/clk/clk_rk3188.c
>>>
>>> Could you add a patch to move these files into a drivers/clk/rockchip
>>> directory?
>>
>> ok
>>
>>> > new file mode 100644
>>> > index 0000000..4c28393
>>> > --- /dev/null
>>> > +++ b/drivers/clk/clk_rk3188.c
>>> > @@ -0,0 +1,465 @@
>>> > +/*
>>> > + * (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 <errno.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>
>>> > +
>>> > +DECLARE_GLOBAL_DATA_PTR;
>>> > +
>>> > +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 = uclass_get_device_by_name(UCLASS_CLK,
>>> > +                                       "clock-controller@20000000",
>>> > &dev);
>>>
>>> This seems odd. Could you use uclass_get_device(UCLASS_CLK, 0, .&dev) ?
>>
>> Index 0 actually gets me the 24MHz oscillator fixed clock :-), which is why I
>> switched to the by-name variant to not depend on some dts or uclass ordering.
>> I'm wondering how that works on the other socs.
>
> I suspect this might have become broken by Stephen's clock changes. I
> had a bit of a look at this but have not resolved it yet. For example
> on firefly, HDMI does not work now.
>
> There is this:
>
> enum rk_clk_id {
> CLK_OSC,
> CLK_ARM,
> CLK_DDR,
> CLK_CODEC,
> CLK_GENERAL,
> CLK_NEW,
>
> CLK_COUNT,
> };
>
> and it used to check against the platform data (see rkclk_get_clk() in
> v2016.05). I'll have a think about it.
>
>>

I found a problem here...please see:

http://patchwork.ozlabs.org/patch/649283/

Regards,
Simon

[...]
diff mbox

Patch

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..1073613
--- /dev/null
+++ b/arch/arm/include/asm/arch-rockchip/cru_rk3188.h
@@ -0,0 +1,183 @@ 
+/*
+ * (C) Copyright 2016 Heiko Stuebner <heiko@sntech.de>
+ *
+ * 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/Makefile b/drivers/clk/Makefile
index f7a8891..9455729 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -7,6 +7,7 @@ 
 
 obj-$(CONFIG_CLK) += clk-uclass.o clk_fixed_rate.o
 obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o
+obj-$(CONFIG_ROCKCHIP_RK3188) += clk_rk3188.o
 obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o
 obj-$(CONFIG_SANDBOX) += clk_sandbox.o
 obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o
diff --git a/drivers/clk/clk_rk3188.c b/drivers/clk/clk_rk3188.c
new file mode 100644
index 0000000..4c28393
--- /dev/null
+++ b/drivers/clk/clk_rk3188.c
@@ -0,0 +1,465 @@ 
+/*
+ * (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 <errno.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>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+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 = uclass_get_device_by_name(UCLASS_CLK,
+					"clock-controller@20000000", &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 inline unsigned int log2(unsigned int value)
+{
+	return fls(value) - 1;
+}
+
+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 = log2(CPU_ACLK_HZ / CPU_HCLK_HZ);
+	assert((1 << hclk_div) * CPU_HCLK_HZ == CPU_ACLK_HZ && hclk_div < 0x3);
+	pclk_div = log2(CPU_ACLK_HZ / CPU_PCLK_HZ);
+	assert((1 << pclk_div) * CPU_PCLK_HZ == CPU_ACLK_HZ && pclk_div < 0x4);
+	h2p_div = log2(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 = log2(PERI_ACLK_HZ / PERI_HCLK_HZ);
+	assert((1 << hclk_div) * PERI_HCLK_HZ ==
+		PERI_ACLK_HZ && (hclk_div < 0x4));
+
+	pclk_div = log2(PERI_ACLK_HZ / PERI_PCLK_HZ);
+	assert((1 << pclk_div) * PERI_PCLK_HZ ==
+		PERI_ACLK_HZ && (pclk_div < 0x4));
+
+	rk_clrsetreg(&cru->cru_clksel_con[10],
+		     PERI_PCLK_DIV_MASK << PERI_PCLK_DIV_SHIFT |
+		     PERI_HCLK_DIV_MASK << PERI_HCLK_DIV_SHIFT |
+		     PERI_ACLK_DIV_MASK << PERI_ACLK_DIV_SHIFT,
+		     PERI_SEL_GPLL << PERI_SEL_PLL_SHIFT |
+		     pclk_div << PERI_PCLK_DIV_SHIFT |
+		     hclk_div << PERI_HCLK_DIV_SHIFT |
+		     aclk_div << PERI_ACLK_DIV_SHIFT);
+
+	/* PLL enter normal-mode */
+	rk_clrsetreg(&cru->cru_mode_con,
+		     GPLL_MODE_MASK << GPLL_MODE_SHIFT |
+		     CPLL_MODE_MASK << CPLL_MODE_SHIFT,
+		     GPLL_MODE_NORMAL << GPLL_MODE_SHIFT |
+		     CPLL_MODE_NORMAL << CPLL_MODE_SHIFT);
+}
+
+/* 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_probe(struct udevice *dev)
+{
+	struct rk3188_clk_priv *priv = dev_get_priv(dev);
+
+	priv->cru = (struct rk3188_cru *)dev_get_addr(dev);
+	priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
+	priv->has_bwadj = of_device_is_compatible(dev, "rockchip,rk3188a-cru")
+			? 1 : 0;
+
+	/* 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" },
+	{ .compatible = "rockchip,rk3188a-cru" },
+	{ }
+};
+
+U_BOOT_DRIVER(clk_rk3188) = {
+	.name		= "clk_rk3188",
+	.id		= UCLASS_CLK,
+	.of_match	= rk3188_clk_ids,
+	.priv_auto_alloc_size = sizeof(struct rk3188_clk_priv),
+	.ops		= &rk3188_clk_ops,
+	.bind		= rk3188_clk_bind,
+	.probe		= rk3188_clk_probe,
+};