diff options
Diffstat (limited to 'drivers')
37 files changed, 2650 insertions, 22 deletions
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index bb89fb918b..008ec100a1 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -6,4 +6,5 @@ # obj-$(CONFIG_CLK) += clk-uclass.o +obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o obj-$(CONFIG_SANDBOX) += clk_sandbox.o diff --git a/drivers/clk/clk_rk3288.c b/drivers/clk/clk_rk3288.c new file mode 100644 index 0000000000..54d49305b0 --- /dev/null +++ b/drivers/clk/clk_rk3288.c @@ -0,0 +1,618 @@ +/* + * (C) Copyright 2015 Google, Inc + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <errno.h> +#include <syscon.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/cru_rk3288.h> +#include <asm/arch/grf_rk3288.h> +#include <asm/arch/hardware.h> +#include <asm/arch/periph.h> +#include <dm/lists.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct rk3288_clk_plat { + enum rk_clk_id clk_id; +}; + +struct rk3288_clk_priv { + struct rk3288_grf *grf; + struct rk3288_cru *cru; + ulong rate; +}; + +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 = 27500000, + FREF_MAX_HZ = 2200U * 1000000, + FREF_MIN_HZ = 269 * 1000000, +}; + +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, + + /* CLKSEL1: pd bus clk pll sel: codec or general */ + PD_BUS_SEL_PLL_MASK = 15, + PD_BUS_SEL_CPLL = 0, + PD_BUS_SEL_GPLL, + + /* pd bus pclk div: pclk = pd_bus_aclk /(div + 1) */ + PD_BUS_PCLK_DIV_SHIFT = 12, + PD_BUS_PCLK_DIV_MASK = 7, + + /* pd bus hclk div: aclk_bus: hclk_bus = 1:1 or 2:1 or 4:1 */ + PD_BUS_HCLK_DIV_SHIFT = 8, + PD_BUS_HCLK_DIV_MASK = 3, + + /* pd bus aclk div: pd_bus_aclk = pd_bus_src_clk /(div0 * div1) */ + PD_BUS_ACLK_DIV0_SHIFT = 3, + PD_BUS_ACLK_DIV0_MASK = 0x1f, + PD_BUS_ACLK_DIV1_SHIFT = 0, + PD_BUS_ACLK_DIV1_MASK = 0x7, + + /* + * CLKSEL10 + * peripheral bus pclk div: + * aclk_bus: pclk_bus = 1:1 or 2:1 or 4:1 or 8:1 + */ + PERI_PCLK_DIV_SHIFT = 12, + PERI_PCLK_DIV_MASK = 7, + + /* peripheral bus hclk div: aclk_bus: hclk_bus = 1:1 or 2:1 or 4:1 */ + PERI_HCLK_DIV_SHIFT = 8, + PERI_HCLK_DIV_MASK = 3, + + /* + * peripheral bus aclk div: + * aclk_periph = periph_clk_src / (peri_aclk_div_con + 1) + */ + PERI_ACLK_DIV_SHIFT = 0, + PERI_ACLK_DIV_MASK = 0x1f, + + /* CLKSEL37 */ + DPLL_MODE_MASK = 0x3, + DPLL_MODE_SHIFT = 4, + DPLL_MODE_SLOW = 0, + DPLL_MODE_NORM, + + CPLL_MODE_MASK = 3, + CPLL_MODE_SHIFT = 8, + CPLL_MODE_SLOW = 0, + CPLL_MODE_NORM, + + GPLL_MODE_MASK = 3, + GPLL_MODE_SHIFT = 12, + GPLL_MODE_SLOW = 0, + GPLL_MODE_NORM, + + NPLL_MODE_MASK = 3, + NPLL_MODE_SHIFT = 14, + NPLL_MODE_SLOW = 0, + NPLL_MODE_NORM, + + SOCSTS_DPLL_LOCK = 1 << 5, + SOCSTS_APLL_LOCK = 1 << 6, + SOCSTS_CPLL_LOCK = 1 << 7, + SOCSTS_GPLL_LOCK = 1 << 8, + SOCSTS_NPLL_LOCK = 1 << 9, +}; + +#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); + +static int rkclk_set_pll(struct rk3288_cru *cru, enum rk_clk_id clk_id, + const struct pll_div *div) +{ + int pll_id = rk_pll_id(clk_id); + struct rk3288_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); + 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 rest */ + 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); + rk_clrsetreg(&pll->con2, PLL_BWADJ_MASK, (div->nf >> 1) - 1); + + udelay(10); + + /* return form rest */ + rk_clrreg(&pll->con3, 1 << PLL_RESET_SHIFT); + + return 0; +} + +static inline unsigned int log2(unsigned int value) +{ + return fls(value) - 1; +} + +static int rkclk_configure_ddr(struct rk3288_cru *cru, struct rk3288_grf *grf, + unsigned int hz) +{ + 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; + + debug("%s: cru=%p, grf=%p, hz=%u\n", __func__, cru, grf, hz); + 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, add to clock.c!"); + 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]); + + /* wait for pll lock */ + while (!(readl(&grf->soc_status[1]) & SOCSTS_DPLL_LOCK)) + udelay(1); + + /* PLL enter normal-mode */ + rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK << DPLL_MODE_SHIFT, + DPLL_MODE_NORM << DPLL_MODE_SHIFT); + + return 0; +} + +#ifdef CONFIG_SPL_BUILD +static void rkclk_init(struct rk3288_cru *cru, struct rk3288_grf *grf) +{ + u32 aclk_div; + u32 hclk_div; + u32 pclk_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); + rkclk_set_pll(cru, CLK_CODEC, &cpll_init_cfg); + + /* waiting for pll lock */ + while ((readl(&grf->soc_status[1]) & + (SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) != + (SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) + udelay(1); + + /* + * pd_bus clock pll source selection and + * set up dependent divisors for PCLK/HCLK and ACLK clocks. + */ + aclk_div = GPLL_HZ / PD_BUS_ACLK_HZ - 1; + assert((aclk_div + 1) * PD_BUS_ACLK_HZ == GPLL_HZ && aclk_div < 0x1f); + hclk_div = PD_BUS_ACLK_HZ / PD_BUS_HCLK_HZ - 1; + assert((hclk_div + 1) * PD_BUS_HCLK_HZ == + PD_BUS_ACLK_HZ && (hclk_div < 0x4) && (hclk_div != 0x2)); + + pclk_div = PD_BUS_ACLK_HZ / PD_BUS_PCLK_HZ - 1; + assert((pclk_div + 1) * PD_BUS_PCLK_HZ == + PD_BUS_ACLK_HZ && pclk_div < 0x7); + + rk_clrsetreg(&cru->cru_clksel_con[1], + PD_BUS_PCLK_DIV_MASK << PD_BUS_PCLK_DIV_SHIFT | + PD_BUS_HCLK_DIV_MASK << PD_BUS_HCLK_DIV_SHIFT | + PD_BUS_ACLK_DIV0_MASK << PD_BUS_ACLK_DIV0_SHIFT | + PD_BUS_ACLK_DIV1_MASK << PD_BUS_ACLK_DIV1_SHIFT, + pclk_div << PD_BUS_PCLK_DIV_SHIFT | + hclk_div << PD_BUS_HCLK_DIV_SHIFT | + aclk_div << PD_BUS_ACLK_DIV0_SHIFT | + 0 << 0); + + /* + * 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, + 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_NORM << GPLL_MODE_SHIFT | + GPLL_MODE_NORM << CPLL_MODE_SHIFT); +} +#endif + +/* Get pll rate by id */ +static uint32_t rkclk_pll_get_rate(struct rk3288_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 rk3288_pll *pll = &cru->pll[pll_id]; + static u8 clk_shift[CLK_COUNT] = { + 0xff, APLL_WORK_SHIFT, DPLL_WORK_SHIFT, CPLL_WORK_SHIFT, + GPLL_WORK_SHIFT, NPLL_WORK_SHIFT + }; + uint shift; + + con = readl(&cru->cru_mode_con); + shift = clk_shift[clk_id]; + switch ((con >> shift) & APLL_WORK_MASK) { + case APLL_WORK_SLOW: + return OSC_HZ; + case APLL_WORK_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_WORK_DEEP: + default: + return 32768; + } +} + +static ulong rk3288_clk_get_rate(struct udevice *dev) +{ + struct rk3288_clk_plat *plat = dev_get_platdata(dev); + struct rk3288_clk_priv *priv = dev_get_priv(dev); + + debug("%s\n", dev->name); + return rkclk_pll_get_rate(priv->cru, plat->clk_id); +} + +static ulong rk3288_clk_set_rate(struct udevice *dev, ulong rate) +{ + struct rk3288_clk_plat *plat = dev_get_platdata(dev); + struct rk3288_clk_priv *priv = dev_get_priv(dev); + + debug("%s\n", dev->name); + switch (plat->clk_id) { + case CLK_DDR: + rkclk_configure_ddr(priv->cru, priv->grf, rate); + break; + default: + return -ENOENT; + } + + return 0; +} + +static ulong rockchip_mmc_get_clk(struct rk3288_cru *cru, uint clk_general_rate, + enum periph_id periph) +{ + uint src_rate; + uint div, mux; + u32 con; + + switch (periph) { + case PERIPH_ID_EMMC: + con = readl(&cru->cru_clksel_con[12]); + mux = (con >> EMMC_PLL_SHIFT) & EMMC_PLL_MASK; + div = (con >> EMMC_DIV_SHIFT) & EMMC_DIV_MASK; + break; + case PERIPH_ID_SDCARD: + con = readl(&cru->cru_clksel_con[12]); + mux = (con >> MMC0_PLL_SHIFT) & MMC0_PLL_MASK; + div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK; + break; + case PERIPH_ID_SDMMC2: + con = readl(&cru->cru_clksel_con[12]); + mux = (con >> SDIO0_PLL_SHIFT) & SDIO0_PLL_MASK; + div = (con >> SDIO0_DIV_SHIFT) & SDIO0_DIV_MASK; + break; + default: + return -EINVAL; + } + + src_rate = mux == EMMC_PLL_SELECT_24MHZ ? OSC_HZ : clk_general_rate; + return DIV_TO_RATE(src_rate, div); +} + +static ulong rockchip_mmc_set_clk(struct rk3288_cru *cru, uint clk_general_rate, + enum periph_id periph, uint freq) +{ + int src_clk_div; + int mux; + + debug("%s: clk_general_rate=%u\n", __func__, clk_general_rate); + src_clk_div = RATE_TO_DIV(clk_general_rate, freq); + + if (src_clk_div > 0x3f) { + src_clk_div = RATE_TO_DIV(OSC_HZ, freq); + mux = EMMC_PLL_SELECT_24MHZ; + assert((int)EMMC_PLL_SELECT_24MHZ == + (int)MMC0_PLL_SELECT_24MHZ); + } else { + mux = EMMC_PLL_SELECT_GENERAL; + assert((int)EMMC_PLL_SELECT_GENERAL == + (int)MMC0_PLL_SELECT_GENERAL); + } + switch (periph) { + case PERIPH_ID_EMMC: + rk_clrsetreg(&cru->cru_clksel_con[12], + EMMC_PLL_MASK << EMMC_PLL_SHIFT | + EMMC_DIV_MASK << EMMC_DIV_SHIFT, + mux << EMMC_PLL_SHIFT | + (src_clk_div - 1) << EMMC_DIV_SHIFT); + break; + case PERIPH_ID_SDCARD: + rk_clrsetreg(&cru->cru_clksel_con[11], + MMC0_PLL_MASK << MMC0_PLL_SHIFT | + MMC0_DIV_MASK << MMC0_DIV_SHIFT, + mux << MMC0_PLL_SHIFT | + (src_clk_div - 1) << MMC0_DIV_SHIFT); + break; + case PERIPH_ID_SDMMC2: + rk_clrsetreg(&cru->cru_clksel_con[12], + SDIO0_PLL_MASK << SDIO0_PLL_SHIFT | + SDIO0_DIV_MASK << SDIO0_DIV_SHIFT, + mux << SDIO0_PLL_SHIFT | + (src_clk_div - 1) << SDIO0_DIV_SHIFT); + break; + default: + return -EINVAL; + } + + return rockchip_mmc_get_clk(cru, clk_general_rate, periph); +} + +static ulong rockchip_spi_get_clk(struct rk3288_cru *cru, uint clk_general_rate, + enum periph_id periph) +{ + uint div, mux; + u32 con; + + switch (periph) { + case PERIPH_ID_SPI0: + con = readl(&cru->cru_clksel_con[25]); + mux = (con >> SPI0_PLL_SHIFT) & SPI0_PLL_MASK; + div = (con >> SPI0_DIV_SHIFT) & SPI0_DIV_MASK; + break; + case PERIPH_ID_SPI1: + con = readl(&cru->cru_clksel_con[25]); + mux = (con >> SPI1_PLL_SHIFT) & SPI1_PLL_MASK; + div = (con >> SPI1_DIV_SHIFT) & SPI1_DIV_MASK; + break; + case PERIPH_ID_SPI2: + con = readl(&cru->cru_clksel_con[39]); + mux = (con >> SPI2_PLL_SHIFT) & SPI2_PLL_MASK; + div = (con >> SPI2_DIV_SHIFT) & SPI2_DIV_MASK; + break; + default: + return -EINVAL; + } + assert(mux == SPI0_PLL_SELECT_GENERAL); + + return DIV_TO_RATE(clk_general_rate, div); +} + +static ulong rockchip_spi_set_clk(struct rk3288_cru *cru, uint clk_general_rate, + enum periph_id periph, uint freq) +{ + int src_clk_div; + + debug("%s: clk_general_rate=%u\n", __func__, clk_general_rate); + src_clk_div = RATE_TO_DIV(clk_general_rate, freq); + switch (periph) { + case PERIPH_ID_SPI0: + rk_clrsetreg(&cru->cru_clksel_con[25], + SPI0_PLL_MASK << SPI0_PLL_SHIFT | + SPI0_DIV_MASK << SPI0_DIV_SHIFT, + SPI0_PLL_SELECT_GENERAL << SPI0_PLL_SHIFT | + src_clk_div << SPI0_DIV_SHIFT); + break; + case PERIPH_ID_SPI1: + rk_clrsetreg(&cru->cru_clksel_con[25], + SPI1_PLL_MASK << SPI1_PLL_SHIFT | + SPI1_DIV_MASK << SPI1_DIV_SHIFT, + SPI1_PLL_SELECT_GENERAL << SPI1_PLL_SHIFT | + src_clk_div << SPI1_DIV_SHIFT); + break; + case PERIPH_ID_SPI2: + rk_clrsetreg(&cru->cru_clksel_con[39], + SPI2_PLL_MASK << SPI2_PLL_SHIFT | + SPI2_DIV_MASK << SPI2_DIV_SHIFT, + SPI2_PLL_SELECT_GENERAL << SPI2_PLL_SHIFT | + src_clk_div << SPI2_DIV_SHIFT); + break; + default: + return -EINVAL; + } + + return rockchip_spi_get_clk(cru, clk_general_rate, periph); +} + +ulong rk3288_set_periph_rate(struct udevice *dev, int periph, ulong rate) +{ + struct rk3288_clk_priv *priv = dev_get_priv(dev); + ulong new_rate; + + switch (periph) { + case PERIPH_ID_EMMC: + case PERIPH_ID_SDCARD: + new_rate = rockchip_mmc_set_clk(priv->cru, clk_get_rate(dev), + periph, rate); + break; + case PERIPH_ID_SPI0: + case PERIPH_ID_SPI1: + case PERIPH_ID_SPI2: + new_rate = rockchip_spi_set_clk(priv->cru, clk_get_rate(dev), + periph, rate); + break; + default: + return -ENOENT; + } + + return new_rate; +} + +static struct clk_ops rk3288_clk_ops = { + .get_rate = rk3288_clk_get_rate, + .set_rate = rk3288_clk_set_rate, + .set_periph_rate = rk3288_set_periph_rate, +}; + +static int rk3288_clk_probe(struct udevice *dev) +{ + struct rk3288_clk_plat *plat = dev_get_platdata(dev); + struct rk3288_clk_priv *priv = dev_get_priv(dev); + + if (plat->clk_id != CLK_OSC) { + struct rk3288_clk_priv *parent_priv = dev_get_priv(dev->parent); + + priv->cru = parent_priv->cru; + priv->grf = parent_priv->grf; + return 0; + } + priv->cru = (struct rk3288_cru *)dev_get_addr(dev); + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); +#ifdef CONFIG_SPL_BUILD + rkclk_init(priv->cru, priv->grf); +#endif + + return 0; +} + +static const char *const clk_name[CLK_COUNT] = { + "osc", + "apll", + "dpll", + "cpll", + "gpll", + "mpll", +}; + +static int rk3288_clk_bind(struct udevice *dev) +{ + struct rk3288_clk_plat *plat = dev_get_platdata(dev); + int pll, ret; + + /* We only need to set up the root clock */ + if (dev->of_offset == -1) { + plat->clk_id = CLK_OSC; + return 0; + } + + /* Create devices for P main clocks */ + for (pll = 1; pll < CLK_COUNT; pll++) { + struct udevice *child; + struct rk3288_clk_plat *cplat; + + debug("%s %s\n", __func__, clk_name[pll]); + ret = device_bind_driver(dev, "clk_rk3288", clk_name[pll], + &child); + if (ret) + return ret; + cplat = dev_get_platdata(child); + cplat->clk_id = pll; + } + + /* The reset driver does not have a device node, so bind it here */ + ret = device_bind_driver(gd->dm_root, "rk3288_reset", "reset", &dev); + if (ret) + debug("Warning: No RK3288 reset driver: ret=%d\n", ret); + + return 0; +} + +static const struct udevice_id rk3288_clk_ids[] = { + { .compatible = "rockchip,rk3288-cru" }, + { } +}; + +U_BOOT_DRIVER(clk_rk3288) = { + .name = "clk_rk3288", + .id = UCLASS_CLK, + .of_match = rk3288_clk_ids, + .priv_auto_alloc_size = sizeof(struct rk3288_clk_priv), + .platdata_auto_alloc_size = sizeof(struct rk3288_clk_plat), + .ops = &rk3288_clk_ops, + .bind = rk3288_clk_bind, + .probe = rk3288_clk_probe, +}; diff --git a/drivers/core/device.c b/drivers/core/device.c index a6cd93698f..0ccd443f25 100644 --- a/drivers/core/device.c +++ b/drivers/core/device.c @@ -39,8 +39,10 @@ int device_bind(struct udevice *parent, const struct driver *drv, return -EINVAL; ret = uclass_get(drv->id, &uc); - if (ret) + if (ret) { + debug("Missing uclass for driver %s\n", drv->name); return ret; + } dev = calloc(1, sizeof(struct udevice)); if (!dev) diff --git a/drivers/core/root.c b/drivers/core/root.c index 78ab00c7bf..bdb394a9ae 100644 --- a/drivers/core/root.c +++ b/drivers/core/root.c @@ -162,8 +162,11 @@ int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset, continue; } err = lists_bind_fdt(parent, blob, offset, NULL); - if (err && !ret) + if (err && !ret) { ret = err; + debug("%s: ret=%d\n", fdt_get_name(blob, offset, NULL), + ret); + } } if (ret) diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index f63ff599a6..e800c28653 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -58,7 +58,12 @@ static int uclass_add(enum uclass_id id, struct uclass **ucp) if (!uc_drv) { debug("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n", id); - return -ENOENT; + /* + * Use a strange error to make this case easier to find. When + * a uclass is not available it can prevent driver model from + * starting up and this failure is otherwise hard to debug. + */ + return -EPFNOSUPPORT; } uc = calloc(1, sizeof(*uc)); if (!uc) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 9a5d29debb..ef57a89ea2 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -28,6 +28,15 @@ config LPC32XX_GPIO help Support for the LPC32XX GPIO driver. +config ROCKCHIP_GPIO + bool "Rockchip GPIO driver" + depends on DM_GPIO + help + Support GPIO access on Rockchip SoCs. The GPIOs are arranged into + a number of banks (different for each SoC type) each with 32 GPIOs. + The GPIOs for a device are defined in the device tree with one node + for each bank. + config SANDBOX_GPIO bool "Enable sandbox GPIO driver" depends on SANDBOX && DM && DM_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index ba185949bf..c58aa4dfd5 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_MXC_GPIO) += mxc_gpio.o obj-$(CONFIG_MXS_GPIO) += mxs_gpio.o obj-$(CONFIG_PCA953X) += pca953x.o obj-$(CONFIG_PCA9698) += pca9698.o +obj-$(CONFIG_ROCKCHIP_GPIO) += rk_gpio.o obj-$(CONFIG_S5P) += s5p_gpio.o obj-$(CONFIG_SANDBOX_GPIO) += sandbox.o obj-$(CONFIG_SPEAR_GPIO) += spear_gpio.o diff --git a/drivers/gpio/rk_gpio.c b/drivers/gpio/rk_gpio.c new file mode 100644 index 0000000000..fbdf9f3fd9 --- /dev/null +++ b/drivers/gpio/rk_gpio.c @@ -0,0 +1,123 @@ +/* + * (C) Copyright 2015 Google, Inc + * + * (C) Copyright 2008-2014 Rockchip Electronics + * Peter, Software Engineering, <superpeter.cai@gmail.com>. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <asm/errno.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <dt-bindings/gpio/gpio.h> + +enum { + ROCKCHIP_GPIOS_PER_BANK = 32, +}; + +#define OFFSET_TO_BIT(bit) (1UL << (bit)) + +struct rockchip_gpio_priv { + struct rockchip_gpio_regs *regs; + char name[2]; +}; + +static int rockchip_gpio_direction_input(struct udevice *dev, unsigned offset) +{ + struct rockchip_gpio_priv *priv = dev_get_priv(dev); + struct rockchip_gpio_regs *regs = priv->regs; + + clrbits_le32(®s->swport_ddr, OFFSET_TO_BIT(offset)); + + return 0; +} + +static int rockchip_gpio_direction_output(struct udevice *dev, unsigned offset, + int value) +{ + struct rockchip_gpio_priv *priv = dev_get_priv(dev); + struct rockchip_gpio_regs *regs = priv->regs; + int mask = OFFSET_TO_BIT(offset); + + clrsetbits_le32(®s->swport_dr, mask, value ? mask : 0); + setbits_le32(®s->swport_ddr, mask); + + return 0; +} + +static int rockchip_gpio_get_value(struct udevice *dev, unsigned offset) +{ + struct rockchip_gpio_priv *priv = dev_get_priv(dev); + struct rockchip_gpio_regs *regs = priv->regs; + + return readl(®s->ext_port) & OFFSET_TO_BIT(offset); +} + +static int rockchip_gpio_set_value(struct udevice *dev, unsigned offset, + int value) +{ + struct rockchip_gpio_priv *priv = dev_get_priv(dev); + struct rockchip_gpio_regs *regs = priv->regs; + int mask = OFFSET_TO_BIT(offset); + + clrsetbits_le32(®s->swport_dr, mask, value ? mask : 0); + + return 0; +} + +static int rockchip_gpio_get_function(struct udevice *dev, unsigned offset) +{ + return -ENOSYS; +} + +static int rockchip_gpio_xlate(struct udevice *dev, struct gpio_desc *desc, + struct fdtdec_phandle_args *args) +{ + desc->offset = args->args[0]; + desc->flags = args->args[1] & GPIO_ACTIVE_LOW ? GPIOD_ACTIVE_LOW : 0; + + return 0; +} + +static int rockchip_gpio_probe(struct udevice *dev) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + struct rockchip_gpio_priv *priv = dev_get_priv(dev); + char *end; + int bank; + + priv->regs = (struct rockchip_gpio_regs *)dev_get_addr(dev); + uc_priv->gpio_count = ROCKCHIP_GPIOS_PER_BANK; + end = strrchr(dev->name, '@'); + bank = trailing_strtoln(dev->name, end); + priv->name[0] = 'A' + bank; + uc_priv->bank_name = priv->name; + + return 0; +} + +static const struct dm_gpio_ops gpio_rockchip_ops = { + .direction_input = rockchip_gpio_direction_input, + .direction_output = rockchip_gpio_direction_output, + .get_value = rockchip_gpio_get_value, + .set_value = rockchip_gpio_set_value, + .get_function = rockchip_gpio_get_function, + .xlate = rockchip_gpio_xlate, +}; + +static const struct udevice_id rockchip_gpio_ids[] = { + { .compatible = "rockchip,gpio-bank" }, + { } +}; + +U_BOOT_DRIVER(gpio_rockchip) = { + .name = "gpio_rockchip", + .id = UCLASS_GPIO, + .of_match = rockchip_gpio_ids, + .ops = &gpio_rockchip_ops, + .priv_auto_alloc_size = sizeof(struct rockchip_gpio_priv), + .probe = rockchip_gpio_probe, +}; diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index c40bd5c1e0..14adda2857 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -58,6 +58,15 @@ config DM_I2C_GPIO bindings are supported. Binding info: doc/device-tree-bindings/i2c/i2c-gpio.txt +config SYS_I2C_ROCKCHIP + bool "Rockchip I2C driver" + depends on DM_I2C + help + Add support for the Rockchip I2C driver. This is used with various + Rockchip parts such as RK3126, RK3128, RK3036 and RK3288. All chips + have several I2C ports and all are provided, controled by the + device tree. + config SYS_I2C_SANDBOX bool "Sandbox I2C driver" depends on SANDBOX && DM_I2C diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 9b45248e2e..8ce294b02a 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_SYS_I2C_OMAP24XX) += omap24xx_i2c.o obj-$(CONFIG_SYS_I2C_OMAP34XX) += omap24xx_i2c.o obj-$(CONFIG_SYS_I2C_PPC4XX) += ppc4xx_i2c.o obj-$(CONFIG_SYS_I2C_RCAR) += rcar_i2c.o +obj-$(CONFIG_SYS_I2C_ROCKCHIP) += rk_i2c.o obj-$(CONFIG_SYS_I2C_S3C24X0) += s3c24x0_i2c.o obj-$(CONFIG_SYS_I2C_SANDBOX) += sandbox_i2c.o i2c-emul-uclass.o obj-$(CONFIG_SYS_I2C_SH) += sh_i2c.o diff --git a/drivers/i2c/rk_i2c.c b/drivers/i2c/rk_i2c.c new file mode 100644 index 0000000000..ebdba35dc6 --- /dev/null +++ b/drivers/i2c/rk_i2c.c @@ -0,0 +1,391 @@ +/* + * (C) Copyright 2015 Google, Inc + * + * (C) Copyright 2008-2014 Rockchip Electronics + * Peter, Software Engineering, <superpeter.cai@gmail.com>. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <errno.h> +#include <i2c.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/i2c.h> +#include <asm/arch/periph.h> +#include <dm/pinctrl.h> +#include <linux/sizes.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* i2c timerout */ +#define I2C_TIMEOUT_MS 100 +#define I2C_RETRY_COUNT 3 + +/* rk i2c fifo max transfer bytes */ +#define RK_I2C_FIFO_SIZE 32 + +struct rk_i2c { + struct udevice *clk; + struct udevice *pinctrl; + struct i2c_regs *regs; + unsigned int speed; + enum periph_id id; +}; + +static inline void rk_i2c_get_div(int div, int *divh, int *divl) +{ + *divl = div / 2; + if (div % 2 == 0) + *divh = div / 2; + else + *divh = DIV_ROUND_UP(div, 2); +} + +/* + * SCL Divisor = 8 * (CLKDIVL+1 + CLKDIVH+1) + * SCL = PCLK / SCLK Divisor + * i2c_rate = PCLK + */ +static void rk_i2c_set_clk(struct rk_i2c *i2c, uint32_t scl_rate) +{ + uint32_t i2c_rate; + int div, divl, divh; + + /* First get i2c rate from pclk */ + i2c_rate = clk_get_periph_rate(i2c->clk, i2c->id); + + div = DIV_ROUND_UP(i2c_rate, scl_rate * 8) - 2; + divh = 0; + divl = 0; + if (div >= 0) + rk_i2c_get_div(div, &divh, &divl); + writel(I2C_CLKDIV_VAL(divl, divh), &i2c->regs->clkdiv); + + debug("rk_i2c_set_clk: i2c rate = %d, scl rate = %d\n", i2c_rate, + scl_rate); + debug("set i2c clk div = %d, divh = %d, divl = %d\n", div, divh, divl); + debug("set clk(I2C_CLKDIV: 0x%08x)\n", readl(&i2c->regs->clkdiv)); +} + +static void rk_i2c_show_regs(struct i2c_regs *regs) +{ +#ifdef DEBUG + uint i; + + debug("i2c_con: 0x%08x\n", readl(®s->con)); + debug("i2c_clkdiv: 0x%08x\n", readl(®s->clkdiv)); + debug("i2c_mrxaddr: 0x%08x\n", readl(®s->mrxaddr)); + debug("i2c_mrxraddR: 0x%08x\n", readl(®s->mrxraddr)); + debug("i2c_mtxcnt: 0x%08x\n", readl(®s->mtxcnt)); + debug("i2c_mrxcnt: 0x%08x\n", readl(®s->mrxcnt)); + debug("i2c_ien: 0x%08x\n", readl(®s->ien)); + debug("i2c_ipd: 0x%08x\n", readl(®s->ipd)); + debug("i2c_fcnt: 0x%08x\n", readl(®s->fcnt)); + for (i = 0; i < 8; i++) + debug("i2c_txdata%d: 0x%08x\n", i, readl(®s->txdata[i])); + for (i = 0; i < 8; i++) + debug("i2c_rxdata%d: 0x%08x\n", i, readl(®s->rxdata[i])); +#endif +} + +static int rk_i2c_send_start_bit(struct rk_i2c *i2c) +{ + struct i2c_regs *regs = i2c->regs; + ulong start; + + debug("I2c Send Start bit.\n"); + writel(I2C_IPD_ALL_CLEAN, ®s->ipd); + + writel(I2C_CON_EN | I2C_CON_START, ®s->con); + writel(I2C_STARTIEN, ®s->ien); + + start = get_timer(0); + while (1) { + if (readl(®s->ipd) & I2C_STARTIPD) { + writel(I2C_STARTIPD, ®s->ipd); + break; + } + if (get_timer(start) > I2C_TIMEOUT_MS) { + debug("I2C Send Start Bit Timeout\n"); + rk_i2c_show_regs(regs); + return -ETIMEDOUT; + } + udelay(1); + } + + return 0; +} + +static int rk_i2c_send_stop_bit(struct rk_i2c *i2c) +{ + struct i2c_regs *regs = i2c->regs; + ulong start; + + debug("I2c Send Stop bit.\n"); + writel(I2C_IPD_ALL_CLEAN, ®s->ipd); + + writel(I2C_CON_EN | I2C_CON_STOP, ®s->con); + writel(I2C_CON_STOP, ®s->ien); + + start = get_timer(0); + while (1) { + if (readl(®s->ipd) & I2C_STOPIPD) { + writel(I2C_STOPIPD, ®s->ipd); + break; + } + if (get_timer(start) > I2C_TIMEOUT_MS) { + debug("I2C Send Start Bit Timeout\n"); + rk_i2c_show_regs(regs); + return -ETIMEDOUT; + } + udelay(1); + } + + return 0; +} + +static inline void rk_i2c_disable(struct rk_i2c *i2c) +{ + writel(0, &i2c->regs->con); +} + +static int rk_i2c_read(struct rk_i2c *i2c, uchar chip, uint reg, uint r_len, + uchar *buf, uint b_len) +{ + struct i2c_regs *regs = i2c->regs; + uchar *pbuf = buf; + uint bytes_remain_len = b_len; + uint bytes_xferred = 0; + uint words_xferred = 0; + ulong start; + uint con = 0; + uint rxdata; + uint i, j; + int err; + + debug("rk_i2c_read: chip = %d, reg = %d, r_len = %d, b_len = %d\n", + chip, reg, r_len, b_len); + + err = rk_i2c_send_start_bit(i2c); + if (err) + return err; + + writel(I2C_MRXADDR_SET(1, chip << 1 | 1), ®s->mrxaddr); + if (r_len == 0) { + writel(0, ®s->mrxraddr); + } else if (r_len < 4) { + writel(I2C_MRXRADDR_SET(r_len, reg), ®s->mrxraddr); + } else { + debug("I2C Read: addr len %d not supported\n", r_len); + return -EIO; + } + + while (bytes_remain_len) { + if (bytes_remain_len > RK_I2C_FIFO_SIZE) { + con = I2C_CON_EN | I2C_CON_MOD(I2C_MODE_TRX); + bytes_xferred = 32; + } else { + con = I2C_CON_EN | I2C_CON_MOD(I2C_MODE_TRX) | + I2C_CON_LASTACK; + bytes_xferred = bytes_remain_len; + } + words_xferred = DIV_ROUND_UP(bytes_xferred, 4); + + writel(con, ®s->con); + writel(bytes_xferred, ®s->mrxcnt); + writel(I2C_MBRFIEN | I2C_NAKRCVIEN, ®s->ien); + + start = get_timer(0); + while (1) { + if (readl(®s->ipd) & I2C_NAKRCVIPD) { + writel(I2C_NAKRCVIPD, ®s->ipd); + err = -EREMOTEIO; + } + if (readl(®s->ipd) & I2C_MBRFIPD) { + writel(I2C_MBRFIPD, ®s->ipd); + break; + } + if (get_timer(start) > I2C_TIMEOUT_MS) { + debug("I2C Read Data Timeout\n"); + err = -ETIMEDOUT; + rk_i2c_show_regs(regs); + goto i2c_exit; + } + udelay(1); + } + + for (i = 0; i < words_xferred; i++) { + rxdata = readl(®s->rxdata[i]); + debug("I2c Read RXDATA[%d] = 0x%x\n", i, rxdata); + for (j = 0; j < 4; j++) { + if ((i * 4 + j) == bytes_xferred) + break; + *pbuf++ = (rxdata >> (j * 8)) & 0xff; + } + } + + bytes_remain_len -= bytes_xferred; + debug("I2C Read bytes_remain_len %d\n", bytes_remain_len); + } + +i2c_exit: + rk_i2c_send_stop_bit(i2c); + rk_i2c_disable(i2c); + + return err; +} + +static int rk_i2c_write(struct rk_i2c *i2c, uchar chip, uint reg, uint r_len, + uchar *buf, uint b_len) +{ + struct i2c_regs *regs = i2c->regs; + int err; + uchar *pbuf = buf; + uint bytes_remain_len = b_len + r_len + 1; + uint bytes_xferred = 0; + uint words_xferred = 0; + ulong start; + uint txdata; + uint i, j; + + debug("rk_i2c_write: chip = %d, reg = %d, r_len = %d, b_len = %d\n", + chip, reg, r_len, b_len); + err = rk_i2c_send_start_bit(i2c); + if (err) + return err; + + while (bytes_remain_len) { + if (bytes_remain_len > RK_I2C_FIFO_SIZE) + bytes_xferred = 32; + else + bytes_xferred = bytes_remain_len; + words_xferred = DIV_ROUND_UP(bytes_xferred, 4); + + for (i = 0; i < words_xferred; i++) { + txdata = 0; + for (j = 0; j < 4; j++) { + if ((i * 4 + j) == bytes_xferred) + break; + + if (i == 0 && j == 0) { + txdata |= (chip << 1); + } else if (i == 0 && j <= r_len) { + txdata |= (reg & + (0xff << ((j - 1) * 8))) << 8; + } else { + txdata |= (*pbuf++)<<(j * 8); + } + writel(txdata, ®s->txdata[i]); + } + debug("I2c Write TXDATA[%d] = 0x%x\n", i, txdata); + } + + writel(I2C_CON_EN | I2C_CON_MOD(I2C_MODE_TX), ®s->con); + writel(bytes_xferred, ®s->mtxcnt); + writel(I2C_MBTFIEN | I2C_NAKRCVIEN, ®s->ien); + + start = get_timer(0); + while (1) { + if (readl(®s->ipd) & I2C_NAKRCVIPD) { + writel(I2C_NAKRCVIPD, ®s->ipd); + err = -EREMOTEIO; + } + if (readl(®s->ipd) & I2C_MBTFIPD) { + writel(I2C_MBTFIPD, ®s->ipd); + break; + } + if (get_timer(start) > I2C_TIMEOUT_MS) { + debug("I2C Write Data Timeout\n"); + err = -ETIMEDOUT; + rk_i2c_show_regs(regs); + goto i2c_exit; + } + udelay(1); + } + + bytes_remain_len -= bytes_xferred; + debug("I2C Write bytes_remain_len %d\n", bytes_remain_len); + } + +i2c_exit: + rk_i2c_send_stop_bit(i2c); + rk_i2c_disable(i2c); + + return err; +} + +static int rockchip_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, + int nmsgs) +{ + struct rk_i2c *i2c = dev_get_priv(bus); + int ret; + + debug("i2c_xfer: %d messages\n", nmsgs); + for (; nmsgs > 0; nmsgs--, msg++) { + debug("i2c_xfer: chip=0x%x, len=0x%x\n", msg->addr, msg->len); + if (msg->flags & I2C_M_RD) { + ret = rk_i2c_read(i2c, msg->addr, 0, 0, msg->buf, + msg->len); + } else { + ret = rk_i2c_write(i2c, msg->addr, 0, 0, msg->buf, + msg->len); + } + if (ret) { + debug("i2c_write: error sending\n"); + return -EREMOTEIO; + } + } + + return 0; +} + +int rockchip_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) +{ + struct rk_i2c *i2c = dev_get_priv(bus); + + rk_i2c_set_clk(i2c, speed); + + return 0; +} + +static int rockchip_i2c_probe(struct udevice *bus) +{ + struct rk_i2c *i2c = dev_get_priv(bus); + int ret; + + ret = uclass_get_device(UCLASS_PINCTRL, 0, &i2c->pinctrl); + if (ret) + return ret; + ret = uclass_get_device(UCLASS_CLK, 0, &i2c->clk); + if (ret) + return ret; + ret = pinctrl_get_periph_id(i2c->pinctrl, bus); + if (ret < 0) + return ret; + i2c->id = ret; + i2c->regs = (void *)dev_get_addr(bus); + return pinctrl_request(i2c->pinctrl, i2c->id, 0); +} + +static const struct dm_i2c_ops rockchip_i2c_ops = { + .xfer = rockchip_i2c_xfer, + .set_bus_speed = rockchip_i2c_set_bus_speed, +}; + +static const struct udevice_id rockchip_i2c_ids[] = { + { .compatible = "rockchip,rk3288-i2c" }, + { } +}; + +U_BOOT_DRIVER(i2c_rockchip) = { + .name = "i2c_rockchip", + .id = UCLASS_I2C, + .of_match = rockchip_i2c_ids, + .probe = rockchip_i2c_probe, + .priv_auto_alloc_size = sizeof(struct rk_i2c), + .ops = &rockchip_i2c_ops, +}; diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 2987337219..fe74403460 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -11,7 +11,7 @@ config LED config SPL_LED bool "Enable LED support in SPL" - depends on LED + depends on SPL && SPL_DM help The LED subsystem adds a small amount of overhead to the image. If this is acceptable and you have a need to use LEDs in SPL, @@ -27,4 +27,11 @@ config LED_GPIO The GPIO driver must used driver model. LEDs are configured using the device tree. +config SPL_LED_GPIO + bool "LED support for GPIO-connected LEDs in SPL" + depends on SPL_LED && DM_GPIO + help + This option is an SPL-variant of the LED_GPIO option. + See the help of LED_GPIO for details. + endmenu diff --git a/drivers/led/Makefile b/drivers/led/Makefile index 990129e08d..02367fdacb 100644 --- a/drivers/led/Makefile +++ b/drivers/led/Makefile @@ -5,5 +5,5 @@ # SPDX-License-Identifier: GPL-2.0+ # -obj-$(CONFIG_LED) += led-uclass.o -obj-$(CONFIG_LED_GPIO) += led_gpio.o +obj-y += led-uclass.o +obj-$(CONFIG_$(SPL_)LED_GPIO) += led_gpio.o diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 3e835f7bca..6277f92ef5 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -10,6 +10,15 @@ config DM_MMC appear as block devices in U-Boot and can support filesystems such as EXT4 and FAT. +config ROCKCHIP_DWMMC + bool "Rockchip SD/MMC controller support" + depends on DM_MMC && OF_CONTROL + help + This enables support for the Rockchip SD/MMM controller, which is + based on Designware IP. The device is compatible with at least + SD 3.0, SDIO 3.0 and MMC 4.5 and supports common eMMC chips as well + as removeable SD and micro-SD cards. + config SH_SDHI bool "SuperH/Renesas ARM SoCs on-chip SDHI host controller support" depends on RMOBILE diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index cae207c303..99d02954ed 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_MXS_MMC) += mxsmmc.o obj-$(CONFIG_OMAP_HSMMC) += omap_hsmmc.o obj-$(CONFIG_X86) += pci_mmc.o obj-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o +obj-$(CONFIG_ROCKCHIP_DWMMC) += rockchip_dw_mmc.o obj-$(CONFIG_SUPPORT_EMMC_RPMB) += rpmb.o obj-$(CONFIG_S3C_SDI) += s3c_sdi.o obj-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c index 77b87e0365..1117fedefe 100644 --- a/drivers/mmc/dw_mmc.c +++ b/drivers/mmc/dw_mmc.c @@ -266,7 +266,7 @@ static int dwmci_setup_bus(struct dwmci_host *host, u32 freq) * host->bus_hz should be set by user. */ if (host->get_mmc_clk) - sclk = host->get_mmc_clk(host); + sclk = host->get_mmc_clk(host, freq); else if (host->bus_hz) sclk = host->bus_hz; else { diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c index cde2ba7118..863bbb3f64 100644 --- a/drivers/mmc/exynos_dw_mmc.c +++ b/drivers/mmc/exynos_dw_mmc.c @@ -39,7 +39,7 @@ static void exynos_dwmci_clksel(struct dwmci_host *host) dwmci_writel(host, DWMCI_CLKSEL, priv->sdr_timing); } -unsigned int exynos_dwmci_get_clk(struct dwmci_host *host) +unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq) { unsigned long sclk; int8_t clk_div; diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index f12546ac51..371c1ec212 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -10,6 +10,8 @@ #include <config.h> #include <common.h> #include <command.h> +#include <dm.h> +#include <dm/device-internal.h> #include <errno.h> #include <mmc.h> #include <part.h> @@ -1759,10 +1761,44 @@ static void do_preinit(void) } } +#if defined(CONFIG_DM_MMC) && defined(CONFIG_SPL_BUILD) +static int mmc_probe(bd_t *bis) +{ + return 0; +} +#elif defined(CONFIG_DM_MMC) +static int mmc_probe(bd_t *bis) +{ + int ret; + struct uclass *uc; + struct udevice *m; + + ret = uclass_get(UCLASS_MMC, &uc); + if (ret) + return ret; + + uclass_foreach_dev(m, uc) { + ret = device_probe(m); + if (ret) + printf("%s - probe failed: %d\n", m->name, ret); + } + + return 0; +} +#else +static int mmc_probe(bd_t *bis) +{ + if (board_mmc_init(bis) < 0) + cpu_mmc_init(bis); + + return 0; +} +#endif int mmc_initialize(bd_t *bis) { static int initialized = 0; + int ret; if (initialized) /* Avoid initializing mmc multiple times */ return 0; initialized = 1; @@ -1770,10 +1806,9 @@ int mmc_initialize(bd_t *bis) INIT_LIST_HEAD (&mmc_devices); cur_dev_num = 0; -#ifndef CONFIG_DM_MMC - if (board_mmc_init(bis) < 0) - cpu_mmc_init(bis); -#endif + ret = mmc_probe(bis); + if (ret) + return ret; #ifndef CONFIG_SPL_BUILD print_mmc_devices(','); diff --git a/drivers/mmc/rockchip_dw_mmc.c b/drivers/mmc/rockchip_dw_mmc.c new file mode 100644 index 0000000000..f11c8e0039 --- /dev/null +++ b/drivers/mmc/rockchip_dw_mmc.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2013 Google, Inc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <dwmmc.h> +#include <errno.h> +#include <syscon.h> +#include <asm/arch/clock.h> +#include <asm/arch/periph.h> +#include <linux/err.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct rockchip_dwmmc_priv { + struct udevice *clk; + struct rk3288_grf *grf; + struct dwmci_host host; +}; + +static uint rockchip_dwmmc_get_mmc_clk(struct dwmci_host *host, uint freq) +{ + struct udevice *dev = host->priv; + struct rockchip_dwmmc_priv *priv = dev_get_priv(dev); + int ret; + + ret = clk_set_periph_rate(priv->clk, PERIPH_ID_SDMMC0 + host->dev_index, + freq); + if (ret < 0) { + debug("%s: err=%d\n", __func__, ret); + return ret; + } + + return freq; +} + +static int rockchip_dwmmc_ofdata_to_platdata(struct udevice *dev) +{ + struct rockchip_dwmmc_priv *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + + host->name = dev->name; + host->ioaddr = (void *)dev_get_addr(dev); + host->buswidth = fdtdec_get_int(gd->fdt_blob, dev->of_offset, + "bus-width", 4); + host->get_mmc_clk = rockchip_dwmmc_get_mmc_clk; + host->priv = dev; + + /* TODO(sjg@chromium.org): Remove the need for this hack */ + host->dev_index = (ulong)host->ioaddr == 0xff0f0000 ? 0 : 1; + + return 0; +} + +static int rockchip_dwmmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct rockchip_dwmmc_priv *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + u32 minmax[2]; + int ret; + + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + if (IS_ERR(priv->grf)) + return PTR_ERR(priv->grf); + ret = uclass_get_device(UCLASS_CLK, CLK_GENERAL, &priv->clk); + if (ret) + return ret; + + ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, + "clock-freq-min-max", minmax, 2); + if (!ret) + ret = add_dwmci(host, minmax[1], minmax[0]); + if (ret) + return ret; + + upriv->mmc = host->mmc; + + return 0; +} + +static const struct udevice_id rockchip_dwmmc_ids[] = { + { .compatible = "rockchip,rk3288-dw-mshc" }, + { } +}; + +U_BOOT_DRIVER(rockchip_dwmmc_drv) = { + .name = "rockchip_dwmmc", + .id = UCLASS_MMC, + .of_match = rockchip_dwmmc_ids, + .ofdata_to_platdata = rockchip_dwmmc_ofdata_to_platdata, + .probe = rockchip_dwmmc_probe, + .priv_auto_alloc_size = sizeof(struct rockchip_dwmmc_priv), +}; diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 30b8e452ef..b8146df99b 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -47,7 +47,10 @@ config PINMUX default y help This option enables pin multiplexing through the generic pinctrl - framework. + framework. Most SoCs have their own own multiplexing arrangement + where a single pin can be used for several functions. An SoC pinctrl + driver allows the required function to be selected for each pin. + The driver is typically controlled by the device tree. config PINCONF bool "Support pin configuration controllers" @@ -86,6 +89,12 @@ config SPL_PINMUX help This option is an SPL-variant of the PINMUX option. See the help of PINMUX for details. + The pinctrl subsystem can add a substantial overhead to the SPL + image since it typically requires quite a few tables either in the + driver or in the device tree. If this is acceptable and you need + to adjust pin multiplexing in SPL in order to boot into U-Boot, + enable this option. You will need to enable device tree in SPL + for this to work. config SPL_PINCONF bool "Support pin configuration controllers in SPL" @@ -96,6 +105,15 @@ config SPL_PINCONF if PINCTRL || SPL_PINCTRL +config ROCKCHIP_PINCTRL + bool "Rockchip pin control driver" + depends on DM + help + Support pin multiplexing control on Rockchip SoCs. The driver is + controlled by a device tree node which contains both the GPIO + definitions and pin control functions for each available multiplex + function. + config PINCTRL_SANDBOX bool "Sandbox pinctrl driver" depends on SANDBOX diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 35decf49c3..f537df4e88 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -1,4 +1,5 @@ obj-y += pinctrl-uclass.o obj-$(CONFIG_$(SPL_)PINCTRL_GENERIC) += pinctrl-generic.o +obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_PINCTRL_SANDBOX) += pinctrl-sandbox.o diff --git a/drivers/pinctrl/pinctrl-uclass.c b/drivers/pinctrl/pinctrl-uclass.c index d96c201e83..58001ef572 100644 --- a/drivers/pinctrl/pinctrl-uclass.c +++ b/drivers/pinctrl/pinctrl-uclass.c @@ -11,6 +11,7 @@ #include <dm/device.h> #include <dm/lists.h> #include <dm/pinctrl.h> +#include <dm/root.h> #include <dm/uclass.h> DECLARE_GLOBAL_DATA_PTR; @@ -159,7 +160,8 @@ static int pinctrl_select_state_full(struct udevice *dev, const char *statename) static int pinconfig_post_bind(struct udevice *dev) { - return 0; + /* Scan the bus for devices */ + return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false); } #endif @@ -205,6 +207,31 @@ int pinctrl_select_state(struct udevice *dev, const char *statename) return 0; } +int pinctrl_request(struct udevice *dev, int func, int flags) +{ + struct pinctrl_ops *ops = pinctrl_get_ops(dev); + + if (!ops->request) + return -ENOSYS; + + return ops->request(dev, func, flags); +} + +int pinctrl_request_noflags(struct udevice *dev, int func) +{ + return pinctrl_request(dev, func, 0); +} + +int pinctrl_get_periph_id(struct udevice *dev, struct udevice *periph) +{ + struct pinctrl_ops *ops = pinctrl_get_ops(dev); + + if (!ops->get_periph_id) + return -ENOSYS; + + return ops->get_periph_id(dev, periph); +} + /** * pinconfig_post-bind() - post binding for PINCTRL uclass * Recursively bind child nodes as pinconfig devices in case of full pinctrl. @@ -222,15 +249,10 @@ static int pinctrl_post_bind(struct udevice *dev) } /* - * If set_state callback is set, we assume this pinctrl driver is the - * full implementation. In this case, its child nodes should be bound - * so that peripheral devices can easily search in parent devices - * during later DT-parsing. + * The pinctrl driver child nodes should be bound so that peripheral + * devices can easily search in parent devices during later DT-parsing. */ - if (ops->set_state) - return pinconfig_post_bind(dev); - - return 0; + return pinconfig_post_bind(dev); } UCLASS_DRIVER(pinctrl) = { diff --git a/drivers/pinctrl/rockchip/Makefile b/drivers/pinctrl/rockchip/Makefile new file mode 100644 index 0000000000..251bace797 --- /dev/null +++ b/drivers/pinctrl/rockchip/Makefile @@ -0,0 +1,8 @@ +# +# Copyright (c) 2015 Google, Inc +# Written by Simon Glass <sjg@chromium.org> +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-$(CONFIG_ROCKCHIP_PINCTRL) += pinctrl_rk3288.o diff --git a/drivers/pinctrl/rockchip/pinctrl_rk3288.c b/drivers/pinctrl/rockchip/pinctrl_rk3288.c new file mode 100644 index 0000000000..5205498b36 --- /dev/null +++ b/drivers/pinctrl/rockchip/pinctrl_rk3288.c @@ -0,0 +1,441 @@ +/* + * Pinctrl driver for Rockchip SoCs + * Copyright (c) 2015 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <syscon.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/grf_rk3288.h> +#include <asm/arch/hardware.h> +#include <asm/arch/periph.h> +#include <asm/arch/pmu_rk3288.h> +#include <dm/pinctrl.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct rk3288_pinctrl_priv { + struct rk3288_grf *grf; + struct rk3288_pmu *pmu; +}; + +static void pinctrl_rk3288_pwm_config(struct rk3288_grf *grf, int pwm_id) +{ + switch (pwm_id) { + case PERIPH_ID_PWM0: + rk_clrsetreg(&grf->gpio7a_iomux, GPIO7A0_MASK << GPIO7A0_SHIFT, + GPIO7A0_PWM_0 << GPIO7A0_SHIFT); + break; + case PERIPH_ID_PWM1: + rk_clrsetreg(&grf->gpio7a_iomux, GPIO7A1_MASK << GPIO7A1_SHIFT, + GPIO7A1_PWM_1 << GPIO7A1_SHIFT); + break; + case PERIPH_ID_PWM2: + rk_clrsetreg(&grf->gpio7a_iomux, GPIO7C6_MASK << GPIO7C6_SHIFT, + GPIO7C6_PWM_2 << GPIO7C6_SHIFT); + break; + case PERIPH_ID_PWM3: + rk_clrsetreg(&grf->gpio7a_iomux, GPIO7C7_MASK << GPIO7C6_SHIFT, + GPIO7C7_PWM_3 << GPIO7C7_SHIFT); + break; + default: + debug("pwm id = %d iomux error!\n", pwm_id); + break; + } +} + +static void pinctrl_rk3288_i2c_config(struct rk3288_grf *grf, + struct rk3288_pmu *pmu, int i2c_id) +{ + switch (i2c_id) { + case PERIPH_ID_I2C0: + clrsetbits_le32(&pmu->gpio0b_iomux, + GPIO0_B7_MASK << GPIO0_B7_SHIFT, + GPIO0_B7_I2C0PMU_SDA << GPIO0_B7_SHIFT); + clrsetbits_le32(&pmu->gpio0b_iomux, + GPIO0_C0_MASK << GPIO0_C0_SHIFT, + GPIO0_C0_I2C0PMU_SCL << GPIO0_C0_SHIFT); + break; + case PERIPH_ID_I2C1: + rk_clrsetreg(&grf->gpio8a_iomux, + GPIO8A4_MASK << GPIO8A4_SHIFT | + GPIO8A5_MASK << GPIO8A5_SHIFT, + GPIO8A4_I2C2SENSOR_SDA << GPIO8A4_SHIFT | + GPIO8A5_I2C2SENSOR_SCL << GPIO8A5_SHIFT); + break; + case PERIPH_ID_I2C2: + rk_clrsetreg(&grf->gpio6b_iomux, + GPIO6B1_MASK << GPIO6B1_SHIFT | + GPIO6B2_MASK << GPIO6B2_SHIFT, + GPIO6B1_I2C1AUDIO_SDA << GPIO6B1_SHIFT | + GPIO6B2_I2C1AUDIO_SCL << GPIO6B2_SHIFT); + break; + case PERIPH_ID_I2C3: + rk_clrsetreg(&grf->gpio2c_iomux, + GPIO2C1_MASK << GPIO2C1_SHIFT | + GPIO2C0_MASK << GPIO2C0_SHIFT, + GPIO2C1_I2C3CAM_SDA << GPIO2C1_SHIFT | + GPIO2C0_I2C3CAM_SCL << GPIO2C0_SHIFT); + break; + case PERIPH_ID_I2C4: + rk_clrsetreg(&grf->gpio7cl_iomux, + GPIO7C1_MASK << GPIO7C1_SHIFT | + GPIO7C2_MASK << GPIO7C2_SHIFT, + GPIO7C1_I2C4TP_SDA << GPIO7C1_SHIFT | + GPIO7C2_I2C4TP_SCL << GPIO7C2_SHIFT); + break; + case PERIPH_ID_I2C5: + rk_clrsetreg(&grf->gpio7cl_iomux, + GPIO7C3_MASK << GPIO7C3_SHIFT, + GPIO7C3_I2C5HDMI_SDA << GPIO7C3_SHIFT); + rk_clrsetreg(&grf->gpio7ch_iomux, + GPIO7C4_MASK << GPIO7C4_SHIFT, + GPIO7C4_I2C5HDMI_SCL << GPIO7C4_SHIFT); + break; + default: + debug("i2c id = %d iomux error!\n", i2c_id); + break; + } +} + +static void pinctrl_rk3288_lcdc_config(struct rk3288_grf *grf, int lcd_id) +{ + switch (lcd_id) { + case PERIPH_ID_LCDC0: + rk_clrsetreg(&grf->gpio1d_iomux, + GPIO1D3_MASK << GPIO1D0_SHIFT | + GPIO1D2_MASK << GPIO1D2_SHIFT | + GPIO1D1_MASK << GPIO1D1_SHIFT | + GPIO1D0_MASK << GPIO1D0_SHIFT, + GPIO1D3_LCDC0_DCLK << GPIO1D3_SHIFT | + GPIO1D2_LCDC0_DEN << GPIO1D2_SHIFT | + GPIO1D1_LCDC0_VSYNC << GPIO1D1_SHIFT | + GPIO1D0_LCDC0_HSYNC << GPIO1D0_SHIFT); + break; + default: + debug("lcdc id = %d iomux error!\n", lcd_id); + break; + } +} + +static int pinctrl_rk3288_spi_config(struct rk3288_grf *grf, + enum periph_id spi_id, int cs) +{ + switch (spi_id) { + case PERIPH_ID_SPI0: + switch (cs) { + case 0: + rk_clrsetreg(&grf->gpio5b_iomux, + GPIO5B5_MASK << GPIO5B5_SHIFT, + GPIO5B5_SPI0_CSN0 << GPIO5B5_SHIFT); + break; + case 1: + rk_clrsetreg(&grf->gpio5c_iomux, + GPIO5C0_MASK << GPIO5C0_SHIFT, + GPIO5C0_SPI0_CSN1 << GPIO5C0_SHIFT); + break; + default: + goto err; + } + rk_clrsetreg(&grf->gpio5b_iomux, + GPIO5B7_MASK << GPIO5B7_SHIFT | + GPIO5B6_MASK << GPIO5B6_SHIFT | + GPIO5B4_MASK << GPIO5B4_SHIFT, + GPIO5B7_SPI0_RXD << GPIO5B7_SHIFT | + GPIO5B6_SPI0_TXD << GPIO5B6_SHIFT | + GPIO5B4_SPI0_CLK << GPIO5B4_SHIFT); + break; + case PERIPH_ID_SPI1: + if (cs != 0) + goto err; + rk_clrsetreg(&grf->gpio7b_iomux, + GPIO7B6_MASK << GPIO7B6_SHIFT | + GPIO7B7_MASK << GPIO7B7_SHIFT | + GPIO7B5_MASK << GPIO7B5_SHIFT | + GPIO7B4_MASK << GPIO7B4_SHIFT, + GPIO7B6_SPI1_RXD << GPIO7B6_SHIFT | + GPIO7B7_SPI1_TXD << GPIO7B7_SHIFT | + GPIO7B5_SPI1_CSN0 << GPIO7B5_SHIFT | + GPIO7B4_SPI1_CLK << GPIO7B4_SHIFT); + break; + case PERIPH_ID_SPI2: + switch (cs) { + case 0: + rk_clrsetreg(&grf->gpio8a_iomux, + GPIO8A7_MASK << GPIO8A7_SHIFT, + GPIO8A7_SPI2_CSN0 << GPIO8A7_SHIFT); + break; + case 1: + rk_clrsetreg(&grf->gpio8a_iomux, + GPIO8A3_MASK << GPIO8A3_SHIFT, + GPIO8A3_SPI2_CSN1 << GPIO8A3_SHIFT); + break; + default: + goto err; + } + rk_clrsetreg(&grf->gpio8b_iomux, + GPIO8B1_MASK << GPIO8B1_SHIFT | + GPIO8B0_MASK << GPIO8B0_SHIFT, + GPIO8B1_SPI2_TXD << GPIO8B1_SHIFT | + GPIO8B0_SPI2_RXD << GPIO8B0_SHIFT); + rk_clrsetreg(&grf->gpio8a_iomux, + GPIO8A6_MASK << GPIO8A6_SHIFT, + GPIO8A6_SPI2_CLK << GPIO8A6_SHIFT); + break; + default: + goto err; + } + + return 0; +err: + debug("rkspi: periph%d cs=%d not supported", spi_id, cs); + return -ENOENT; +} + +static void pinctrl_rk3288_uart_config(struct rk3288_grf *grf, int uart_id) +{ + switch (uart_id) { + case PERIPH_ID_UART_BT: + rk_clrsetreg(&grf->gpio4c_iomux, + GPIO4C3_MASK << GPIO4C3_SHIFT | + GPIO4C2_MASK << GPIO4C2_SHIFT | + GPIO4C1_MASK << GPIO4C1_SHIFT | + GPIO4C0_MASK << GPIO4C0_SHIFT, + GPIO4C3_UART0BT_RTSN << GPIO4C3_SHIFT | + GPIO4C2_UART0BT_CTSN << GPIO4C2_SHIFT | + GPIO4C1_UART0BT_SOUT << GPIO4C1_SHIFT | + GPIO4C0_UART0BT_SIN << GPIO4C0_SHIFT); + break; + case PERIPH_ID_UART_BB: + rk_clrsetreg(&grf->gpio5b_iomux, + GPIO5B3_MASK << GPIO5B3_SHIFT | + GPIO5B2_MASK << GPIO5B2_SHIFT | + GPIO5B1_MASK << GPIO5B1_SHIFT | + GPIO5B0_MASK << GPIO5B0_SHIFT, + GPIO5B3_UART1BB_RTSN << GPIO5B3_SHIFT | + GPIO5B2_UART1BB_CTSN << GPIO5B2_SHIFT | + GPIO5B1_UART1BB_SOUT << GPIO5B1_SHIFT | + GPIO5B0_UART1BB_SIN << GPIO5B0_SHIFT); + break; + case PERIPH_ID_UART_DBG: + rk_clrsetreg(&grf->gpio7ch_iomux, + GPIO7C7_MASK << GPIO7C7_SHIFT | + GPIO7C6_MASK << GPIO7C6_SHIFT, + GPIO7C7_UART2DBG_SOUT << GPIO7C7_SHIFT | + GPIO7C6_UART2DBG_SIN << GPIO7C6_SHIFT); + break; + case PERIPH_ID_UART_GPS: + rk_clrsetreg(&grf->gpio7b_iomux, + GPIO7B2_MASK << GPIO7B2_SHIFT | + GPIO7B1_MASK << GPIO7B1_SHIFT | + GPIO7B0_MASK << GPIO7B0_SHIFT, + GPIO7B2_UART3GPS_RTSN << GPIO7B2_SHIFT | + GPIO7B1_UART3GPS_CTSN << GPIO7B1_SHIFT | + GPIO7B0_UART3GPS_SOUT << GPIO7B0_SHIFT); + rk_clrsetreg(&grf->gpio7a_iomux, + GPIO7A7_MASK << GPIO7A7_SHIFT, + GPIO7A7_UART3GPS_SIN << GPIO7A7_SHIFT); + break; + case PERIPH_ID_UART_EXP: + rk_clrsetreg(&grf->gpio5b_iomux, + GPIO5B5_MASK << GPIO5B5_SHIFT | + GPIO5B4_MASK << GPIO5B4_SHIFT | + GPIO5B6_MASK << GPIO5B6_SHIFT | + GPIO5B7_MASK << GPIO5B7_SHIFT, + GPIO5B5_UART4EXP_RTSN << GPIO5B5_SHIFT | + GPIO5B4_UART4EXP_CTSN << GPIO5B4_SHIFT | + GPIO5B6_UART4EXP_SOUT << GPIO5B6_SHIFT | + GPIO5B7_UART4EXP_SIN << GPIO5B7_SHIFT); + break; + default: + debug("uart id = %d iomux error!\n", uart_id); + break; + } +} + +static void pinctrl_rk3288_sdmmc_config(struct rk3288_grf *grf, int mmc_id) +{ + switch (mmc_id) { + case PERIPH_ID_EMMC: + rk_clrsetreg(&grf->gpio3a_iomux, 0xffff, + GPIO3A7_EMMC_DATA7 << GPIO3A7_SHIFT | + GPIO3A6_EMMC_DATA6 << GPIO3A6_SHIFT | + GPIO3A5_EMMC_DATA5 << GPIO3A5_SHIFT | + GPIO3A4_EMMC_DATA4 << GPIO3A4_SHIFT | + GPIO3A3_EMMC_DATA3 << GPIO3A3_SHIFT | + GPIO3A2_EMMC_DATA2 << GPIO3A2_SHIFT | + GPIO3A1_EMMC_DATA1 << GPIO3A1_SHIFT | + GPIO3A0_EMMC_DATA0 << GPIO3A0_SHIFT); + rk_clrsetreg(&grf->gpio3b_iomux, GPIO3B1_MASK << GPIO3B1_SHIFT, + GPIO3B1_EMMC_PWREN << GPIO3B1_SHIFT); + rk_clrsetreg(&grf->gpio3c_iomux, + GPIO3C0_MASK << GPIO3C0_SHIFT, + GPIO3C0_EMMC_CMD << GPIO3C0_SHIFT); + break; + case PERIPH_ID_SDCARD: + rk_clrsetreg(&grf->gpio6c_iomux, 0xffff, + GPIO6C6_SDMMC0_DECTN << GPIO6C6_SHIFT | + GPIO6C5_SDMMC0_CMD << GPIO6C5_SHIFT | + GPIO6C4_SDMMC0_CLKOUT << GPIO6C4_SHIFT | + GPIO6C3_SDMMC0_DATA3 << GPIO6C3_SHIFT | + GPIO6C2_SDMMC0_DATA2 << GPIO6C2_SHIFT | + GPIO6C1_SDMMC0_DATA1 << GPIO6C1_SHIFT | + GPIO6C0_SDMMC0_DATA0 << GPIO6C0_SHIFT); + + /* use sdmmc0 io, disable JTAG function */ + rk_clrsetreg(&grf->soc_con0, 1 << GRF_FORCE_JTAG_SHIFT, 0); + break; + default: + debug("mmc id = %d iomux error!\n", mmc_id); + break; + } +} + +static void pinctrl_rk3288_hdmi_config(struct rk3288_grf *grf, int hdmi_id) +{ + switch (hdmi_id) { + case PERIPH_ID_HDMI: + rk_clrsetreg(&grf->gpio7cl_iomux, GPIO7C3_MASK << GPIO7C3_SHIFT, + GPIO7C3_EDPHDMII2C_SDA << GPIO7C3_SHIFT); + rk_clrsetreg(&grf->gpio7ch_iomux, GPIO7C4_MASK << GPIO7C4_SHIFT, + GPIO7C4_EDPHDMII2C_SCL << GPIO7C4_SHIFT); + break; + default: + debug("hdmi id = %d iomux error!\n", hdmi_id); + break; + } +} + +static int rk3288_pinctrl_request(struct udevice *dev, int func, int flags) +{ + struct rk3288_pinctrl_priv *priv = dev_get_priv(dev); + + debug("%s: func=%x, flags=%x\n", __func__, func, flags); + switch (func) { + case PERIPH_ID_PWM0: + case PERIPH_ID_PWM1: + case PERIPH_ID_PWM2: + case PERIPH_ID_PWM3: + case PERIPH_ID_PWM4: + pinctrl_rk3288_pwm_config(priv->grf, func); + break; + case PERIPH_ID_I2C0: + case PERIPH_ID_I2C1: + case PERIPH_ID_I2C2: + case PERIPH_ID_I2C3: + case PERIPH_ID_I2C4: + case PERIPH_ID_I2C5: + pinctrl_rk3288_i2c_config(priv->grf, priv->pmu, func); + break; + case PERIPH_ID_SPI0: + case PERIPH_ID_SPI1: + case PERIPH_ID_SPI2: + pinctrl_rk3288_spi_config(priv->grf, func, flags); + break; + case PERIPH_ID_UART0: + case PERIPH_ID_UART1: + case PERIPH_ID_UART2: + case PERIPH_ID_UART3: + case PERIPH_ID_UART4: + pinctrl_rk3288_uart_config(priv->grf, func); + break; + case PERIPH_ID_LCDC0: + case PERIPH_ID_LCDC1: + pinctrl_rk3288_lcdc_config(priv->grf, func); + break; + case PERIPH_ID_SDMMC0: + case PERIPH_ID_SDMMC1: + pinctrl_rk3288_sdmmc_config(priv->grf, func); + break; + case PERIPH_ID_HDMI: + pinctrl_rk3288_hdmi_config(priv->grf, func); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rk3288_pinctrl_get_periph_id(struct udevice *dev, + struct udevice *periph) +{ + u32 cell[3]; + int ret; + + ret = fdtdec_get_int_array(gd->fdt_blob, periph->of_offset, + "interrupts", cell, ARRAY_SIZE(cell)); + if (ret < 0) + return -EINVAL; + + switch (cell[1]) { + case 44: + return PERIPH_ID_SPI0; + case 45: + return PERIPH_ID_SPI1; + case 46: + return PERIPH_ID_SPI2; + case 60: + return PERIPH_ID_I2C0; + case 62: /* Note strange order */ + return PERIPH_ID_I2C1; + case 61: + return PERIPH_ID_I2C2; + case 63: + return PERIPH_ID_I2C3; + case 64: + return PERIPH_ID_I2C4; + case 65: + return PERIPH_ID_I2C5; + } + + return -ENOENT; +} + +static int rk3288_pinctrl_set_state_simple(struct udevice *dev, + struct udevice *periph) +{ + int func; + + func = rk3288_pinctrl_get_periph_id(dev, periph); + if (func < 0) + return func; + return rk3288_pinctrl_request(dev, func, 0); +} + +static struct pinctrl_ops rk3288_pinctrl_ops = { + .set_state_simple = rk3288_pinctrl_set_state_simple, + .request = rk3288_pinctrl_request, + .get_periph_id = rk3288_pinctrl_get_periph_id, +}; + +static int rk3288_pinctrl_probe(struct udevice *dev) +{ + struct rk3288_pinctrl_priv *priv = dev_get_priv(dev); + + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + priv->pmu = syscon_get_first_range(ROCKCHIP_SYSCON_PMU); + debug("%s: grf=%p, pmu=%p\n", __func__, priv->grf, priv->pmu); + + return 0; +} + +static const struct udevice_id rk3288_pinctrl_ids[] = { + { .compatible = "rockchip,rk3288-pinctrl" }, + { } +}; + +U_BOOT_DRIVER(pinctrl_rk3288) = { + .name = "pinctrl_rk3288", + .id = UCLASS_PINCTRL, + .of_match = rk3288_pinctrl_ids, + .priv_auto_alloc_size = sizeof(struct rk3288_pinctrl_priv), + .ops = &rk3288_pinctrl_ops, + .probe = rk3288_pinctrl_probe, +}; diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig index fc6a3743dc..547fd1aaa6 100644 --- a/drivers/power/pmic/Kconfig +++ b/drivers/power/pmic/Kconfig @@ -10,6 +10,15 @@ config DM_PMIC - 'drivers/power/pmic/pmic-uclass.c' - 'include/power/pmic.h' +config PMIC_ACT8846 + bool "Enable support for the active-semi 8846 PMIC" + depends on DM_PMIC && DM_I2C + ---help--- + This PMIC includes 4 DC/DC step-down buck regulators and 8 low-dropout + regulators (LDOs). It also provides some GPIO, reset and battery + functions. It uses an I2C interface and is designed for use with + tablets and smartphones. + config DM_PMIC_PFUZE100 bool "Enable Driver Model for PMIC PFUZE100" depends on DM_PMIC diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index e7a761703e..00fde71b2c 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_DM_PMIC) += pmic-uclass.o obj-$(CONFIG_DM_PMIC_MAX77686) += max77686.o obj-$(CONFIG_DM_PMIC_PFUZE100) += pfuze100.o obj-$(CONFIG_DM_PMIC_SANDBOX) += sandbox.o i2c_pmic_emul.o +obj-$(CONFIG_PMIC_ACT8846) += act8846.o obj-$(CONFIG_PMIC_TPS65090) += tps65090.o obj-$(CONFIG_PMIC_S5M8767) += s5m8767.o diff --git a/drivers/power/pmic/act8846.c b/drivers/power/pmic/act8846.c new file mode 100644 index 0000000000..ff096b3a9c --- /dev/null +++ b/drivers/power/pmic/act8846.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <fdtdec.h> +#include <libfdt.h> +#include <power/act8846_pmic.h> +#include <power/pmic.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const struct pmic_child_info pmic_children_info[] = { + { .prefix = "REG", .driver = "act8846_reg"}, + { }, +}; + +static int act8846_reg_count(struct udevice *dev) +{ + return ACT8846_NUM_OF_REGS; +} + +static int act8846_write(struct udevice *dev, uint reg, const uint8_t *buff, + int len) +{ + if (dm_i2c_write(dev, reg, buff, len)) { + debug("write error to device: %p register: %#x!", dev, reg); + return -EIO; + } + + return 0; +} + +static int act8846_read(struct udevice *dev, uint reg, uint8_t *buff, int len) +{ + if (dm_i2c_read(dev, reg, buff, len)) { + debug("read error from device: %p register: %#x!", dev, reg); + return -EIO; + } + + return 0; +} + +static int act8846_bind(struct udevice *dev) +{ + const void *blob = gd->fdt_blob; + int regulators_node; + int children; + + regulators_node = fdt_subnode_offset(blob, dev->of_offset, + "regulators"); + if (regulators_node <= 0) { + debug("%s: %s regulators subnode not found!", __func__, + dev->name); + return -ENXIO; + } + + debug("%s: '%s' - found regulators subnode\n", __func__, dev->name); + + children = pmic_bind_children(dev, regulators_node, pmic_children_info); + if (!children) + debug("%s: %s - no child found\n", __func__, dev->name); + + /* Always return success for this device */ + return 0; +} + +static struct dm_pmic_ops act8846_ops = { + .reg_count = act8846_reg_count, + .read = act8846_read, + .write = act8846_write, +}; + +static const struct udevice_id act8846_ids[] = { + { .compatible = "active-semi,act8846" }, + { } +}; + +U_BOOT_DRIVER(pmic_act8846) = { + .name = "act8846 pmic", + .id = UCLASS_PMIC, + .of_match = act8846_ids, + .bind = act8846_bind, + .ops = &act8846_ops, +}; diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig index 77d64e43c6..434dd029b5 100644 --- a/drivers/power/regulator/Kconfig +++ b/drivers/power/regulator/Kconfig @@ -16,6 +16,15 @@ config DM_REGULATOR for this purpose if PMIC I/O driver is implemented or dm_scan_fdt_node() otherwise. Detailed information can be found in the header file. +config REGULATOR_ACT8846 + bool "Enable driver for ACT8846 regulator" + depends on DM_REGULATOR && PMIC_ACT8846 + ---help--- + Enable support for the regulator functions of the ACT8846 PMIC. The + driver implements get/set api for the various BUCKS and LDOS supported + by the PMIC device. This driver is controlled by a device tree node + which includes voltage limits. + config DM_REGULATOR_PFUZE100 bool "Enable Driver Model for REGULATOR PFUZE100" depends on DM_REGULATOR && DM_PMIC_PFUZE100 diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile index 7035936a35..c85978e024 100644 --- a/drivers/power/regulator/Makefile +++ b/drivers/power/regulator/Makefile @@ -6,6 +6,7 @@ # obj-$(CONFIG_DM_REGULATOR) += regulator-uclass.o +obj-$(CONFIG_REGULATOR_ACT8846) += act8846.o obj-$(CONFIG_DM_REGULATOR_MAX77686) += max77686.o obj-$(CONFIG_DM_REGULATOR_PFUZE100) += pfuze100.o obj-$(CONFIG_DM_REGULATOR_FIXED) += fixed.o diff --git a/drivers/power/regulator/act8846.c b/drivers/power/regulator/act8846.c new file mode 100644 index 0000000000..255f8b096e --- /dev/null +++ b/drivers/power/regulator/act8846.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * Based on Rockchip's drivers/power/pmic/pmic_act8846.c: + * Copyright (C) 2012 rockchips + * zyw <zyw@rock-chips.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <power/act8846_pmic.h> +#include <power/pmic.h> +#include <power/regulator.h> + +static const u16 voltage_map[] = { + 600, 625, 650, 675, 700, 725, 750, 775, + 800, 825, 850, 875, 900, 925, 950, 975, + 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175, + 1200, 1250, 1300, 1350, 1400, 1450, 1500, 1550, + 1600, 1650, 1700, 1750, 1800, 1850, 1900, 1950, + 2000, 2050, 2100, 2150, 2200, 2250, 2300, 2350, + 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, + 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900, +}; + +enum { + REG_SYS0, + REG_SYS1, + REG1_VOL = 0x10, + REG1_CTL = 0X11, + REG2_VOL0 = 0x20, + REG2_VOL1, + REG2_CTL, + REG3_VOL0 = 0x30, + REG3_VOL1, + REG3_CTL, + REG4_VOL0 = 0x40, + REG4_VOL1, + REG4_CTL, + REG5_VOL = 0x50, + REG5_CTL, + REG6_VOL = 0X58, + REG6_CTL, + REG7_VOL = 0x60, + REG7_CTL, + REG8_VOL = 0x68, + REG8_CTL, + REG9_VOL = 0x70, + REG9_CTL, + REG10_VOL = 0x80, + REG10_CTL, + REG11_VOL = 0x90, + REG11_CTL, + REG12_VOL = 0xa0, + REG12_CTL, + REG13 = 0xb1, +}; + +static const u8 addr_vol[] = { + 0, REG1_VOL, REG2_VOL0, REG3_VOL0, REG4_VOL0, + REG5_VOL, REG6_VOL, REG7_VOL, REG8_VOL, REG9_VOL, + REG10_VOL, REG11_VOL, REG12_VOL, +}; + +static const u8 addr_ctl[] = { + 0, REG1_CTL, REG2_CTL, REG3_CTL, REG4_CTL, + REG5_CTL, REG6_CTL, REG7_CTL, REG8_CTL, REG9_CTL, + REG10_CTL, REG11_CTL, REG12_CTL, +}; + +static int check_volt_table(const u16 *volt_table, int uvolt) +{ + int i; + + for (i = VOL_MIN_IDX; i < VOL_MAX_IDX; i++) { + if (uvolt <= (volt_table[i] * 1000)) + return i; + } + return -EINVAL; +} + +static int reg_get_value(struct udevice *dev) +{ + int reg = dev->driver_data; + int ret; + + ret = pmic_reg_read(dev->parent, reg); + if (ret < 0) + return ret; + + return voltage_map[ret & LDO_VOL_MASK] * 1000; +} + +static int reg_set_value(struct udevice *dev, int uvolt) +{ + int reg = dev->driver_data; + int val; + + val = check_volt_table(voltage_map, uvolt); + if (val < 0) + return val; + + return pmic_clrsetbits(dev->parent, addr_vol[reg], LDO_VOL_MASK, val); +} + +static int reg_set_enable(struct udevice *dev, bool enable) +{ + int reg = dev->driver_data; + + return pmic_clrsetbits(dev->parent, addr_ctl[reg], LDO_EN_MASK, + enable ? LDO_EN_MASK : 0); +} + +static bool reg_get_enable(struct udevice *dev) +{ + int reg = dev->driver_data; + int ret; + + ret = pmic_reg_read(dev->parent, reg); + if (ret < 0) + return ret; + + return ret & LDO_EN_MASK ? true : false; +} + +static int act8846_reg_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_platdata *uc_pdata; + int reg = dev->driver_data; + + uc_pdata = dev_get_uclass_platdata(dev); + + uc_pdata->type = reg <= 4 ? REGULATOR_TYPE_BUCK : REGULATOR_TYPE_LDO; + uc_pdata->mode_count = 0; + + return 0; +} + +static const struct dm_regulator_ops act8846_reg_ops = { + .get_value = reg_get_value, + .set_value = reg_set_value, + .get_enable = reg_get_enable, + .set_enable = reg_set_enable, +}; + +U_BOOT_DRIVER(act8846_buck) = { + .name = "act8846_reg", + .id = UCLASS_REGULATOR, + .ops = &act8846_reg_ops, + .probe = act8846_reg_probe, +}; diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 002f1dafe1..ccb80d2d1d 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -109,6 +109,15 @@ config DEBUG_UART_SHIFT value. Use this value to specify the shift to use, where 0=byte registers, 2=32-bit word registers, etc. +config ROCKCHIP_SERIAL + bool "Rockchip on-chip UART support" + depends on ARCH_UNIPHIER && DM_SERIAL + help + Select this to enable a debug UART for Rockchip devices. This uses + the ns16550 driver. You will need to #define CONFIG_SYS_NS16550 in + your board config header. The clock input is automatically set to + use the oscillator (24MHz). + config SANDBOX_SERIAL bool "Sandbox UART support" depends on SANDBOX diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 1d1f0361cd..869ea7b8fb 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_ZYNQ_SERIAL) += serial_zynq.o obj-$(CONFIG_BFIN_SERIAL) += serial_bfin.o obj-$(CONFIG_FSL_LPUART) += serial_lpuart.o obj-$(CONFIG_MXS_AUART) += mxs_auart.o +obj-$(CONFIG_ROCKCHIP_SERIAL) += serial_rockchip.o obj-$(CONFIG_ARC_SERIAL) += serial_arc.o obj-$(CONFIG_TEGRA_SERIAL) += serial_tegra.o obj-$(CONFIG_UNIPHIER_SERIAL) += serial_uniphier.o diff --git a/drivers/serial/serial_rockchip.c b/drivers/serial/serial_rockchip.c new file mode 100644 index 0000000000..0e7bbfc690 --- /dev/null +++ b/drivers/serial/serial_rockchip.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015 Google, Inc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <ns16550.h> +#include <serial.h> +#include <asm/arch/clock.h> + +static const struct udevice_id rockchip_serial_ids[] = { + { .compatible = "rockchip,rk3288-uart" }, + { } +}; + +static int rockchip_serial_ofdata_to_platdata(struct udevice *dev) +{ + struct ns16550_platdata *plat = dev_get_platdata(dev); + int ret; + + ret = ns16550_serial_ofdata_to_platdata(dev); + if (ret) + return ret; + + /* Do all Rockchip parts use 24MHz? */ + plat->clock = 24 * 1000000; + + return 0; +} + +U_BOOT_DRIVER(serial_ns16550) = { + .name = "serial_rockchip", + .id = UCLASS_SERIAL, + .of_match = rockchip_serial_ids, + .ofdata_to_platdata = rockchip_serial_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct ns16550_platdata), + .priv_auto_alloc_size = sizeof(struct NS16550), + .probe = ns16550_serial_probe, + .ops = &ns16550_serial_ops, + .flags = DM_FLAG_PRE_RELOC, +}; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index c84a7b74d6..8e04fce6f2 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -58,6 +58,14 @@ config ICH_SPI access the SPI NOR flash on platforms embedding this Intel ICH IP core. +config ROCKCHIP_SPI + bool "Rockchip SPI driver" + help + Enable the Rockchip SPI driver, used to access SPI NOR flash and + other SPI peripherals (such as the Chrome OS EC) on Rockchip SoCs. + This uses driver model and requires a device tree binding to + operate. + config SANDBOX_SPI bool "Sandbox SPI driver" depends on SANDBOX && DM diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index ee88aa162b..de241be15a 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o obj-$(CONFIG_MXC_SPI) += mxc_spi.o obj-$(CONFIG_MXS_SPI) += mxs_spi.o obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o +obj-$(CONFIG_ROCKCHIP_SPI) += rk_spi.o obj-$(CONFIG_SANDBOX_SPI) += sandbox_spi.o obj-$(CONFIG_SH_SPI) += sh_spi.o obj-$(CONFIG_SH_QSPI) += sh_qspi.o diff --git a/drivers/spi/rk_spi.c b/drivers/spi/rk_spi.c new file mode 100644 index 0000000000..5e0c6ad278 --- /dev/null +++ b/drivers/spi/rk_spi.c @@ -0,0 +1,374 @@ +/* + * spi driver for rockchip + * + * (C) Copyright 2015 Google, Inc + * + * (C) Copyright 2008-2013 Rockchip Electronics + * Peter, Software Engineering, <superpeter.cai@gmail.com>. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <errno.h> +#include <spi.h> +#include <asm/errno.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/periph.h> +#include <dm/pinctrl.h> +#include "rk_spi.h" + +DECLARE_GLOBAL_DATA_PTR; + +/* Change to 1 to output registers at the start of each transaction */ +#define DEBUG_RK_SPI 0 + +struct rockchip_spi_platdata { + enum periph_id periph_id; + struct udevice *pinctrl; + s32 frequency; /* Default clock frequency, -1 for none */ + fdt_addr_t base; + uint deactivate_delay_us; /* Delay to wait after deactivate */ +}; + +struct rockchip_spi_priv { + struct rockchip_spi *regs; + struct udevice *clk_gpll; + unsigned int max_freq; + unsigned int mode; + enum periph_id periph_id; /* Peripheral ID for this device */ + ulong last_transaction_us; /* Time of last transaction end */ + u8 bits_per_word; /* max 16 bits per word */ + u8 n_bytes; + unsigned int speed_hz; + unsigned int tmode; + uint input_rate; +}; + +#define SPI_FIFO_DEPTH 32 + +static void rkspi_dump_regs(struct rockchip_spi *regs) +{ + debug("ctrl0: \t\t0x%08x\n", readl(®s->ctrlr0)); + debug("ctrl1: \t\t0x%08x\n", readl(®s->ctrlr1)); + debug("ssienr: \t\t0x%08x\n", readl(®s->enr)); + debug("ser: \t\t0x%08x\n", readl(®s->ser)); + debug("baudr: \t\t0x%08x\n", readl(®s->baudr)); + debug("txftlr: \t\t0x%08x\n", readl(®s->txftlr)); + debug("rxftlr: \t\t0x%08x\n", readl(®s->rxftlr)); + debug("txflr: \t\t0x%08x\n", readl(®s->txflr)); + debug("rxflr: \t\t0x%08x\n", readl(®s->rxflr)); + debug("sr: \t\t0x%08x\n", readl(®s->sr)); + debug("imr: \t\t0x%08x\n", readl(®s->imr)); + debug("isr: \t\t0x%08x\n", readl(®s->isr)); + debug("dmacr: \t\t0x%08x\n", readl(®s->dmacr)); + debug("dmatdlr: \t0x%08x\n", readl(®s->dmatdlr)); + debug("dmardlr: \t0x%08x\n", readl(®s->dmardlr)); +} + +static void rkspi_enable_chip(struct rockchip_spi *regs, bool enable) +{ + writel(enable ? 1 : 0, ®s->enr); +} + +static void rkspi_set_clk(struct rockchip_spi_priv *priv, uint speed) +{ + uint clk_div; + + clk_div = clk_get_divisor(priv->input_rate, speed); + debug("spi speed %u, div %u\n", speed, clk_div); + + writel(clk_div, &priv->regs->baudr); +} + +static int rkspi_wait_till_not_busy(struct rockchip_spi *regs) +{ + unsigned long start; + + start = get_timer(0); + while (readl(®s->sr) & SR_BUSY) { + if (get_timer(start) > ROCKCHIP_SPI_TIMEOUT_MS) { + debug("RK SPI: Status keeps busy for 1000us after a read/write!\n"); + return -ETIMEDOUT; + } + } + + return 0; +} + +static void spi_cs_activate(struct rockchip_spi *regs, uint cs) +{ + debug("activate cs%u\n", cs); + writel(1 << cs, ®s->ser); +} + +static void spi_cs_deactivate(struct rockchip_spi *regs, uint cs) +{ + debug("deactivate cs%u\n", cs); + writel(0, ®s->ser); +} + +static int rockchip_spi_ofdata_to_platdata(struct udevice *bus) +{ + struct rockchip_spi_platdata *plat = bus->platdata; + const void *blob = gd->fdt_blob; + int node = bus->of_offset; + int ret; + + plat->base = dev_get_addr(bus); + ret = uclass_get_device(UCLASS_PINCTRL, 0, &plat->pinctrl); + if (ret) + return ret; + ret = pinctrl_get_periph_id(plat->pinctrl, bus); + + if (ret < 0) { + debug("%s: Could not get peripheral ID for %s: %d\n", __func__, + bus->name, ret); + return -FDT_ERR_NOTFOUND; + } + plat->periph_id = ret; + + plat->frequency = fdtdec_get_int(blob, node, "spi-max-frequency", + 50000000); + plat->deactivate_delay_us = fdtdec_get_int(blob, node, + "spi-deactivate-delay", 0); + debug("%s: base=%x, periph_id=%d, max-frequency=%d, deactivate_delay=%d\n", + __func__, plat->base, plat->periph_id, plat->frequency, + plat->deactivate_delay_us); + + return 0; +} + +static int rockchip_spi_probe(struct udevice *bus) +{ + struct rockchip_spi_platdata *plat = dev_get_platdata(bus); + struct rockchip_spi_priv *priv = dev_get_priv(bus); + int ret; + + debug("%s: probe\n", __func__); + priv->regs = (struct rockchip_spi *)plat->base; + + priv->last_transaction_us = timer_get_us(); + priv->max_freq = plat->frequency; + priv->periph_id = plat->periph_id; + ret = uclass_get_device(UCLASS_CLK, CLK_GENERAL, &priv->clk_gpll); + if (ret) { + debug("%s: Failed to find CLK_GENERAL: %d\n", __func__, ret); + return ret; + } + + /* + * Use 99 MHz as our clock since it divides nicely into 594 MHz which + * is the assumed speed for CLK_GENERAL. + */ + ret = clk_set_periph_rate(priv->clk_gpll, plat->periph_id, 99000000); + if (ret < 0) { + debug("%s: Failed to set clock: %d\n", __func__, ret); + return ret; + } + priv->input_rate = ret; + debug("%s: rate = %u\n", __func__, priv->input_rate); + priv->bits_per_word = 8; + priv->tmode = TMOD_TR; /* Tx & Rx */ + + return 0; +} + +static int rockchip_spi_claim_bus(struct udevice *dev) +{ + struct udevice *bus = dev->parent; + struct rockchip_spi_platdata *plat = dev_get_platdata(bus); + struct rockchip_spi_priv *priv = dev_get_priv(bus); + struct rockchip_spi *regs = priv->regs; + struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev); + u8 spi_dfs, spi_tf; + uint ctrlr0; + int ret; + + /* Disable the SPI hardware */ + rkspi_enable_chip(regs, 0); + + switch (priv->bits_per_word) { + case 8: + priv->n_bytes = 1; + spi_dfs = DFS_8BIT; + spi_tf = HALF_WORD_OFF; + break; + case 16: + priv->n_bytes = 2; + spi_dfs = DFS_16BIT; + spi_tf = HALF_WORD_ON; + break; + default: + debug("%s: unsupported bits: %dbits\n", __func__, + priv->bits_per_word); + return -EPROTONOSUPPORT; + } + + rkspi_set_clk(priv, priv->speed_hz); + + /* Operation Mode */ + ctrlr0 = OMOD_MASTER << OMOD_SHIFT; + + /* Data Frame Size */ + ctrlr0 |= spi_dfs & DFS_MASK << DFS_SHIFT; + + /* set SPI mode 0..3 */ + if (priv->mode & SPI_CPOL) + ctrlr0 |= SCOL_HIGH << SCOL_SHIFT; + if (priv->mode & SPI_CPHA) + ctrlr0 |= SCPH_TOGSTA << SCPH_SHIFT; + + /* Chip Select Mode */ + ctrlr0 |= CSM_KEEP << CSM_SHIFT; + + /* SSN to Sclk_out delay */ + ctrlr0 |= SSN_DELAY_ONE << SSN_DELAY_SHIFT; + + /* Serial Endian Mode */ + ctrlr0 |= SEM_LITTLE << SEM_SHIFT; + + /* First Bit Mode */ + ctrlr0 |= FBM_MSB << FBM_SHIFT; + + /* Byte and Halfword Transform */ + ctrlr0 |= (spi_tf & HALF_WORD_MASK) << HALF_WORD_TX_SHIFT; + + /* Rxd Sample Delay */ + ctrlr0 |= 0 << RXDSD_SHIFT; + + /* Frame Format */ + ctrlr0 |= FRF_SPI << FRF_SHIFT; + + /* Tx and Rx mode */ + ctrlr0 |= (priv->tmode & TMOD_MASK) << TMOD_SHIFT; + + writel(ctrlr0, ®s->ctrlr0); + + ret = pinctrl_request(plat->pinctrl, priv->periph_id, slave_plat->cs); + if (ret) { + debug("%s: Cannot request pinctrl: %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int rockchip_spi_release_bus(struct udevice *dev) +{ + return 0; +} + +static int rockchip_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct rockchip_spi_priv *priv = dev_get_priv(bus); + struct rockchip_spi *regs = priv->regs; + struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev); + int len = bitlen >> 3; + const u8 *out = dout; + u8 *in = din; + int toread, towrite; + int ret; + + debug("%s: dout=%p, din=%p, len=%x, flags=%lx\n", __func__, dout, din, + len, flags); + if (DEBUG_RK_SPI) + rkspi_dump_regs(regs); + + /* Assert CS before transfer */ + if (flags & SPI_XFER_BEGIN) + spi_cs_activate(regs, slave_plat->cs); + + while (len > 0) { + int todo = min(len, 0xffff); + + rkspi_enable_chip(regs, true); + writel(todo - 1, ®s->ctrlr1); + rkspi_enable_chip(regs, true); + + toread = todo; + towrite = todo; + while (toread || towrite) { + u32 status = readl(®s->sr); + + if (towrite && !(status & SR_TF_FULL)) { + writel(out ? *out++ : 0, regs->txdr); + towrite--; + } + if (toread && !(status & SR_RF_EMPT)) { + u32 byte = readl(regs->rxdr); + + if (in) + *in++ = byte; + toread--; + } + } + ret = rkspi_wait_till_not_busy(regs); + if (ret) + break; + len -= todo; + } + + /* Deassert CS after transfer */ + if (flags & SPI_XFER_END) + spi_cs_deactivate(regs, slave_plat->cs); + + rkspi_enable_chip(regs, false); + + return ret; +} + +static int rockchip_spi_set_speed(struct udevice *bus, uint speed) +{ + struct rockchip_spi_priv *priv = dev_get_priv(bus); + + if (speed > ROCKCHIP_SPI_MAX_RATE) + return -EINVAL; + if (speed > priv->max_freq) + speed = priv->max_freq; + priv->speed_hz = speed; + + return 0; +} + +static int rockchip_spi_set_mode(struct udevice *bus, uint mode) +{ + struct rockchip_spi_priv *priv = dev_get_priv(bus); + + priv->mode = mode; + + return 0; +} + +static const struct dm_spi_ops rockchip_spi_ops = { + .claim_bus = rockchip_spi_claim_bus, + .release_bus = rockchip_spi_release_bus, + .xfer = rockchip_spi_xfer, + .set_speed = rockchip_spi_set_speed, + .set_mode = rockchip_spi_set_mode, + /* + * cs_info is not needed, since we require all chip selects to be + * in the device tree explicitly + */ +}; + +static const struct udevice_id rockchip_spi_ids[] = { + { .compatible = "rockchip,rk3288-spi" }, + { } +}; + +U_BOOT_DRIVER(rockchip_spi) = { + .name = "rockchip_spi", + .id = UCLASS_SPI, + .of_match = rockchip_spi_ids, + .ops = &rockchip_spi_ops, + .ofdata_to_platdata = rockchip_spi_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct rockchip_spi_platdata), + .priv_auto_alloc_size = sizeof(struct rockchip_spi_priv), + .probe = rockchip_spi_probe, +}; diff --git a/drivers/spi/rk_spi.h b/drivers/spi/rk_spi.h new file mode 100644 index 0000000000..f1ac81203f --- /dev/null +++ b/drivers/spi/rk_spi.h @@ -0,0 +1,124 @@ +/* + * SPI driver for rockchip + * + * (C) Copyright 2015 Google, Inc + * + * (C) Copyright 2008-2013 Rockchip Electronics + * Peter, Software Engineering, <superpeter.cai@gmail.com>. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __RK_SPI_H +#define __RK_SPI_H + +struct rockchip_spi { + u32 ctrlr0; + u32 ctrlr1; + u32 enr; + u32 ser; + u32 baudr; + u32 txftlr; + u32 rxftlr; + u32 txflr; + u32 rxflr; + u32 sr; + u32 ipr; + u32 imr; + u32 isr; + u32 risr; + u32 icr; + u32 dmacr; + u32 dmatdlr; + u32 dmardlr; /* 0x44 */ + u32 reserved[0xef]; + u32 txdr[0x100]; /* 0x400 */ + u32 rxdr[0x100]; /* 0x800 */ +}; + +/* CTRLR0 */ +enum { + DFS_SHIFT = 0, /* Data Frame Size */ + DFS_MASK = 3, + DFS_4BIT = 0, + DFS_8BIT, + DFS_16BIT, + DFS_RESV, + + CFS_SHIFT = 2, /* Control Frame Size */ + CFS_MASK = 0xf, + + SCPH_SHIFT = 6, /* Serial Clock Phase */ + SCPH_MASK = 1, + SCPH_TOGMID = 0, /* SCLK toggles in middle of first data bit */ + SCPH_TOGSTA, /* SCLK toggles at start of first data bit */ + + SCOL_SHIFT = 7, /* Serial Clock Polarity */ + SCOL_MASK = 1, + SCOL_LOW = 0, /* Inactive state of serial clock is low */ + SCOL_HIGH, /* Inactive state of serial clock is high */ + + CSM_SHIFT = 8, /* Chip Select Mode */ + CSM_MASK = 0x3, + CSM_KEEP = 0, /* ss_n stays low after each frame */ + CSM_HALF, /* ss_n high for half sclk_out cycles */ + CSM_ONE, /* ss_n high for one sclk_out cycle */ + CSM_RESV, + + SSN_DELAY_SHIFT = 10, /* SSN to Sclk_out delay */ + SSN_DELAY_MASK = 1, + SSN_DELAY_HALF = 0, /* 1/2 sclk_out cycle */ + SSN_DELAY_ONE = 1, /* 1 sclk_out cycle */ + + SEM_SHIFT = 11, /* Serial Endian Mode */ + SEM_MASK = 1, + SEM_LITTLE = 0, /* little endian */ + SEM_BIG, /* big endian */ + + FBM_SHIFT = 12, /* First Bit Mode */ + FBM_MASK = 1, + FBM_MSB = 0, /* first bit is MSB */ + FBM_LSB, /* first bit in LSB */ + + HALF_WORD_TX_SHIFT = 13, /* Byte and Halfword Transform */ + HALF_WORD_MASK = 1, + HALF_WORD_ON = 0, /* apb 16bit write/read, spi 8bit write/read */ + HALF_WORD_OFF, /* apb 8bit write/read, spi 8bit write/read */ + + RXDSD_SHIFT = 14, /* Rxd Sample Delay, in cycles */ + RXDSD_MASK = 3, + + FRF_SHIFT = 16, /* Frame Format */ + FRF_MASK = 3, + FRF_SPI = 0, /* Motorola SPI */ + FRF_SSP, /* Texas Instruments SSP*/ + FRF_MICROWIRE, /* National Semiconductors Microwire */ + FRF_RESV, + + TMOD_SHIFT = 18, /* Transfer Mode */ + TMOD_MASK = 3, + TMOD_TR = 0, /* xmit & recv */ + TMOD_TO, /* xmit only */ + TMOD_RO, /* recv only */ + TMOD_RESV, + + OMOD_SHIFT = 20, /* Operation Mode */ + OMOD_MASK = 1, + OMOD_MASTER = 0, /* Master Mode */ + OMOD_SLAVE, /* Slave Mode */ +}; + +/* SR */ +enum { + SR_MASK = 0x7f, + SR_BUSY = 1 << 0, + SR_TF_FULL = 1 << 1, + SR_TF_EMPT = 1 << 2, + SR_RF_EMPT = 1 << 3, + SR_RF_FULL = 1 << 4, +}; + +#define ROCKCHIP_SPI_TIMEOUT_MS 1000 +#define ROCKCHIP_SPI_MAX_RATE 48000000 + +#endif /* __RK_SPI_H */ |