diff options
Diffstat (limited to 'drivers')
51 files changed, 4419 insertions, 241 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index c481e93..70993fd 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -46,6 +46,8 @@ source "drivers/pinctrl/Kconfig" source "drivers/power/Kconfig" +source "drivers/pwm/Kconfig" + source "drivers/ram/Kconfig" source "drivers/remoteproc/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 4a6a4a8..8aa81f4 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -5,7 +5,7 @@ # SPDX-License-Identifier: GPL-2.0+ # -obj-$(CONFIG_CLK) += clk-uclass.o +obj-$(CONFIG_CLK) += clk-uclass.o clk_fixed_rate.o obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o obj-$(CONFIG_SANDBOX) += clk_sandbox.o diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c index 19f6f07..b483c1e 100644 --- a/drivers/clk/clk-uclass.c +++ b/drivers/clk/clk-uclass.c @@ -12,6 +12,8 @@ #include <dm/lists.h> #include <dm/root.h> +DECLARE_GLOBAL_DATA_PTR; + ulong clk_get_rate(struct udevice *dev) { struct clk_ops *ops = clk_get_ops(dev); @@ -62,6 +64,48 @@ ulong clk_set_periph_rate(struct udevice *dev, int periph, ulong rate) return ops->set_periph_rate(dev, periph, rate); } +#if CONFIG_IS_ENABLED(OF_CONTROL) +int clk_get_by_index(struct udevice *dev, int index, struct udevice **clk_devp) +{ + int ret; +#ifdef CONFIG_SPL_BUILD + u32 cell[2]; + + if (index != 0) + return -ENOSYS; + assert(*clk_devp); + ret = uclass_get_device(UCLASS_CLK, 0, clk_devp); + if (ret) + return ret; + ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clocks", + cell, 2); + if (ret) + return ret; + return cell[1]; +#else + struct fdtdec_phandle_args args; + + assert(*clk_devp); + ret = fdtdec_parse_phandle_with_args(gd->fdt_blob, dev->of_offset, + "clocks", "#clock-cells", 0, index, + &args); + if (ret) { + debug("%s: fdtdec_parse_phandle_with_args failed: err=%d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_of_offset(UCLASS_CLK, args.node, clk_devp); + if (ret) { + debug("%s: uclass_get_device_by_of_offset failed: err=%d\n", + __func__, ret); + return ret; + } + return args.args_count > 0 ? args.args[0] : 0; +#endif +} +#endif + UCLASS_DRIVER(clk) = { .id = UCLASS_CLK, .name = "clk", diff --git a/drivers/clk/clk_fixed_rate.c b/drivers/clk/clk_fixed_rate.c new file mode 100644 index 0000000..8beda9c --- /dev/null +++ b/drivers/clk/clk_fixed_rate.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Masahiro Yamada <yamada.masahiro@socionext.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <dm/device.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct clk_fixed_rate { + unsigned long fixed_rate; +}; + +#define to_clk_fixed_rate(dev) ((struct clk_fixed_rate *)dev_get_platdata(dev)) + +static ulong clk_fixed_rate_get_rate(struct udevice *dev) +{ + return to_clk_fixed_rate(dev)->fixed_rate; +} + +static ulong clk_fixed_rate_get_periph_rate(struct udevice *dev, int periph) +{ + return clk_fixed_rate_get_rate(dev); +} + +const struct clk_ops clk_fixed_rate_ops = { + .get_rate = clk_fixed_rate_get_rate, + .get_periph_rate = clk_fixed_rate_get_periph_rate, +}; + +static int clk_fixed_rate_ofdata_to_platdata(struct udevice *dev) +{ + to_clk_fixed_rate(dev)->fixed_rate = + fdtdec_get_int(gd->fdt_blob, dev->of_offset, + "clock-frequency", 0); + + return 0; +} + +static const struct udevice_id clk_fixed_rate_match[] = { + { + .compatible = "fixed-clock", + }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(clk_fixed_rate) = { + .name = "fixed_rate_clock", + .id = UCLASS_CLK, + .of_match = clk_fixed_rate_match, + .ofdata_to_platdata = clk_fixed_rate_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct clk_fixed_rate), + .ops = &clk_fixed_rate_ops, +}; diff --git a/drivers/clk/clk_rk3036.c b/drivers/clk/clk_rk3036.c index f650810..bd5f22a 100644 --- a/drivers/clk/clk_rk3036.c +++ b/drivers/clk/clk_rk3036.c @@ -13,8 +13,8 @@ #include <asm/arch/clock.h> #include <asm/arch/cru_rk3036.h> #include <asm/arch/hardware.h> -#include <asm/arch/periph.h> #include <dm/lists.h> +#include <dt-bindings/clock/rk3036-cru.h> DECLARE_GLOBAL_DATA_PTR; @@ -57,6 +57,23 @@ static inline unsigned int log2(unsigned int value) return fls(value) - 1; } +void *rockchip_get_cru(void) +{ + struct udevice *dev; + fdt_addr_t addr; + int ret; + + ret = uclass_get_device(UCLASS_CLK, 0, &dev); + if (ret) + return ERR_PTR(ret); + + addr = dev_get_addr(dev); + if (addr == FDT_ADDR_T_NONE) + return ERR_PTR(-EINVAL); + + return (void *)addr; +} + static int rkclk_set_pll(struct rk3036_cru *cru, enum rk_clk_id clk_id, const struct pll_div *div) { @@ -233,19 +250,19 @@ static uint32_t rkclk_pll_get_rate(struct rk3036_cru *cru, } static ulong rockchip_mmc_get_clk(struct rk3036_cru *cru, uint clk_general_rate, - enum periph_id periph) + int periph) { uint src_rate; uint div, mux; u32 con; switch (periph) { - case PERIPH_ID_EMMC: + case HCLK_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: + case HCLK_SDIO: con = readl(&cru->cru_clksel_con[12]); mux = (con >> MMC0_PLL_SHIFT) & MMC0_PLL_MASK; div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK; @@ -259,7 +276,7 @@ static ulong rockchip_mmc_get_clk(struct rk3036_cru *cru, uint clk_general_rate, } static ulong rockchip_mmc_set_clk(struct rk3036_cru *cru, uint clk_general_rate, - enum periph_id periph, uint freq) + int periph, uint freq) { int src_clk_div; int mux; @@ -277,14 +294,14 @@ static ulong rockchip_mmc_set_clk(struct rk3036_cru *cru, uint clk_general_rate, } switch (periph) { - case PERIPH_ID_EMMC: + case HCLK_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: + case HCLK_SDIO: rk_clrsetreg(&cru->cru_clksel_con[11], MMC0_PLL_MASK << MMC0_PLL_SHIFT | MMC0_DIV_MASK << MMC0_DIV_SHIFT, @@ -320,7 +337,7 @@ static ulong rk3036_set_periph_rate(struct udevice *dev, int periph, ulong rate) ulong new_rate; switch (periph) { - case PERIPH_ID_EMMC: + case HCLK_EMMC: new_rate = rockchip_mmc_set_clk(priv->cru, clk_get_rate(dev), periph, rate); break; diff --git a/drivers/clk/clk_rk3288.c b/drivers/clk/clk_rk3288.c index 0172ad1..2a85e93 100644 --- a/drivers/clk/clk_rk3288.c +++ b/drivers/clk/clk_rk3288.c @@ -14,8 +14,10 @@ #include <asm/arch/cru_rk3288.h> #include <asm/arch/grf_rk3288.h> #include <asm/arch/hardware.h> -#include <asm/arch/periph.h> +#include <dt-bindings/clock/rk3288-cru.h> +#include <dm/device-internal.h> #include <dm/lists.h> +#include <dm/uclass-internal.h> DECLARE_GLOBAL_DATA_PTR; @@ -57,6 +59,16 @@ enum { /* PLL CON3 */ PLL_RESET_SHIFT = 5, + /* CLKSEL0 */ + CORE_SEL_PLL_MASK = 1, + CORE_SEL_PLL_SHIFT = 15, + A17_DIV_MASK = 0x1f, + A17_DIV_SHIFT = 8, + MP_DIV_MASK = 0xf, + MP_DIV_SHIFT = 4, + M0_DIV_MASK = 0xf, + M0_DIV_SHIFT = 0, + /* CLKSEL1: pd bus clk pll sel: codec or general */ PD_BUS_SEL_PLL_MASK = 15, PD_BUS_SEL_CPLL = 0, @@ -81,8 +93,13 @@ enum { * peripheral bus pclk div: * aclk_bus: pclk_bus = 1:1 or 2:1 or 4:1 or 8:1 */ + PERI_SEL_PLL_MASK = 1, + PERI_SEL_PLL_SHIFT = 15, + PERI_SEL_CPLL = 0, + PERI_SEL_GPLL, + PERI_PCLK_DIV_SHIFT = 12, - PERI_PCLK_DIV_MASK = 7, + PERI_PCLK_DIV_MASK = 3, /* peripheral bus hclk div: aclk_bus: hclk_bus = 1:1 or 2:1 or 4:1 */ PERI_HCLK_DIV_SHIFT = 8, @@ -95,27 +112,6 @@ enum { 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, @@ -139,6 +135,37 @@ 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); +int rkclk_get_clk(enum rk_clk_id clk_id, struct udevice **devp) +{ + struct udevice *dev; + + for (uclass_find_first_device(UCLASS_CLK, &dev); + dev; + uclass_find_next_device(&dev)) { + struct rk3288_clk_plat *plat = dev_get_platdata(dev); + + if (plat->clk_id == clk_id) { + *devp = dev; + return device_probe(dev); + } + } + + return -ENODEV; +} + +void *rockchip_get_cru(void) +{ + struct rk3288_clk_priv *priv; + struct udevice *dev; + int ret; + + ret = rkclk_get_clk(CLK_GENERAL, &dev); + if (ret) + return ERR_PTR(ret); + priv = dev_get_priv(dev); + return priv->cru; +} + static int rkclk_set_pll(struct rk3288_cru *cru, enum rk_clk_id clk_id, const struct pll_div *div) { @@ -148,13 +175,13 @@ static int rkclk_set_pll(struct rk3288_cru *cru, enum rk_clk_id clk_id, 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); + 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 rest */ + /* enter reset */ rk_setreg(&pll->con3, 1 << PLL_RESET_SHIFT); rk_clrsetreg(&pll->con0, @@ -165,7 +192,7 @@ static int rkclk_set_pll(struct rk3288_cru *cru, enum rk_clk_id clk_id, udelay(10); - /* return form rest */ + /* return from reset */ rk_clrreg(&pll->con3, 1 << PLL_RESET_SHIFT); return 0; @@ -187,7 +214,6 @@ static int rkclk_configure_ddr(struct rk3288_cru *cru, struct rk3288_grf *grf, }; int cfg; - debug("%s: cru=%p, grf=%p, hz=%u\n", __func__, cru, grf, hz); switch (hz) { case 300000000: cfg = 0; @@ -202,7 +228,7 @@ static int rkclk_configure_ddr(struct rk3288_cru *cru, struct rk3288_grf *grf, cfg = 3; break; default: - debug("Unsupported SDRAM frequency, add to clock.c!"); + debug("Unsupported SDRAM frequency"); return -EINVAL; } @@ -218,10 +244,128 @@ static int rkclk_configure_ddr(struct rk3288_cru *cru, struct rk3288_grf *grf, /* PLL enter normal-mode */ rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK << DPLL_MODE_SHIFT, - DPLL_MODE_NORM << DPLL_MODE_SHIFT); + DPLL_MODE_NORMAL << DPLL_MODE_SHIFT); + + return 0; +} + +#ifndef CONFIG_SPL_BUILD +#define VCO_MAX_KHZ 2200000 +#define VCO_MIN_KHZ 440000 +#define FREF_MAX_KHZ 2200000 +#define FREF_MIN_KHZ 269 + +static int pll_para_config(ulong freq_hz, struct pll_div *div, uint *ext_div) +{ + uint ref_khz = OSC_HZ / 1000, nr, nf = 0; + uint fref_khz; + uint diff_khz, best_diff_khz; + const uint max_nr = 1 << 6, max_nf = 1 << 12, max_no = 1 << 4; + uint vco_khz; + uint no = 1; + uint freq_khz = freq_hz / 1000; + + if (!freq_hz) { + printf("%s: the frequency can not be 0 Hz\n", __func__); + return -EINVAL; + } + + no = DIV_ROUND_UP(VCO_MIN_KHZ, freq_khz); + if (ext_div) { + *ext_div = DIV_ROUND_UP(no, max_no); + no = DIV_ROUND_UP(no, *ext_div); + } + + /* only even divisors (and 1) are supported */ + if (no > 1) + no = DIV_ROUND_UP(no, 2) * 2; + + vco_khz = freq_khz * no; + if (ext_div) + vco_khz *= *ext_div; + + if (vco_khz < VCO_MIN_KHZ || vco_khz > VCO_MAX_KHZ || no > max_no) { + printf("%s: Cannot find out a supported VCO for Frequency (%luHz).\n", + __func__, freq_hz); + return -1; + } + + div->no = no; + + best_diff_khz = vco_khz; + for (nr = 1; nr < max_nr && best_diff_khz; nr++) { + fref_khz = ref_khz / nr; + if (fref_khz < FREF_MIN_KHZ) + break; + if (fref_khz > FREF_MAX_KHZ) + continue; + + nf = vco_khz / fref_khz; + if (nf >= max_nf) + continue; + diff_khz = vco_khz - nf * fref_khz; + if (nf + 1 < max_nf && diff_khz > fref_khz / 2) { + nf++; + diff_khz = fref_khz - diff_khz; + } + + if (diff_khz >= best_diff_khz) + continue; + + best_diff_khz = diff_khz; + div->nr = nr; + div->nf = nf; + } + + if (best_diff_khz > 4 * 1000) { + printf("%s: Failed to match output frequency %lu, difference is %u Hz, exceed 4MHZ\n", + __func__, freq_hz, best_diff_khz * 1000); + return -EINVAL; + } + + return 0; +} + +static int rockchip_vop_set_clk(struct rk3288_cru *cru, struct rk3288_grf *grf, + int periph, unsigned int rate_hz) +{ + struct pll_div npll_config = {0}; + u32 lcdc_div; + int ret; + + ret = pll_para_config(rate_hz, &npll_config, &lcdc_div); + if (ret) + return ret; + + rk_clrsetreg(&cru->cru_mode_con, NPLL_MODE_MASK << NPLL_MODE_SHIFT, + NPLL_MODE_SLOW << NPLL_MODE_SHIFT); + rkclk_set_pll(cru, CLK_NEW, &npll_config); + + /* waiting for pll lock */ + while (1) { + if (readl(&grf->soc_status[1]) & SOCSTS_NPLL_LOCK) + break; + udelay(1); + } + + rk_clrsetreg(&cru->cru_mode_con, NPLL_MODE_MASK << NPLL_MODE_SHIFT, + NPLL_MODE_NORMAL << NPLL_MODE_SHIFT); + + /* vop dclk source clk: npll,dclk_div: 1 */ + switch (periph) { + case DCLK_VOP0: + rk_clrsetreg(&cru->cru_clksel_con[27], 0xff << 8 | 3 << 0, + (lcdc_div - 1) << 8 | 2 << 0); + break; + case DCLK_VOP1: + rk_clrsetreg(&cru->cru_clksel_con[29], 0xff << 8 | 3 << 6, + (lcdc_div - 1) << 8 | 2 << 6); + break; + } return 0; } +#endif #ifdef CONFIG_SPL_BUILD static void rkclk_init(struct rk3288_cru *cru, struct rk3288_grf *grf) @@ -290,6 +434,7 @@ static void rkclk_init(struct rk3288_cru *cru, struct rk3288_grf *grf) 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); @@ -298,11 +443,57 @@ static void rkclk_init(struct rk3288_cru *cru, struct rk3288_grf *grf) 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); + GPLL_MODE_NORMAL << GPLL_MODE_SHIFT | + CPLL_MODE_NORMAL << CPLL_MODE_SHIFT); } #endif +void rkclk_configure_cpu(struct rk3288_cru *cru, struct rk3288_grf *grf) +{ + /* pll enter slow-mode */ + rk_clrsetreg(&cru->cru_mode_con, + APLL_MODE_MASK << APLL_MODE_SHIFT, + APLL_MODE_SLOW << APLL_MODE_SHIFT); + + rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg); + + /* waiting for pll lock */ + while (!(readl(&grf->soc_status[1]) & SOCSTS_APLL_LOCK)) + udelay(1); + + /* + * core clock pll source selection and + * set up dependent divisors for MPAXI/M0AXI and ARM clocks. + * core clock select apll, apll clk = 1800MHz + * arm clk = 1800MHz, mpclk = 450MHz, m0clk = 900MHz + */ + rk_clrsetreg(&cru->cru_clksel_con[0], + CORE_SEL_PLL_MASK << CORE_SEL_PLL_SHIFT | + A17_DIV_MASK << A17_DIV_SHIFT | + MP_DIV_MASK << MP_DIV_SHIFT | + M0_DIV_MASK << M0_DIV_SHIFT, + 0 << A17_DIV_SHIFT | + 3 << MP_DIV_SHIFT | + 1 << M0_DIV_SHIFT); + + /* + * set up dependent divisors for L2RAM/ATCLK and PCLK clocks. + * l2ramclk = 900MHz, atclk = 450MHz, pclk_dbg = 450MHz + */ + rk_clrsetreg(&cru->cru_clksel_con[37], + CLK_L2RAM_DIV_MASK << CLK_L2RAM_DIV_SHIFT | + ATCLK_CORE_DIV_CON_MASK << ATCLK_CORE_DIV_CON_SHIFT | + PCLK_CORE_DBG_DIV_MASK >> PCLK_CORE_DBG_DIV_SHIFT, + 1 << CLK_L2RAM_DIV_SHIFT | + 3 << ATCLK_CORE_DIV_CON_SHIFT | + 3 << PCLK_CORE_DBG_DIV_SHIFT); + + /* PLL enter normal-mode */ + rk_clrsetreg(&cru->cru_mode_con, + APLL_MODE_MASK << APLL_MODE_SHIFT, + APLL_MODE_NORMAL << APLL_MODE_SHIFT); +} + /* Get pll rate by id */ static uint32_t rkclk_pll_get_rate(struct rk3288_cru *cru, enum rk_clk_id clk_id) @@ -312,17 +503,17 @@ static uint32_t rkclk_pll_get_rate(struct rk3288_cru *cru, 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 + 0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, CPLL_MODE_SHIFT, + GPLL_MODE_SHIFT, NPLL_MODE_SHIFT }; uint shift; con = readl(&cru->cru_mode_con); shift = clk_shift[clk_id]; - switch ((con >> shift) & APLL_WORK_MASK) { - case APLL_WORK_SLOW: + switch ((con >> shift) & APLL_MODE_MASK) { + case APLL_MODE_SLOW: return OSC_HZ; - case APLL_WORK_NORMAL: + case APLL_MODE_NORMAL: /* normal mode */ con = readl(&pll->con0); no = ((con >> CLKOD_SHIFT) & CLKOD_MASK) + 1; @@ -331,7 +522,7 @@ static uint32_t rkclk_pll_get_rate(struct rk3288_cru *cru, nf = ((con >> CLKF_SHIFT) & CLKF_MASK) + 1; return (24 * nf / (nr * no)) * 1000000; - case APLL_WORK_DEEP: + case APLL_MODE_DEEP: default: return 32768; } @@ -363,25 +554,25 @@ static ulong rk3288_clk_set_rate(struct udevice *dev, ulong rate) return 0; } -static ulong rockchip_mmc_get_clk(struct rk3288_cru *cru, uint clk_general_rate, - enum periph_id periph) +static ulong rockchip_mmc_get_clk(struct rk3288_cru *cru, uint gclk_rate, + int periph) { uint src_rate; uint div, mux; u32 con; switch (periph) { - case PERIPH_ID_EMMC: + case HCLK_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]); + case HCLK_SDMMC: + con = readl(&cru->cru_clksel_con[11]); mux = (con >> MMC0_PLL_SHIFT) & MMC0_PLL_MASK; div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK; break; - case PERIPH_ID_SDMMC2: + case HCLK_SDIO0: con = readl(&cru->cru_clksel_con[12]); mux = (con >> SDIO0_PLL_SHIFT) & SDIO0_PLL_MASK; div = (con >> SDIO0_DIV_SHIFT) & SDIO0_DIV_MASK; @@ -390,18 +581,18 @@ static ulong rockchip_mmc_get_clk(struct rk3288_cru *cru, uint clk_general_rate, return -EINVAL; } - src_rate = mux == EMMC_PLL_SELECT_24MHZ ? OSC_HZ : clk_general_rate; + src_rate = mux == EMMC_PLL_SELECT_24MHZ ? OSC_HZ : gclk_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) +static ulong rockchip_mmc_set_clk(struct rk3288_cru *cru, uint gclk_rate, + int 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); + debug("%s: gclk_rate=%u\n", __func__, gclk_rate); + src_clk_div = RATE_TO_DIV(gclk_rate, freq); if (src_clk_div > 0x3f) { src_clk_div = RATE_TO_DIV(OSC_HZ, freq); @@ -414,21 +605,21 @@ static ulong rockchip_mmc_set_clk(struct rk3288_cru *cru, uint clk_general_rate, (int)MMC0_PLL_SELECT_GENERAL); } switch (periph) { - case PERIPH_ID_EMMC: + case HCLK_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: + case HCLK_SDMMC: 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: + case HCLK_SDIO0: rk_clrsetreg(&cru->cru_clksel_con[12], SDIO0_PLL_MASK << SDIO0_PLL_SHIFT | SDIO0_DIV_MASK << SDIO0_DIV_SHIFT, @@ -439,27 +630,27 @@ static ulong rockchip_mmc_set_clk(struct rk3288_cru *cru, uint clk_general_rate, return -EINVAL; } - return rockchip_mmc_get_clk(cru, clk_general_rate, periph); + return rockchip_mmc_get_clk(cru, gclk_rate, periph); } -static ulong rockchip_spi_get_clk(struct rk3288_cru *cru, uint clk_general_rate, - enum periph_id periph) +static ulong rockchip_spi_get_clk(struct rk3288_cru *cru, uint gclk_rate, + int periph) { uint div, mux; u32 con; switch (periph) { - case PERIPH_ID_SPI0: + case SCLK_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: + case SCLK_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: + case SCLK_SPI2: con = readl(&cru->cru_clksel_con[39]); mux = (con >> SPI2_PLL_SHIFT) & SPI2_PLL_MASK; div = (con >> SPI2_DIV_SHIFT) & SPI2_DIV_MASK; @@ -469,32 +660,32 @@ static ulong rockchip_spi_get_clk(struct rk3288_cru *cru, uint clk_general_rate, } assert(mux == SPI0_PLL_SELECT_GENERAL); - return DIV_TO_RATE(clk_general_rate, div); + return DIV_TO_RATE(gclk_rate, div); } -static ulong rockchip_spi_set_clk(struct rk3288_cru *cru, uint clk_general_rate, - enum periph_id periph, uint freq) +static ulong rockchip_spi_set_clk(struct rk3288_cru *cru, uint gclk_rate, + int 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); + debug("%s: clk_general_rate=%u\n", __func__, gclk_rate); + src_clk_div = RATE_TO_DIV(gclk_rate, freq); switch (periph) { - case PERIPH_ID_SPI0: + case SCLK_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: + case SCLK_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: + case SCLK_SPI2: rk_clrsetreg(&cru->cru_clksel_con[39], SPI2_PLL_MASK << SPI2_PLL_SHIFT | SPI2_DIV_MASK << SPI2_DIV_SHIFT, @@ -505,26 +696,117 @@ static ulong rockchip_spi_set_clk(struct rk3288_cru *cru, uint clk_general_rate, return -EINVAL; } - return rockchip_spi_get_clk(cru, clk_general_rate, periph); + return rockchip_spi_get_clk(cru, gclk_rate, periph); +} + +static ulong rk3288_get_periph_rate(struct udevice *dev, int periph) +{ + struct rk3288_clk_priv *priv = dev_get_priv(dev); + struct udevice *gclk; + ulong new_rate, gclk_rate; + int ret; + + ret = rkclk_get_clk(CLK_GENERAL, &gclk); + if (ret) + return ret; + gclk_rate = clk_get_rate(gclk); + switch (periph) { + case HCLK_EMMC: + case HCLK_SDMMC: + case HCLK_SDIO0: + new_rate = rockchip_mmc_get_clk(priv->cru, gclk_rate, periph); + break; + case SCLK_SPI0: + case SCLK_SPI1: + case SCLK_SPI2: + new_rate = rockchip_spi_get_clk(priv->cru, gclk_rate, periph); + break; + case PCLK_I2C0: + case PCLK_I2C1: + case PCLK_I2C2: + case PCLK_I2C3: + case PCLK_I2C4: + case PCLK_I2C5: + return gclk_rate; + default: + return -ENOENT; + } + + return new_rate; } static ulong rk3288_set_periph_rate(struct udevice *dev, int periph, ulong rate) { struct rk3288_clk_priv *priv = dev_get_priv(dev); - ulong new_rate; + struct rk3288_cru *cru = priv->cru; + struct udevice *gclk; + ulong new_rate, gclk_rate; + int ret; + ret = rkclk_get_clk(CLK_GENERAL, &gclk); + if (ret) + return ret; + gclk_rate = clk_get_rate(gclk); switch (periph) { - case PERIPH_ID_EMMC: - case PERIPH_ID_SDCARD: - new_rate = rockchip_mmc_set_clk(priv->cru, clk_get_rate(dev), - periph, rate); + case HCLK_EMMC: + case HCLK_SDMMC: + case HCLK_SDIO0: + new_rate = rockchip_mmc_set_clk(cru, gclk_rate, periph, rate); + break; + case SCLK_SPI0: + case SCLK_SPI1: + case SCLK_SPI2: + new_rate = rockchip_spi_set_clk(cru, gclk_rate, periph, rate); + break; +#ifndef CONFIG_SPL_BUILD + case DCLK_VOP0: + case DCLK_VOP1: + new_rate = rockchip_vop_set_clk(cru, priv->grf, periph, rate); + break; + case SCLK_EDP_24M: + /* clk_edp_24M source: 24M */ + rk_setreg(&cru->cru_clksel_con[28], 1 << 15); + + /* rst edp */ + rk_setreg(&cru->cru_clksel_con[6], 1 << 15); + udelay(1); + rk_clrreg(&cru->cru_clksel_con[6], 1 << 15); + new_rate = rate; + break; + case ACLK_VOP0: + case ACLK_VOP1: { + u32 div; + + /* vop aclk source clk: cpll */ + div = CPLL_HZ / rate; + assert((div - 1 < 64) && (div * rate == CPLL_HZ)); + + switch (periph) { + case ACLK_VOP0: + rk_clrsetreg(&cru->cru_clksel_con[31], + 3 << 6 | 0x1f << 0, + 0 << 6 | (div - 1) << 0); + break; + case ACLK_VOP1: + rk_clrsetreg(&cru->cru_clksel_con[31], + 3 << 14 | 0x1f << 8, + 0 << 14 | (div - 1) << 8); + break; + } + new_rate = 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); + } + case PCLK_HDMI_CTRL: + /* enable pclk hdmi ctrl */ + rk_clrreg(&cru->cru_clkgate_con[16], 1 << 9); + + /* software reset hdmi */ + rk_setreg(&cru->cru_clkgate_con[7], 1 << 9); + udelay(1); + rk_clrreg(&cru->cru_clkgate_con[7], 1 << 9); + new_rate = rate; break; +#endif default: return -ENOENT; } @@ -536,6 +818,7 @@ 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, + .get_periph_rate = rk3288_get_periph_rate, }; static int rk3288_clk_probe(struct udevice *dev) @@ -565,7 +848,7 @@ static const char *const clk_name[CLK_COUNT] = { "dpll", "cpll", "gpll", - "mpll", + "npll", }; static int rk3288_clk_bind(struct udevice *dev) diff --git a/drivers/core/device.c b/drivers/core/device.c index 1e5584a..f5def35 100644 --- a/drivers/core/device.c +++ b/drivers/core/device.c @@ -299,9 +299,11 @@ int device_probe_child(struct udevice *dev, void *parent_priv) /* * Process pinctrl for everything except the root device, and - * continue regardless of the result of pinctrl. + * continue regardless of the result of pinctrl. Don't process pinctrl + * settings for pinctrl devices since the device may not yet be + * probed. */ - if (dev->parent) + if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL) pinctrl_select_state(dev, "default"); ret = uclass_pre_probe_device(dev); diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index e1acefe..12095e7 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -254,8 +254,8 @@ int uclass_find_device_by_seq(enum uclass_id id, int seq_or_req_seq, return -ENODEV; } -static int uclass_find_device_by_of_offset(enum uclass_id id, int node, - struct udevice **devp) +int uclass_find_device_by_of_offset(enum uclass_id id, int node, + struct udevice **devp) { struct uclass *uc; struct udevice *dev; diff --git a/drivers/gpio/rk_gpio.c b/drivers/gpio/rk_gpio.c index fbdf9f3..c62f025 100644 --- a/drivers/gpio/rk_gpio.c +++ b/drivers/gpio/rk_gpio.c @@ -8,11 +8,16 @@ */ #include <common.h> +#include <clk.h> #include <dm.h> +#include <syscon.h> #include <asm/errno.h> #include <asm/gpio.h> #include <asm/io.h> +#include <asm/arch/clock.h> +#include <dm/pinctrl.h> #include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/clock/rk3288-cru.h> enum { ROCKCHIP_GPIOS_PER_BANK = 32, @@ -22,6 +27,8 @@ enum { struct rockchip_gpio_priv { struct rockchip_gpio_regs *regs; + struct udevice *pinctrl; + int bank; char name[2]; }; @@ -53,7 +60,7 @@ 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); + return readl(®s->ext_port) & OFFSET_TO_BIT(offset) ? 1 : 0; } static int rockchip_gpio_set_value(struct udevice *dev, unsigned offset, @@ -70,7 +77,25 @@ static int rockchip_gpio_set_value(struct udevice *dev, unsigned offset, static int rockchip_gpio_get_function(struct udevice *dev, unsigned offset) { - return -ENOSYS; +#ifdef CONFIG_SPL_BUILD + return -ENODATA; +#else + struct rockchip_gpio_priv *priv = dev_get_priv(dev); + struct rockchip_gpio_regs *regs = priv->regs; + bool is_output; + int ret; + + ret = pinctrl_get_gpio_mux(priv->pinctrl, priv->bank, offset); + if (ret) + return ret; + + /* If it's not 0, then it is not a GPIO */ + if (ret) + return GPIOF_FUNC; + is_output = readl(®s->swport_ddr) & OFFSET_TO_BIT(offset); + + return is_output ? GPIOF_OUTPUT : GPIOF_INPUT; +#endif } static int rockchip_gpio_xlate(struct udevice *dev, struct gpio_desc *desc, @@ -87,13 +112,20 @@ 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; + int ret; + /* This only supports RK3288 at present */ priv->regs = (struct rockchip_gpio_regs *)dev_get_addr(dev); + ret = uclass_first_device(UCLASS_PINCTRL, &priv->pinctrl); + if (ret) + return ret; + if (!priv->pinctrl) + return -ENODEV; + uc_priv->gpio_count = ROCKCHIP_GPIOS_PER_BANK; end = strrchr(dev->name, '@'); - bank = trailing_strtoln(dev->name, end); - priv->name[0] = 'A' + bank; + priv->bank = trailing_strtoln(dev->name, end); + priv->name[0] = 'A' + priv->bank; uc_priv->bank_name = priv->name; return 0; diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 811ad9b..bbbc0dc 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -7,8 +7,8 @@ obj-$(CONFIG_DM_I2C) += i2c-uclass.o obj-$(CONFIG_DM_I2C_COMPAT) += i2c-uclass-compat.o obj-$(CONFIG_DM_I2C_GPIO) += i2c-gpio.o -obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += cros_ec_tunnel.o -obj-$(CONFIG_I2C_CROS_EC_LDO) += cros_ec_ldo.o +obj-$(CONFIG_$(SPL_)I2C_CROS_EC_TUNNEL) += cros_ec_tunnel.o +obj-$(CONFIG_$(SPL_)I2C_CROS_EC_LDO) += cros_ec_ldo.o obj-$(CONFIG_SYS_I2C_ADI) += adi_i2c.o obj-$(CONFIG_I2C_MV) += mv_i2c.o diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig index 913093d..f959d9d 100644 --- a/drivers/i2c/muxes/Kconfig +++ b/drivers/i2c/muxes/Kconfig @@ -7,6 +7,15 @@ config I2C_MUX bus select is handled automatically when that bus is accessed, using a suitable I2C MUX driver. +config SPL_I2C_MUX + bool "Support I2C multiplexers on SPL" + depends on I2C_MUX + help + This enables I2C buses to be multiplexed, so that you can select + one of several buses using some sort of control mechanism. The + bus select is handled automatically when that bus is accessed, + using a suitable I2C MUX driver. + config I2C_ARB_GPIO_CHALLENGE bool "GPIO-based I2C arbitration" depends on I2C_MUX diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile index 612cc27..47c1240 100644 --- a/drivers/i2c/muxes/Makefile +++ b/drivers/i2c/muxes/Makefile @@ -4,4 +4,4 @@ # SPDX-License-Identifier: GPL-2.0+ # obj-$(CONFIG_I2C_ARB_GPIO_CHALLENGE) += i2c-arb-gpio-challenge.o -obj-$(CONFIG_I2C_MUX) += i2c-mux-uclass.o +obj-$(CONFIG_$(SPL_)I2C_MUX) += i2c-mux-uclass.o diff --git a/drivers/i2c/rk_i2c.c b/drivers/i2c/rk_i2c.c index ebdba35..3fceade 100644 --- a/drivers/i2c/rk_i2c.c +++ b/drivers/i2c/rk_i2c.c @@ -30,10 +30,9 @@ DECLARE_GLOBAL_DATA_PTR; struct rk_i2c { struct udevice *clk; - struct udevice *pinctrl; struct i2c_regs *regs; unsigned int speed; - enum periph_id id; + int clk_id; }; static inline void rk_i2c_get_div(int div, int *divh, int *divl) @@ -56,7 +55,7 @@ static void rk_i2c_set_clk(struct rk_i2c *i2c, uint32_t scl_rate) int div, divl, divh; /* First get i2c rate from pclk */ - i2c_rate = clk_get_periph_rate(i2c->clk, i2c->id); + i2c_rate = clk_get_periph_rate(i2c->clk, i2c->clk_id); div = DIV_ROUND_UP(i2c_rate, scl_rate * 8) - 2; divh = 0; @@ -352,23 +351,29 @@ int rockchip_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) return 0; } -static int rockchip_i2c_probe(struct udevice *bus) +static int rockchip_i2c_ofdata_to_platdata(struct udevice *bus) { - struct rk_i2c *i2c = dev_get_priv(bus); + struct rk_i2c *priv = 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) + ret = clk_get_by_index(bus, 0, &priv->clk); + if (ret < 0) { + debug("%s: Could not get clock for %s: %d\n", __func__, + bus->name, ret); return ret; - i2c->id = ret; - i2c->regs = (void *)dev_get_addr(bus); - return pinctrl_request(i2c->pinctrl, i2c->id, 0); + } + priv->clk_id = ret; + + return 0; +} + +static int rockchip_i2c_probe(struct udevice *bus) +{ + struct rk_i2c *priv = dev_get_priv(bus); + + priv->regs = (void *)dev_get_addr(bus); + + return 0; } static const struct dm_i2c_ops rockchip_i2c_ops = { @@ -385,6 +390,7 @@ U_BOOT_DRIVER(i2c_rockchip) = { .name = "i2c_rockchip", .id = UCLASS_I2C, .of_match = rockchip_i2c_ids, + .ofdata_to_platdata = rockchip_i2c_ofdata_to_platdata, .probe = rockchip_i2c_probe, .priv_auto_alloc_size = sizeof(struct rk_i2c), .ops = &rockchip_i2c_ops, diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b92da4e..cba2363 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -90,6 +90,24 @@ config MXC_OCOTP Programmable memory pages that are stored on the some Freescale i.MX processors. +config PWRSEQ + bool "Enable power-sequencing drivers" + depends on DM + help + Power-sequencing drivers provide support for controlling power for + devices. They are typically referenced by a phandle from another + device. When the device is started up, its power sequence can be + initiated. + +config SPL_PWRSEQ + bool "Enable power-sequencing drivers for SPL" + depends on PWRSEQ + help + Power-sequencing drivers provide support for controlling power for + devices. They are typically referenced by a phandle from another + device. When the device is started up, its power sequence can be + initiated. + config PCA9551_LED bool "Enable PCA9551 LED driver" help diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index aa137f5..cd4846b 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -10,11 +10,13 @@ obj-$(CONFIG_ALI152X) += ali512x.o obj-$(CONFIG_ALTERA_SYSID) += altera_sysid.o obj-$(CONFIG_DS4510) += ds4510.o obj-$(CONFIG_CBMEM_CONSOLE) += cbmem_console.o +ifndef CONFIG_SPL_BUILD obj-$(CONFIG_CROS_EC) += cros_ec.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o obj-$(CONFIG_CROS_EC_SANDBOX) += cros_ec_sandbox.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o +endif obj-$(CONFIG_FSL_DEBUG_SERVER) += fsl_debug_server.o obj-$(CONFIG_FSL_IIM) += fsl_iim.o obj-$(CONFIG_GPIO_LED) += gpio_led.o @@ -24,6 +26,7 @@ obj-$(CONFIG_MXC_OCOTP) += mxc_ocotp.o obj-$(CONFIG_MXS_OCOTP) += mxs_ocotp.o obj-$(CONFIG_NS87308) += ns87308.o obj-$(CONFIG_PDSP188x) += pdsp188x.o +obj-$(CONFIG_$(SPL_)PWRSEQ) += pwrseq-uclass.o obj-$(CONFIG_SANDBOX) += reset_sandbox.o ifdef CONFIG_DM_I2C obj-$(CONFIG_SANDBOX) += i2c_eeprom_emul.o diff --git a/drivers/misc/pwrseq-uclass.c b/drivers/misc/pwrseq-uclass.c new file mode 100644 index 0000000..8ed2ad4 --- /dev/null +++ b/drivers/misc/pwrseq-uclass.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 Google, Inc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <pwrseq.h> + +int pwrseq_set_power(struct udevice *dev, bool enable) +{ + struct pwrseq_ops *ops = pwrseq_get_ops(dev); + + if (!ops->set_power) + return -ENOSYS; + + return ops->set_power(dev, enable); +} + +UCLASS_DRIVER(pwrseq) = { + .id = UCLASS_PWRSEQ, + .name = "pwrseq", +}; diff --git a/drivers/mmc/rockchip_dw_mmc.c b/drivers/mmc/rockchip_dw_mmc.c index 9b4268e..cb9e104 100644 --- a/drivers/mmc/rockchip_dw_mmc.c +++ b/drivers/mmc/rockchip_dw_mmc.c @@ -9,7 +9,9 @@ #include <dm.h> #include <dwmmc.h> #include <errno.h> +#include <pwrseq.h> #include <syscon.h> +#include <asm/gpio.h> #include <asm/arch/clock.h> #include <asm/arch/periph.h> #include <linux/err.h> @@ -18,7 +20,7 @@ DECLARE_GLOBAL_DATA_PTR; struct rockchip_dwmmc_priv { struct udevice *clk; - struct rk3288_grf *grf; + int periph; struct dwmci_host host; }; @@ -28,8 +30,7 @@ static uint rockchip_dwmmc_get_mmc_clk(struct dwmci_host *host, uint freq) 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); + ret = clk_set_periph_rate(priv->clk, priv->periph, freq); if (ret < 0) { debug("%s: err=%d\n", __func__, ret); return ret; @@ -64,16 +65,15 @@ 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; + struct udevice *pwr_dev __maybe_unused; u32 minmax[2]; int ret; int fifo_depth; - 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) + ret = clk_get_by_index(dev, 0, &priv->clk); + if (ret < 0) return ret; + priv->periph = ret; if (fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock-freq-min-max", minmax, 2)) @@ -90,6 +90,16 @@ static int rockchip_dwmmc_probe(struct udevice *dev) if (fdtdec_get_bool(gd->fdt_blob, dev->of_offset, "fifo-mode")) host->fifo_mode = true; +#ifdef CONFIG_PWRSEQ + /* Enable power if needed */ + ret = uclass_get_device_by_phandle(UCLASS_PWRSEQ, dev, "mmc-pwrseq", + &pwr_dev); + if (!ret) { + ret = pwrseq_set_power(pwr_dev, true); + if (ret) + return ret; + } +#endif ret = add_dwmci(host, minmax[1], minmax[0]); if (ret) return ret; @@ -112,3 +122,37 @@ U_BOOT_DRIVER(rockchip_dwmmc_drv) = { .probe = rockchip_dwmmc_probe, .priv_auto_alloc_size = sizeof(struct rockchip_dwmmc_priv), }; + +#ifdef CONFIG_PWRSEQ +static int rockchip_dwmmc_pwrseq_set_power(struct udevice *dev, bool enable) +{ + struct gpio_desc reset; + int ret; + + ret = gpio_request_by_name(dev, "reset-gpios", 0, &reset, GPIOD_IS_OUT); + if (ret) + return ret; + dm_gpio_set_value(&reset, 1); + udelay(1); + dm_gpio_set_value(&reset, 0); + udelay(200); + + return 0; +} + +static const struct pwrseq_ops rockchip_dwmmc_pwrseq_ops = { + .set_power = rockchip_dwmmc_pwrseq_set_power, +}; + +static const struct udevice_id rockchip_dwmmc_pwrseq_ids[] = { + { .compatible = "mmc-pwrseq-emmc" }, + { } +}; + +U_BOOT_DRIVER(rockchip_dwmmc_pwrseq_drv) = { + .name = "mmc_pwrseq_emmc", + .id = UCLASS_PWRSEQ, + .of_match = rockchip_dwmmc_pwrseq_ids, + .ops = &rockchip_dwmmc_pwrseq_ops, +}; +#endif diff --git a/drivers/mtd/spi/spi_flash.c b/drivers/mtd/spi/spi_flash.c index a567414..8a60c72 100644 --- a/drivers/mtd/spi/spi_flash.c +++ b/drivers/mtd/spi/spi_flash.c @@ -939,14 +939,10 @@ static int set_quad_mode(struct spi_flash *flash, u8 idcode0) #if CONFIG_IS_ENABLED(OF_CONTROL) int spi_flash_decode_fdt(const void *blob, struct spi_flash *flash) { +#ifdef CONFIG_DM_SPI_FLASH fdt_addr_t addr; fdt_size_t size; - int node; - - /* If there is no node, do nothing */ - node = fdtdec_next_compatible(blob, 0, COMPAT_GENERIC_SPI_FLASH); - if (node < 0) - return 0; + int node = flash->dev->of_offset; addr = fdtdec_get_addr_size(blob, node, "memory-map", &size); if (addr == FDT_ADDR_T_NONE) { @@ -959,6 +955,7 @@ int spi_flash_decode_fdt(const void *blob, struct spi_flash *flash) return -1; } flash->memory_map = map_sysmem(addr, size); +#endif return 0; } diff --git a/drivers/pinctrl/pinctrl-uclass.c b/drivers/pinctrl/pinctrl-uclass.c index c42b312..ccc5d30 100644 --- a/drivers/pinctrl/pinctrl-uclass.c +++ b/drivers/pinctrl/pinctrl-uclass.c @@ -15,6 +15,18 @@ DECLARE_GLOBAL_DATA_PTR; +int pinctrl_decode_pin_config(const void *blob, int node) +{ + int flags = 0; + + if (fdtdec_get_bool(blob, node, "bias-pull-up")) + flags |= 1 << PIN_CONFIG_BIAS_PULL_UP; + else if (fdtdec_get_bool(blob, node, "bias-pull-down")) + flags |= 1 << PIN_CONFIG_BIAS_PULL_DOWN; + + return flags; +} + #if CONFIG_IS_ENABLED(PINCTRL_FULL) /** * pinctrl_config_one() - apply pinctrl settings for a single node @@ -234,6 +246,16 @@ int pinctrl_get_periph_id(struct udevice *dev, struct udevice *periph) return ops->get_periph_id(dev, periph); } +int pinctrl_get_gpio_mux(struct udevice *dev, int banknum, int index) +{ + struct pinctrl_ops *ops = pinctrl_get_ops(dev); + + if (!ops->get_gpio_mux) + return -ENOSYS; + + return ops->get_gpio_mux(dev, banknum, index); +} + /** * pinconfig_post-bind() - post binding for PINCTRL uclass * Recursively bind child nodes as pinconfig devices in case of full pinctrl. diff --git a/drivers/pinctrl/rockchip/pinctrl_rk3036.c b/drivers/pinctrl/rockchip/pinctrl_rk3036.c index 581b096..1f78bf8 100644 --- a/drivers/pinctrl/rockchip/pinctrl_rk3036.c +++ b/drivers/pinctrl/rockchip/pinctrl_rk3036.c @@ -15,6 +15,7 @@ #include <asm/arch/hardware.h> #include <asm/arch/periph.h> #include <dm/pinctrl.h> +#include <dm/root.h> DECLARE_GLOBAL_DATA_PTR; @@ -252,6 +253,12 @@ static struct pinctrl_ops rk3036_pinctrl_ops = { .get_periph_id = rk3036_pinctrl_get_periph_id, }; +static int rk3036_pinctrl_bind(struct udevice *dev) +{ + /* scan child GPIO banks */ + return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false); +} + static int rk3036_pinctrl_probe(struct udevice *dev) { struct rk3036_pinctrl_priv *priv = dev_get_priv(dev); @@ -272,5 +279,6 @@ U_BOOT_DRIVER(pinctrl_rk3036) = { .of_match = rk3036_pinctrl_ids, .priv_auto_alloc_size = sizeof(struct rk3036_pinctrl_priv), .ops = &rk3036_pinctrl_ops, + .bind = rk3036_pinctrl_bind, .probe = rk3036_pinctrl_probe, }; diff --git a/drivers/pinctrl/rockchip/pinctrl_rk3288.c b/drivers/pinctrl/rockchip/pinctrl_rk3288.c index c432a00..7c769bd 100644 --- a/drivers/pinctrl/rockchip/pinctrl_rk3288.c +++ b/drivers/pinctrl/rockchip/pinctrl_rk3288.c @@ -24,8 +24,103 @@ DECLARE_GLOBAL_DATA_PTR; struct rk3288_pinctrl_priv { struct rk3288_grf *grf; struct rk3288_pmu *pmu; + int num_banks; }; +/** + * Encode variants of iomux registers into a type variable + */ +#define IOMUX_GPIO_ONLY BIT(0) +#define IOMUX_WIDTH_4BIT BIT(1) +#define IOMUX_SOURCE_PMU BIT(2) +#define IOMUX_UNROUTED BIT(3) + +/** + * @type: iomux variant using IOMUX_* constants + * @offset: if initialized to -1 it will be autocalculated, by specifying + * an initial offset value the relevant source offset can be reset + * to a new value for autocalculating the following iomux registers. + */ +struct rockchip_iomux { + u8 type; + s16 offset; +}; + +/** + * @reg: register offset of the gpio bank + * @nr_pins: number of pins in this bank + * @bank_num: number of the bank, to account for holes + * @name: name of the bank + * @iomux: array describing the 4 iomux sources of the bank + */ +struct rockchip_pin_bank { + u16 reg; + u8 nr_pins; + u8 bank_num; + char *name; + struct rockchip_iomux iomux[4]; +}; + +#define PIN_BANK(id, pins, label) \ + { \ + .bank_num = id, \ + .nr_pins = pins, \ + .name = label, \ + .iomux = { \ + { .offset = -1 }, \ + { .offset = -1 }, \ + { .offset = -1 }, \ + { .offset = -1 }, \ + }, \ + } + +#define PIN_BANK_IOMUX_FLAGS(id, pins, label, iom0, iom1, iom2, iom3) \ + { \ + .bank_num = id, \ + .nr_pins = pins, \ + .name = label, \ + .iomux = { \ + { .type = iom0, .offset = -1 }, \ + { .type = iom1, .offset = -1 }, \ + { .type = iom2, .offset = -1 }, \ + { .type = iom3, .offset = -1 }, \ + }, \ + } + +#ifndef CONFIG_SPL_BUILD +static struct rockchip_pin_bank rk3288_pin_banks[] = { + PIN_BANK_IOMUX_FLAGS(0, 24, "gpio0", IOMUX_SOURCE_PMU, + IOMUX_SOURCE_PMU, + IOMUX_SOURCE_PMU, + IOMUX_UNROUTED + ), + PIN_BANK_IOMUX_FLAGS(1, 32, "gpio1", IOMUX_UNROUTED, + IOMUX_UNROUTED, + IOMUX_UNROUTED, + 0 + ), + PIN_BANK_IOMUX_FLAGS(2, 32, "gpio2", 0, 0, 0, IOMUX_UNROUTED), + PIN_BANK_IOMUX_FLAGS(3, 32, "gpio3", 0, 0, 0, IOMUX_WIDTH_4BIT), + PIN_BANK_IOMUX_FLAGS(4, 32, "gpio4", IOMUX_WIDTH_4BIT, + IOMUX_WIDTH_4BIT, + 0, + 0 + ), + PIN_BANK_IOMUX_FLAGS(5, 32, "gpio5", IOMUX_UNROUTED, + 0, + 0, + IOMUX_UNROUTED + ), + PIN_BANK_IOMUX_FLAGS(6, 32, "gpio6", 0, 0, 0, IOMUX_UNROUTED), + PIN_BANK_IOMUX_FLAGS(7, 32, "gpio7", 0, + 0, + IOMUX_WIDTH_4BIT, + IOMUX_UNROUTED + ), + PIN_BANK(8, 16, "gpio8"), +}; +#endif + static void pinctrl_rk3288_pwm_config(struct rk3288_grf *grf, int pwm_id) { switch (pwm_id) { @@ -56,13 +151,14 @@ static void pinctrl_rk3288_i2c_config(struct rk3288_grf *grf, { switch (i2c_id) { case PERIPH_ID_I2C0: - clrsetbits_le32(&pmu->gpio0b_iomux, + clrsetbits_le32(&pmu->gpio0_iomux[PMU_GPIO0_B], GPIO0_B7_MASK << GPIO0_B7_SHIFT, GPIO0_B7_I2C0PMU_SDA << GPIO0_B7_SHIFT); - clrsetbits_le32(&pmu->gpio0b_iomux, + clrsetbits_le32(&pmu->gpio0_iomux[PMU_GPIO0_C], GPIO0_C0_MASK << GPIO0_C0_SHIFT, GPIO0_C0_I2C0PMU_SCL << GPIO0_C0_SHIFT); break; +#ifndef CONFIG_SPL_BUILD case PERIPH_ID_I2C1: rk_clrsetreg(&grf->gpio8a_iomux, GPIO8A4_MASK << GPIO8A4_SHIFT | @@ -99,12 +195,14 @@ static void pinctrl_rk3288_i2c_config(struct rk3288_grf *grf, GPIO7C4_MASK << GPIO7C4_SHIFT, GPIO7C4_I2C5HDMI_SCL << GPIO7C4_SHIFT); break; +#endif default: debug("i2c id = %d iomux error!\n", i2c_id); break; } } +#ifndef CONFIG_SPL_BUILD static void pinctrl_rk3288_lcdc_config(struct rk3288_grf *grf, int lcd_id) { switch (lcd_id) { @@ -124,11 +222,13 @@ static void pinctrl_rk3288_lcdc_config(struct rk3288_grf *grf, int lcd_id) break; } } +#endif static int pinctrl_rk3288_spi_config(struct rk3288_grf *grf, enum periph_id spi_id, int cs) { switch (spi_id) { +#ifndef CONFIG_SPL_BUILD case PERIPH_ID_SPI0: switch (cs) { case 0: @@ -165,6 +265,7 @@ static int pinctrl_rk3288_spi_config(struct rk3288_grf *grf, GPIO7B5_SPI1_CSN0 << GPIO7B5_SHIFT | GPIO7B4_SPI1_CLK << GPIO7B4_SHIFT); break; +#endif case PERIPH_ID_SPI2: switch (cs) { case 0: @@ -202,6 +303,7 @@ err: static void pinctrl_rk3288_uart_config(struct rk3288_grf *grf, int uart_id) { switch (uart_id) { +#ifndef CONFIG_SPL_BUILD case PERIPH_ID_UART_BT: rk_clrsetreg(&grf->gpio4c_iomux, GPIO4C3_MASK << GPIO4C3_SHIFT | @@ -224,6 +326,7 @@ static void pinctrl_rk3288_uart_config(struct rk3288_grf *grf, int uart_id) GPIO5B1_UART1BB_SOUT << GPIO5B1_SHIFT | GPIO5B0_UART1BB_SIN << GPIO5B0_SHIFT); break; +#endif case PERIPH_ID_UART_DBG: rk_clrsetreg(&grf->gpio7ch_iomux, GPIO7C7_MASK << GPIO7C7_SHIFT | @@ -231,6 +334,7 @@ static void pinctrl_rk3288_uart_config(struct rk3288_grf *grf, int uart_id) GPIO7C7_UART2DBG_SOUT << GPIO7C7_SHIFT | GPIO7C6_UART2DBG_SIN << GPIO7C6_SHIFT); break; +#ifndef CONFIG_SPL_BUILD case PERIPH_ID_UART_GPS: rk_clrsetreg(&grf->gpio7b_iomux, GPIO7B2_MASK << GPIO7B2_SHIFT | @@ -254,6 +358,7 @@ static void pinctrl_rk3288_uart_config(struct rk3288_grf *grf, int uart_id) GPIO5B6_UART4EXP_SOUT << GPIO5B6_SHIFT | GPIO5B7_UART4EXP_SIN << GPIO5B7_SHIFT); break; +#endif default: debug("uart id = %d iomux error!\n", uart_id); break; @@ -298,6 +403,7 @@ static void pinctrl_rk3288_sdmmc_config(struct rk3288_grf *grf, int mmc_id) } } +#ifndef CONFIG_SPL_BUILD static void pinctrl_rk3288_hdmi_config(struct rk3288_grf *grf, int hdmi_id) { switch (hdmi_id) { @@ -312,6 +418,7 @@ static void pinctrl_rk3288_hdmi_config(struct rk3288_grf *grf, int hdmi_id) break; } } +#endif static int rk3288_pinctrl_request(struct udevice *dev, int func, int flags) { @@ -346,17 +453,19 @@ static int rk3288_pinctrl_request(struct udevice *dev, int func, int flags) case PERIPH_ID_UART4: pinctrl_rk3288_uart_config(priv->grf, func); break; +#ifndef CONFIG_SPL_BUILD case PERIPH_ID_LCDC0: case PERIPH_ID_LCDC1: pinctrl_rk3288_lcdc_config(priv->grf, func); break; + case PERIPH_ID_HDMI: + pinctrl_rk3288_hdmi_config(priv->grf, func); + break; +#endif 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; } @@ -394,6 +503,8 @@ static int rk3288_pinctrl_get_periph_id(struct udevice *dev, return PERIPH_ID_I2C4; case 65: return PERIPH_ID_I2C5; + case 103: + return PERIPH_ID_HDMI; } return -ENOENT; @@ -410,7 +521,142 @@ static int rk3288_pinctrl_set_state_simple(struct udevice *dev, return rk3288_pinctrl_request(dev, func, 0); } +#ifndef CONFIG_SPL_BUILD +int rk3288_pinctrl_get_pin_info(struct rk3288_pinctrl_priv *priv, + int banknum, int ind, u32 **addrp, uint *shiftp, + uint *maskp) +{ + struct rockchip_pin_bank *bank = &rk3288_pin_banks[banknum]; + uint muxnum; + u32 *addr; + + for (muxnum = 0; muxnum < 4; muxnum++) { + struct rockchip_iomux *mux = &bank->iomux[muxnum]; + + if (ind >= 8) { + ind -= 8; + continue; + } + + if (mux->type & IOMUX_SOURCE_PMU) + addr = priv->pmu->gpio0_iomux; + else + addr = (u32 *)priv->grf - 4; + addr += mux->offset; + *shiftp = ind & 7; + if (mux->type & IOMUX_WIDTH_4BIT) { + *maskp = 0xf; + *shiftp *= 4; + if (*shiftp >= 16) { + *shiftp -= 16; + addr++; + } + } else { + *maskp = 3; + *shiftp *= 2; + } + + debug("%s: addr=%p, mask=%x, shift=%x\n", __func__, addr, + *maskp, *shiftp); + *addrp = addr; + return 0; + } + + return -EINVAL; +} + +static int rk3288_pinctrl_get_gpio_mux(struct udevice *dev, int banknum, + int index) +{ + struct rk3288_pinctrl_priv *priv = dev_get_priv(dev); + uint shift; + uint mask; + u32 *addr; + int ret; + + ret = rk3288_pinctrl_get_pin_info(priv, banknum, index, &addr, &shift, + &mask); + if (ret) + return ret; + return (readl(addr) & mask) >> shift; +} + +static int rk3288_pinctrl_set_pins(struct udevice *dev, int banknum, int index, + int muxval, int flags) +{ + struct rk3288_pinctrl_priv *priv = dev_get_priv(dev); + uint shift, ind = index; + uint mask; + u32 *addr; + int ret; + + debug("%s: %x %x %x %x\n", __func__, banknum, index, muxval, flags); + ret = rk3288_pinctrl_get_pin_info(priv, banknum, index, &addr, &shift, + &mask); + if (ret) + return ret; + rk_clrsetreg(addr, mask << shift, muxval << shift); + + /* Handle pullup/pulldown */ + if (flags) { + uint val = 0; + + if (flags & (1 << PIN_CONFIG_BIAS_PULL_UP)) + val = 1; + else if (flags & (1 << PIN_CONFIG_BIAS_PULL_DOWN)) + val = 2; + shift = (index & 7) * 2; + ind = index >> 3; + if (banknum == 0) + addr = &priv->pmu->gpio0pull[ind]; + else + addr = &priv->grf->gpio1_p[banknum - 1][ind]; + debug("%s: addr=%p, val=%x, shift=%x\n", __func__, addr, val, + shift); + rk_clrsetreg(addr, 3 << shift, val << shift); + } + + return 0; +} + +static int rk3288_pinctrl_set_state(struct udevice *dev, struct udevice *config) +{ + const void *blob = gd->fdt_blob; + int pcfg_node, ret, flags, count, i; + u32 cell[40], *ptr; + + debug("%s: %s %s\n", __func__, dev->name, config->name); + ret = fdtdec_get_int_array_count(blob, config->of_offset, + "rockchip,pins", cell, + ARRAY_SIZE(cell)); + if (ret < 0) { + debug("%s: bad array %d\n", __func__, ret); + return -EINVAL; + } + count = ret; + for (i = 0, ptr = cell; i < count; i += 4, ptr += 4) { + pcfg_node = fdt_node_offset_by_phandle(blob, ptr[3]); + if (pcfg_node < 0) + return -EINVAL; + flags = pinctrl_decode_pin_config(blob, pcfg_node); + if (flags < 0) + return flags; + + ret = rk3288_pinctrl_set_pins(dev, ptr[0], ptr[1], ptr[2], + flags); + if (ret) + return ret; + } + + return 0; +} +#endif + static struct pinctrl_ops rk3288_pinctrl_ops = { +#ifndef CONFIG_SPL_BUILD + .set_state = rk3288_pinctrl_set_state, + .get_gpio_mux = rk3288_pinctrl_get_gpio_mux, +#endif .set_state_simple = rk3288_pinctrl_set_state_simple, .request = rk3288_pinctrl_request, .get_periph_id = rk3288_pinctrl_get_periph_id, @@ -422,15 +668,49 @@ static int rk3288_pinctrl_bind(struct udevice *dev) return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false); } +#ifndef CONFIG_SPL_BUILD +static int rk3288_pinctrl_parse_tables(struct rk3288_pinctrl_priv *priv, + struct rockchip_pin_bank *banks, + int count) +{ + struct rockchip_pin_bank *bank; + uint reg, muxnum, banknum; + + reg = 0; + for (banknum = 0; banknum < count; banknum++) { + bank = &banks[banknum]; + bank->reg = reg; + debug("%s: bank %d, reg %x\n", __func__, banknum, reg * 4); + for (muxnum = 0; muxnum < 4; muxnum++) { + struct rockchip_iomux *mux = &bank->iomux[muxnum]; + + if (!(mux->type & IOMUX_UNROUTED)) + mux->offset = reg; + if (mux->type & IOMUX_WIDTH_4BIT) + reg += 2; + else + reg += 1; + } + } + + return 0; +} +#endif + static int rk3288_pinctrl_probe(struct udevice *dev) { struct rk3288_pinctrl_priv *priv = dev_get_priv(dev); + int ret = 0; 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); +#ifndef CONFIG_SPL_BUILD + ret = rk3288_pinctrl_parse_tables(priv, rk3288_pin_banks, + ARRAY_SIZE(rk3288_pin_banks)); +#endif - return 0; + return ret; } static const struct udevice_id rk3288_pinctrl_ids[] = { diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig index fb29843..7f69ae1 100644 --- a/drivers/power/pmic/Kconfig +++ b/drivers/power/pmic/Kconfig @@ -10,6 +10,27 @@ config DM_PMIC - 'drivers/power/pmic/pmic-uclass.c' - 'include/power/pmic.h' +config PMIC_CHILDREN + bool "Allow child devices for PMICs" + depends on DM_PMIC + default y + ---help--- + This allows PMICs to support child devices (such as regulators) in + SPL. This adds quite a bit of code so if you are not using this + feature you can turn it off. Most likely you should turn it on for + U-Boot proper. + +config SPL_PMIC_CHILDREN + bool "Allow child devices for PMICs in SPL" + depends on DM_PMIC + default y + ---help--- + This allows PMICs to support child devices (such as regulators) in + SPL. This adds quite a bit of code so if you are not using this + feature you can turn it off. In this case you may need a 'back door' + to call your regulator code (e.g. see rk808.c for direct functions + for use in SPL). + config PMIC_ACT8846 bool "Enable support for the active-semi 8846 PMIC" depends on DM_PMIC && DM_I2C @@ -33,6 +54,15 @@ config DM_PMIC_MAX77686 This config enables implementation of driver-model pmic uclass features for PMIC MAX77686. The driver implements read/write operations. +config PMIC_RK808 + bool "Enable support for Rockchip PMIC RK808" + depends on DM_PMIC + ---help--- + The Rockchip RK808 PMIC provides four buck DC-DC convertors, 8 LDOs, + an RTC and two low Rds (resistance (drain to source)) switches. It is + accessed via an I2C interface. The device is used with Rockchip SoCs. + This driver implements register read/write operations. + config PMIC_S2MPS11 bool "Enable Driver Model for PMIC Samsung S2MPS11" depends on DM_PMIC diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index 91e78f8..c6e8d0c 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_DM_PMIC_PFUZE100) += pfuze100.o obj-$(CONFIG_PMIC_S2MPS11) += s2mps11.o obj-$(CONFIG_DM_PMIC_SANDBOX) += sandbox.o i2c_pmic_emul.o obj-$(CONFIG_PMIC_ACT8846) += act8846.o +obj-$(CONFIG_PMIC_RK808) += rk808.o obj-$(CONFIG_PMIC_TPS65090) += tps65090.o obj-$(CONFIG_PMIC_S5M8767) += s5m8767.o diff --git a/drivers/power/pmic/pmic-uclass.c b/drivers/power/pmic/pmic-uclass.c index 49709f3..7211026 100644 --- a/drivers/power/pmic/pmic-uclass.c +++ b/drivers/power/pmic/pmic-uclass.c @@ -18,6 +18,7 @@ DECLARE_GLOBAL_DATA_PTR; +#if CONFIG_IS_ENABLED(PMIC_CHILDREN) int pmic_bind_children(struct udevice *pmic, int offset, const struct pmic_child_info *child_info) { @@ -84,6 +85,7 @@ int pmic_bind_children(struct udevice *pmic, int offset, debug("Bound: %d childs for PMIC: '%s'\n", bind_count, pmic->name); return bind_count; } +#endif int pmic_get(const char *name, struct udevice **devp) { @@ -131,8 +133,9 @@ int pmic_reg_read(struct udevice *dev, uint reg) u8 byte; int ret; + debug("%s: reg=%x", __func__, reg); ret = pmic_read(dev, reg, &byte, 1); - debug("%s: reg=%x, value=%x\n", __func__, reg, byte); + debug(", value=%x, ret=%d\n", byte, ret); return ret ? ret : byte; } @@ -140,9 +143,13 @@ int pmic_reg_read(struct udevice *dev, uint reg) int pmic_reg_write(struct udevice *dev, uint reg, uint value) { u8 byte = value; + int ret; + + debug("%s: reg=%x, value=%x", __func__, reg, value); + ret = pmic_write(dev, reg, &byte, 1); + debug(", ret=%d\n", ret); - debug("%s: reg=%x, value=%x\n", __func__, reg, value); - return pmic_write(dev, reg, &byte, 1); + return ret; } int pmic_clrsetbits(struct udevice *dev, uint reg, uint clr, uint set) diff --git a/drivers/power/pmic/rk808.c b/drivers/power/pmic/rk808.c new file mode 100644 index 0000000..770f471 --- /dev/null +++ b/drivers/power/pmic/rk808.c @@ -0,0 +1,102 @@ +/* + * 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/rk808_pmic.h> +#include <power/pmic.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const struct pmic_child_info pmic_children_info[] = { + { .prefix = "DCDC_REG", .driver = "rk808_buck"}, + { .prefix = "LDO_REG", .driver = "rk808_ldo"}, + { .prefix = "SWITCH_REG", .driver = "rk808_switch"}, + { }, +}; + +static int rk808_reg_count(struct udevice *dev) +{ + return RK808_NUM_OF_REGS; +} + +static int rk808_write(struct udevice *dev, uint reg, const uint8_t *buff, + int len) +{ + int ret; + + ret = dm_i2c_write(dev, reg, buff, len); + if (ret) { + debug("write error to device: %p register: %#x!", dev, reg); + return ret; + } + + return 0; +} + +static int rk808_read(struct udevice *dev, uint reg, uint8_t *buff, int len) +{ + int ret; + + ret = dm_i2c_read(dev, reg, buff, len); + if (ret) { + debug("read error from device: %p register: %#x!", dev, reg); + return ret; + } + + return 0; +} + +#if CONFIG_IS_ENABLED(PMIC_CHILDREN) +static int rk808_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; +} +#endif + +static struct dm_pmic_ops rk808_ops = { + .reg_count = rk808_reg_count, + .read = rk808_read, + .write = rk808_write, +}; + +static const struct udevice_id rk808_ids[] = { + { .compatible = "rockchip,rk808" }, + { } +}; + +U_BOOT_DRIVER(pmic_rk808) = { + .name = "rk808 pmic", + .id = UCLASS_PMIC, + .of_match = rk808_ids, +#if CONFIG_IS_ENABLED(PMIC_CHILDREN) + .bind = rk808_bind, +#endif + .ops = &rk808_ops, +}; diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig index 434dd02..465ff3f 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 SPL_DM_REGULATOR + bool "Enable regulators for SPL" + depends on DM_REGULATOR + ---help--- + Regulators are seldom needed in SPL. Even if they are accessed, some + code space can be saved by accessing the PMIC registers directly. + Enable this option if you need regulators in SPL and can cope with + the extra code size. + config REGULATOR_ACT8846 bool "Enable driver for ACT8846 regulator" depends on DM_REGULATOR && PMIC_ACT8846 @@ -49,6 +58,15 @@ config DM_REGULATOR_FIXED features for fixed value regulators. The driver implements get/set api for enable and get only for voltage value. +config REGULATOR_RK808 + bool "Enable driver for RK808 regulators" + depends on DM_REGULATOR && PMIC_RK808 + ---help--- + Enable support for the regulator functions of the RK808 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 REGULATOR_S5M8767 bool "Enable support for S5M8767 regulator" depends on DM_REGULATOR && PMIC_S5M8767 diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile index c85978e..1590d85 100644 --- a/drivers/power/regulator/Makefile +++ b/drivers/power/regulator/Makefile @@ -5,11 +5,12 @@ # SPDX-License-Identifier: GPL-2.0+ # -obj-$(CONFIG_DM_REGULATOR) += regulator-uclass.o +obj-$(CONFIG_$(SPL_)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 +obj-$(CONFIG_$(SPL_)DM_REGULATOR_FIXED) += fixed.o +obj-$(CONFIG_REGULATOR_RK808) += rk808.o obj-$(CONFIG_REGULATOR_S5M8767) += s5m8767.o obj-$(CONFIG_DM_REGULATOR_SANDBOX) += sandbox.o obj-$(CONFIG_REGULATOR_TPS65090) += tps65090_regulator.o diff --git a/drivers/power/regulator/regulator-uclass.c b/drivers/power/regulator/regulator-uclass.c index 4241a4c..9fe07f2 100644 --- a/drivers/power/regulator/regulator-uclass.c +++ b/drivers/power/regulator/regulator-uclass.c @@ -178,7 +178,7 @@ static void regulator_show(struct udevice *dev, int ret) printf("; set %d uA", uc_pdata->min_uA); printf("; enabling"); if (ret) - printf(" (ret: %d)\n", ret); + printf(" (ret: %d)", ret); printf("\n"); } @@ -334,6 +334,8 @@ int regulators_enable_boot_on(bool verbose) } if (verbose) regulator_show(dev, ret); + if (ret == -ENOSYS) + ret = 0; } return ret; diff --git a/drivers/power/regulator/rk808.c b/drivers/power/regulator/rk808.c new file mode 100644 index 0000000..adef8f5 --- /dev/null +++ b/drivers/power/regulator/rk808.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2015 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * Based on Rockchip's drivers/power/pmic/pmic_rk808.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/rk808_pmic.h> +#include <power/pmic.h> +#include <power/regulator.h> + +#ifndef CONFIG_SPL_BUILD +#define ENABLE_DRIVER +#endif + +struct rk808_reg_info { + uint min_uv; + uint step_uv; + s8 vsel_reg; + u8 vsel_bits; +}; + +static const struct rk808_reg_info rk808_buck[] = { + { 712500, 12500, REG_BUCK1_ON_VSEL, 6, }, + { 712500, 12500, REG_BUCK2_ON_VSEL, 6, }, + { 712500, 12500, -1, 6, }, + { 1800000, 100000, REG_BUCK4_ON_VSEL, 4, }, +}; + +static const struct rk808_reg_info rk808_ldo[] = { + { 1800000, 100000, LDO1_ON_VSEL, 5, }, + { 1800000, 100000, LDO2_ON_VSEL, 5, }, + { 800000, 100000, LDO3_ON_VSEL, 4, }, + { 1800000, 100000, LDO4_ON_VSEL, 5, }, + { 1800000, 100000, LDO5_ON_VSEL, 5, }, + { 800000, 100000, LDO6_ON_VSEL, 5, }, + { 800000, 100000, LDO7_ON_VSEL, 5, }, + { 1800000, 100000, LDO8_ON_VSEL, 5, }, +}; + + +static int _buck_set_value(struct udevice *pmic, int buck, int uvolt) +{ + const struct rk808_reg_info *info = &rk808_buck[buck - 1]; + int mask = (1 << info->vsel_bits) - 1; + int val; + + if (info->vsel_reg == -1) + return -ENOSYS; + val = (uvolt - info->min_uv) / info->step_uv; + debug("%s: reg=%x, mask=%x, val=%x\n", __func__, info->vsel_reg, mask, + val); + + return pmic_clrsetbits(pmic, info->vsel_reg, mask, val); +} + +static int _buck_set_enable(struct udevice *pmic, int buck, bool enable) +{ + uint mask; + int ret; + + buck--; + mask = 1 << buck; + if (enable) { + ret = pmic_clrsetbits(pmic, DCDC_ILMAX, 0, 3 << (buck * 2)); + if (ret) + return ret; + ret = pmic_clrsetbits(pmic, REG_DCDC_UV_ACT, 1 << buck, 0); + if (ret) + return ret; + } + + return pmic_clrsetbits(pmic, REG_DCDC_EN, mask, enable ? mask : 0); +} + +#ifdef ENABLE_DRIVER +static int buck_get_value(struct udevice *dev) +{ + int buck = dev->driver_data - 1; + const struct rk808_reg_info *info = &rk808_buck[buck]; + int mask = (1 << info->vsel_bits) - 1; + int ret, val; + + if (info->vsel_reg == -1) + return -ENOSYS; + ret = pmic_reg_read(dev->parent, info->vsel_reg); + if (ret < 0) + return ret; + val = ret & mask; + + return info->min_uv + val * info->step_uv; +} + +static int buck_set_value(struct udevice *dev, int uvolt) +{ + int buck = dev->driver_data; + + return _buck_set_value(dev->parent, buck, uvolt); +} + +static int buck_set_enable(struct udevice *dev, bool enable) +{ + int buck = dev->driver_data; + + return _buck_set_enable(dev->parent, buck, enable); +} + +static bool buck_get_enable(struct udevice *dev) +{ + int buck = dev->driver_data - 1; + int ret; + uint mask; + + mask = 1 << buck; + + ret = pmic_reg_read(dev->parent, REG_DCDC_EN); + if (ret < 0) + return ret; + + return ret & mask ? true : false; +} + +static int ldo_get_value(struct udevice *dev) +{ + int ldo = dev->driver_data - 1; + const struct rk808_reg_info *info = &rk808_ldo[ldo]; + int mask = (1 << info->vsel_bits) - 1; + int ret, val; + + if (info->vsel_reg == -1) + return -ENOSYS; + ret = pmic_reg_read(dev->parent, info->vsel_reg); + if (ret < 0) + return ret; + val = ret & mask; + + return info->min_uv + val * info->step_uv; +} + +static int ldo_set_value(struct udevice *dev, int uvolt) +{ + int ldo = dev->driver_data - 1; + const struct rk808_reg_info *info = &rk808_ldo[ldo]; + int mask = (1 << info->vsel_bits) - 1; + int val; + + if (info->vsel_reg == -1) + return -ENOSYS; + val = (uvolt - info->min_uv) / info->step_uv; + debug("%s: reg=%x, mask=%x, val=%x\n", __func__, info->vsel_reg, mask, + val); + + return pmic_clrsetbits(dev->parent, info->vsel_reg, mask, val); +} + +static int ldo_set_enable(struct udevice *dev, bool enable) +{ + int ldo = dev->driver_data - 1; + uint mask; + + mask = 1 << ldo; + + return pmic_clrsetbits(dev->parent, REG_LDO_EN, mask, + enable ? mask : 0); +} + +static bool ldo_get_enable(struct udevice *dev) +{ + int ldo = dev->driver_data - 1; + int ret; + uint mask; + + mask = 1 << ldo; + + ret = pmic_reg_read(dev->parent, REG_LDO_EN); + if (ret < 0) + return ret; + + return ret & mask ? true : false; +} + +static int switch_set_enable(struct udevice *dev, bool enable) +{ + int sw = dev->driver_data - 1; + uint mask; + + mask = 1 << (sw + 5); + + return pmic_clrsetbits(dev->parent, REG_DCDC_EN, mask, + enable ? mask : 0); +} + +static bool switch_get_enable(struct udevice *dev) +{ + int sw = dev->driver_data - 1; + int ret; + uint mask; + + mask = 1 << (sw + 5); + + ret = pmic_reg_read(dev->parent, REG_DCDC_EN); + if (ret < 0) + return ret; + + return ret & mask ? true : false; +} + +static int rk808_buck_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_platdata *uc_pdata; + + uc_pdata = dev_get_uclass_platdata(dev); + + uc_pdata->type = REGULATOR_TYPE_BUCK; + uc_pdata->mode_count = 0; + + return 0; +} + +static int rk808_ldo_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_platdata *uc_pdata; + + uc_pdata = dev_get_uclass_platdata(dev); + + uc_pdata->type = REGULATOR_TYPE_LDO; + uc_pdata->mode_count = 0; + + return 0; +} + +static int rk808_switch_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_platdata *uc_pdata; + + uc_pdata = dev_get_uclass_platdata(dev); + + uc_pdata->type = REGULATOR_TYPE_FIXED; + uc_pdata->mode_count = 0; + + return 0; +} + +static const struct dm_regulator_ops rk808_buck_ops = { + .get_value = buck_get_value, + .set_value = buck_set_value, + .get_enable = buck_get_enable, + .set_enable = buck_set_enable, +}; + +static const struct dm_regulator_ops rk808_ldo_ops = { + .get_value = ldo_get_value, + .set_value = ldo_set_value, + .get_enable = ldo_get_enable, + .set_enable = ldo_set_enable, +}; + +static const struct dm_regulator_ops rk808_switch_ops = { + .get_enable = switch_get_enable, + .set_enable = switch_set_enable, +}; + +U_BOOT_DRIVER(rk808_buck) = { + .name = "rk808_buck", + .id = UCLASS_REGULATOR, + .ops = &rk808_buck_ops, + .probe = rk808_buck_probe, +}; + +U_BOOT_DRIVER(rk808_ldo) = { + .name = "rk808_ldo", + .id = UCLASS_REGULATOR, + .ops = &rk808_ldo_ops, + .probe = rk808_ldo_probe, +}; + +U_BOOT_DRIVER(rk808_switch) = { + .name = "rk808_switch", + .id = UCLASS_REGULATOR, + .ops = &rk808_switch_ops, + .probe = rk808_switch_probe, +}; +#endif + +int rk808_spl_configure_buck(struct udevice *pmic, int buck, int uvolt) +{ + int ret; + + ret = _buck_set_value(pmic, buck, uvolt); + if (ret) + return ret; + + return _buck_set_enable(pmic, buck, true); +} diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig new file mode 100644 index 0000000..cd8f357 --- /dev/null +++ b/drivers/pwm/Kconfig @@ -0,0 +1,19 @@ +config DM_PWM + bool "Enable support for pulse-width modulation devices (PWM)" + depends on DM + help + A pulse-width modulator emits a pulse of varying width and provides + control over the duty cycle (high and low time) of the signal. This + is often used to control a voltage level. The more time the PWM + spends in the 'high' state, the higher the voltage. The PWM's + frequency/period can be controlled along with the proportion of that + time that the signal is high. + +config PWM_ROCKCHIP + bool "Enable support for the Rockchip PWM" + depends on DM_PWM + help + This PWM is found on RK3288 and other Rockchip SoCs. It supports a + programmable period and duty cycle. A 32-bit counter is used. + Various options provided in the hardware (such as capture mode and + continuous/single-shot) are not supported by the driver. diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index c0c4883..b6d8c16 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -10,4 +10,6 @@ #ccflags-y += -DDEBUG +obj-$(CONFIG_DM_PWM) += pwm-uclass.o +obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o diff --git a/drivers/pwm/pwm-uclass.c b/drivers/pwm/pwm-uclass.c new file mode 100644 index 0000000..c2200af --- /dev/null +++ b/drivers/pwm/pwm-uclass.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <pwm.h> + +int pwm_set_config(struct udevice *dev, uint channel, uint period_ns, + uint duty_ns) +{ + struct pwm_ops *ops = pwm_get_ops(dev); + + if (!ops->set_config) + return -ENOSYS; + + return ops->set_config(dev, channel, period_ns, duty_ns); +} + +int pwm_set_enable(struct udevice *dev, uint channel, bool enable) +{ + struct pwm_ops *ops = pwm_get_ops(dev); + + if (!ops->set_enable) + return -ENOSYS; + + return ops->set_enable(dev, channel, enable); +} + +UCLASS_DRIVER(pwm) = { + .id = UCLASS_PWM, + .name = "pwm", +}; diff --git a/drivers/pwm/rk_pwm.c b/drivers/pwm/rk_pwm.c new file mode 100644 index 0000000..2d289a4 --- /dev/null +++ b/drivers/pwm/rk_pwm.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <div64.h> +#include <dm.h> +#include <pwm.h> +#include <regmap.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/pwm.h> +#include <power/regulator.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct rk_pwm_priv { + struct rk3288_pwm *regs; + struct rk3288_grf *grf; +}; + +static int rk_pwm_set_config(struct udevice *dev, uint channel, uint period_ns, + uint duty_ns) +{ + struct rk_pwm_priv *priv = dev_get_priv(dev); + struct rk3288_pwm *regs = priv->regs; + unsigned long period, duty; + + debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns); + writel(PWM_SEL_SRC_CLK | PWM_OUTPUT_LEFT | PWM_LP_DISABLE | + PWM_CONTINUOUS | PWM_DUTY_POSTIVE | PWM_INACTIVE_POSTIVE | + RK_PWM_DISABLE, + ®s->ctrl); + + period = lldiv((uint64_t)(PD_BUS_PCLK_HZ / 1000) * period_ns, 1000000); + duty = lldiv((uint64_t)(PD_BUS_PCLK_HZ / 1000) * duty_ns, 1000000); + + writel(period, ®s->period_hpr); + writel(duty, ®s->duty_lpr); + debug("%s: period=%lu, duty=%lu\n", __func__, period, duty); + + return 0; +} + +static int rk_pwm_set_enable(struct udevice *dev, uint channel, bool enable) +{ + struct rk_pwm_priv *priv = dev_get_priv(dev); + struct rk3288_pwm *regs = priv->regs; + + debug("%s: Enable '%s'\n", __func__, dev->name); + clrsetbits_le32(®s->ctrl, RK_PWM_ENABLE, enable ? RK_PWM_ENABLE : 0); + + return 0; +} + +static int rk_pwm_ofdata_to_platdata(struct udevice *dev) +{ + struct rk_pwm_priv *priv = dev_get_priv(dev); + struct regmap *map; + + priv->regs = (struct rk3288_pwm *)dev_get_addr(dev); + map = syscon_get_regmap_by_driver_data(ROCKCHIP_SYSCON_GRF); + if (IS_ERR(map)) + return PTR_ERR(map); + priv->grf = regmap_get_range(map, 0); + + return 0; +} + +static int rk_pwm_probe(struct udevice *dev) +{ + struct rk_pwm_priv *priv = dev_get_priv(dev); + + rk_setreg(&priv->grf->soc_con2, 1 << 0); + + return 0; +} + +static const struct pwm_ops rk_pwm_ops = { + .set_config = rk_pwm_set_config, + .set_enable = rk_pwm_set_enable, +}; + +static const struct udevice_id rk_pwm_ids[] = { + { .compatible = "rockchip,rk3288-pwm" }, + { } +}; + +U_BOOT_DRIVER(rk_pwm) = { + .name = "rk_pwm", + .id = UCLASS_PWM, + .of_match = rk_pwm_ids, + .ops = &rk_pwm_ops, + .ofdata_to_platdata = rk_pwm_ofdata_to_platdata, + .probe = rk_pwm_probe, + .priv_auto_alloc_size = sizeof(struct rk_pwm_priv), +}; diff --git a/drivers/spi/rk_spi.c b/drivers/spi/rk_spi.c index 242a83b..9eff423 100644 --- a/drivers/spi/rk_spi.c +++ b/drivers/spi/rk_spi.c @@ -27,23 +27,23 @@ DECLARE_GLOBAL_DATA_PTR; #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 */ + uint activate_delay_us; /* Delay to wait after activate */ }; struct rockchip_spi_priv { struct rockchip_spi *regs; - struct udevice *clk_gpll; + struct udevice *clk; + int clk_id; 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 last_speed_hz; unsigned int tmode; uint input_rate; }; @@ -82,6 +82,7 @@ static void rkspi_set_clk(struct rockchip_spi_priv *priv, uint speed) debug("spi speed %u, div %u\n", speed, clk_div); writel(clk_div, &priv->regs->baudr); + priv->last_speed_hz = speed; } static int rkspi_wait_till_not_busy(struct rockchip_spi *regs) @@ -99,44 +100,60 @@ static int rkspi_wait_till_not_busy(struct rockchip_spi *regs) return 0; } -static void spi_cs_activate(struct rockchip_spi *regs, uint cs) +static void spi_cs_activate(struct udevice *dev, uint cs) { + struct udevice *bus = dev->parent; + struct rockchip_spi_platdata *plat = bus->platdata; + struct rockchip_spi_priv *priv = dev_get_priv(bus); + struct rockchip_spi *regs = priv->regs; + debug("activate cs%u\n", cs); writel(1 << cs, ®s->ser); + if (plat->activate_delay_us) + udelay(plat->activate_delay_us); } -static void spi_cs_deactivate(struct rockchip_spi *regs, uint cs) +static void spi_cs_deactivate(struct udevice *dev, uint cs) { + struct udevice *bus = dev->parent; + struct rockchip_spi_platdata *plat = bus->platdata; + struct rockchip_spi_priv *priv = dev_get_priv(bus); + struct rockchip_spi *regs = priv->regs; + debug("deactivate cs%u\n", cs); writel(0, ®s->ser); + + /* Remember time of this transaction so we can honour the bus delay */ + if (plat->deactivate_delay_us) + priv->last_transaction_us = timer_get_us(); } static int rockchip_spi_ofdata_to_platdata(struct udevice *bus) { struct rockchip_spi_platdata *plat = bus->platdata; + struct rockchip_spi_priv *priv = dev_get_priv(bus); 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); + ret = clk_get_by_index(bus, 0, &priv->clk); if (ret < 0) { - debug("%s: Could not get peripheral ID for %s: %d\n", __func__, + debug("%s: Could not get clock for %s: %d\n", __func__, bus->name, ret); - return -FDT_ERR_NOTFOUND; + return ret; } - plat->periph_id = ret; + priv->clk_id = ret; plat->frequency = fdtdec_get_int(blob, node, "spi-max-frequency", - 50000000); + 50000000); plat->deactivate_delay_us = fdtdec_get_int(blob, node, "spi-deactivate-delay", 0); - debug("%s: base=%lx, periph_id=%d, max-frequency=%d, deactivate_delay=%d\n", - __func__, plat->base, plat->periph_id, plat->frequency, + plat->activate_delay_us = fdtdec_get_int(blob, node, + "spi-activate-delay", 0); + debug("%s: base=%x, max-frequency=%d, deactivate_delay=%d\n", + __func__, (uint)plat->base, plat->frequency, plat->deactivate_delay_us); return 0; @@ -153,18 +170,12 @@ static int rockchip_spi_probe(struct udevice *bus) 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); + ret = clk_set_periph_rate(priv->clk, priv->clk_id, 99000000); if (ret < 0) { debug("%s: Failed to set clock: %d\n", __func__, ret); return ret; @@ -180,13 +191,10 @@ static int rockchip_spi_probe(struct udevice *bus) 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); @@ -208,13 +216,14 @@ static int rockchip_spi_claim_bus(struct udevice *dev) return -EPROTONOSUPPORT; } - rkspi_set_clk(priv, priv->speed_hz); + if (priv->speed_hz != priv->last_speed_hz) + rkspi_set_clk(priv, priv->speed_hz); /* Operation Mode */ ctrlr0 = OMOD_MASTER << OMOD_SHIFT; /* Data Frame Size */ - ctrlr0 |= spi_dfs & DFS_MASK << DFS_SHIFT; + ctrlr0 |= spi_dfs << DFS_SHIFT; /* set SPI mode 0..3 */ if (priv->mode & SPI_CPOL) @@ -235,7 +244,7 @@ static int rockchip_spi_claim_bus(struct udevice *dev) ctrlr0 |= FBM_MSB << FBM_SHIFT; /* Byte and Halfword Transform */ - ctrlr0 |= (spi_tf & HALF_WORD_MASK) << HALF_WORD_TX_SHIFT; + ctrlr0 |= spi_tf << HALF_WORD_TX_SHIFT; /* Rxd Sample Delay */ ctrlr0 |= 0 << RXDSD_SHIFT; @@ -248,17 +257,16 @@ static int rockchip_spi_claim_bus(struct udevice *dev) 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) { + struct udevice *bus = dev->parent; + struct rockchip_spi_priv *priv = dev_get_priv(bus); + + rkspi_enable_chip(priv->regs, false); + return 0; } @@ -282,12 +290,12 @@ static int rockchip_spi_xfer(struct udevice *dev, unsigned int bitlen, /* Assert CS before transfer */ if (flags & SPI_XFER_BEGIN) - spi_cs_activate(regs, slave_plat->cs); + spi_cs_activate(dev, slave_plat->cs); while (len > 0) { int todo = min(len, 0xffff); - rkspi_enable_chip(regs, true); + rkspi_enable_chip(regs, false); writel(todo - 1, ®s->ctrlr1); rkspi_enable_chip(regs, true); @@ -316,7 +324,7 @@ static int rockchip_spi_xfer(struct udevice *dev, unsigned int bitlen, /* Deassert CS after transfer */ if (flags & SPI_XFER_END) - spi_cs_deactivate(regs, slave_plat->cs); + spi_cs_deactivate(dev, slave_plat->cs); rkspi_enable_chip(regs, false); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index ae122da..912e397 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -293,12 +293,25 @@ config VIDEO_LCD_SPI_MISO option takes a string in the format understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H. -config DISPLAY_PORT - bool "Enable DisplayPort support" +config DISPLAY + bool "Enable Display support" + depends on DM + default y + help + This supports drivers that provide a display, such as eDP (Embedded + DisplayPort) and HDMI (High Definition Multimedia Interface). + The devices provide a simple interface to start up the display, + read display information and enable it. + +config VIDEO_ROCKCHIP + bool "Enable Rockchip video support" + depends on DM_VIDEO help - eDP (Embedded DisplayPort) is a standard widely used in laptops - to drive LCD panels. This framework provides support for enabling - these displays where supported by the video hardware. + Rockchip SoCs provide video output capabilities for High-Definition + Multimedia Interface (HDMI), Low-voltage Differential Signalling + (LVDS), embedded DisplayPort (eDP) and Display Serial Interface + (DSI). This driver supports the on-chip video output device, and + targets the Rockchip RK3288. config VIDEO_SANDBOX_SDL bool "Enable sandbox video console using SDL" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index ee04629..0910510 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -6,9 +6,14 @@ # ifdef CONFIG_DM -obj-$(CONFIG_DISPLAY_PORT) += dp-uclass.o +obj-$(CONFIG_DISPLAY) += display-uclass.o +obj-$(CONFIG_DM_VIDEO) += backlight-uclass.o +obj-$(CONFIG_DM_VIDEO) += panel-uclass.o simple_panel.o obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o console_normal.o obj-$(CONFIG_DM_VIDEO) += video_bmp.o +ifdef CONFIG_DM_VIDEO +obj-$(CONFIG_DM_PWM) += pwm_backlight.o +endif obj-$(CONFIG_VIDEO_ROTATION) += console_rotate.o endif @@ -57,5 +62,6 @@ obj-$(CONFIG_LG4573) += lg4573.o obj-$(CONFIG_AM335X_LCD) += am335x-fb.o obj-${CONFIG_VIDEO_TEGRA124} += tegra124/ +obj-${CONFIG_VIDEO_ROCKCHIP} += rockchip/ obj-y += bridge/ diff --git a/drivers/video/backlight-uclass.c b/drivers/video/backlight-uclass.c new file mode 100644 index 0000000..0238289 --- /dev/null +++ b/drivers/video/backlight-uclass.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <backlight.h> + +int backlight_enable(struct udevice *dev) +{ + const struct backlight_ops *ops = backlight_get_ops(dev); + + if (!ops->enable) + return -ENOSYS; + + return ops->enable(dev); +} + +UCLASS_DRIVER(backlight) = { + .id = UCLASS_PANEL_BACKLIGHT, + .name = "backlight", +}; diff --git a/drivers/video/bridge/video-bridge-uclass.c b/drivers/video/bridge/video-bridge-uclass.c index 6c5990f..07270ba 100644 --- a/drivers/video/bridge/video-bridge-uclass.c +++ b/drivers/video/bridge/video-bridge-uclass.c @@ -55,7 +55,8 @@ static int video_bridge_pre_probe(struct udevice *dev) &uc_priv->sleep, GPIOD_IS_OUT); if (ret) { debug("%s: Could not decode sleep-gpios (%d)\n", __func__, ret); - return ret; + if (ret != -ENOENT) + return ret; } /* * Drop this for now as we do not have driver model pinctrl support @@ -70,7 +71,8 @@ static int video_bridge_pre_probe(struct udevice *dev) GPIOD_IS_OUT); if (ret) { debug("%s: Could not decode reset-gpios (%d)\n", __func__, ret); - return ret; + if (ret != -ENOENT) + return ret; } /* * Drop this for now as we do not have driver model pinctrl support @@ -83,9 +85,10 @@ static int video_bridge_pre_probe(struct udevice *dev) */ ret = gpio_request_by_name(dev, "hotplug-gpios", 0, &uc_priv->hotplug, GPIOD_IS_IN); - if (ret && ret != -ENOENT) { + if (ret) { debug("%s: Could not decode hotplug (%d)\n", __func__, ret); - return ret; + if (ret != -ENOENT) + return ret; } return 0; diff --git a/drivers/video/display-uclass.c b/drivers/video/display-uclass.c new file mode 100644 index 0000000..31522ea --- /dev/null +++ b/drivers/video/display-uclass.c @@ -0,0 +1,52 @@ +/* + * Copyright 2014 Google Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <display.h> +#include <edid.h> +#include <errno.h> + +int display_read_edid(struct udevice *dev, u8 *buf, int buf_size) +{ + struct dm_display_ops *ops = display_get_ops(dev); + + if (!ops || !ops->read_edid) + return -ENOSYS; + return ops->read_edid(dev, buf, buf_size); +} + +int display_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *timing) +{ + struct dm_display_ops *ops = display_get_ops(dev); + + if (!ops || !ops->enable) + return -ENOSYS; + return ops->enable(dev, panel_bpp, timing); +} + +int display_read_timing(struct udevice *dev, struct display_timing *timing) +{ + struct dm_display_ops *ops = display_get_ops(dev); + int panel_bits_per_colour; + u8 buf[EDID_EXT_SIZE]; + int ret; + + if (!ops || !ops->read_edid) + return -ENOSYS; + ret = ops->read_edid(dev, buf, sizeof(buf)); + if (ret < 0) + return ret; + + return edid_get_timing(buf, ret, timing, &panel_bits_per_colour); +} + +UCLASS_DRIVER(display) = { + .id = UCLASS_DISPLAY, + .name = "display", + .per_device_platdata_auto_alloc_size = sizeof(struct display_plat), +}; diff --git a/drivers/video/dp-uclass.c b/drivers/video/dp-uclass.c deleted file mode 100644 index 17f5de9..0000000 --- a/drivers/video/dp-uclass.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2014 Google Inc. - * - * SPDX-License-Identifier: GPL-2.0+ - */ - -#include <common.h> -#include <dm.h> -#include <displayport.h> -#include <errno.h> - -int display_port_read_edid(struct udevice *dev, u8 *buf, int buf_size) -{ - struct dm_display_port_ops *ops = display_port_get_ops(dev); - - if (!ops || !ops->read_edid) - return -ENOSYS; - return ops->read_edid(dev, buf, buf_size); -} - -int display_port_enable(struct udevice *dev, int panel_bpp, - const struct display_timing *timing) -{ - struct dm_display_port_ops *ops = display_port_get_ops(dev); - - if (!ops || !ops->enable) - return -ENOSYS; - return ops->enable(dev, panel_bpp, timing); -} - -UCLASS_DRIVER(display_port) = { - .id = UCLASS_DISPLAY_PORT, - .name = "display_port", -}; diff --git a/drivers/video/panel-uclass.c b/drivers/video/panel-uclass.c new file mode 100644 index 0000000..3f4c41b --- /dev/null +++ b/drivers/video/panel-uclass.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <panel.h> + +int panel_enable_backlight(struct udevice *dev) +{ + struct panel_ops *ops = panel_get_ops(dev); + + if (!ops->enable_backlight) + return -ENOSYS; + + return ops->enable_backlight(dev); +} + +UCLASS_DRIVER(panel) = { + .id = UCLASS_PANEL, + .name = "panel", +}; diff --git a/drivers/video/pwm_backlight.c b/drivers/video/pwm_backlight.c new file mode 100644 index 0000000..de6277f --- /dev/null +++ b/drivers/video/pwm_backlight.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <backlight.h> +#include <pwm.h> +#include <asm/gpio.h> +#include <power/regulator.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct pwm_backlight_priv { + struct udevice *reg; + struct gpio_desc enable; + struct udevice *pwm; + uint channel; + uint period_ns; + uint default_level; + uint min_level; + uint max_level; +}; + +static int pwm_backlight_enable(struct udevice *dev) +{ + struct pwm_backlight_priv *priv = dev_get_priv(dev); + uint duty_cycle; + int ret; + + debug("%s: Enable '%s', regulator '%s'\n", __func__, dev->name, + priv->reg->name); + ret = regulator_set_enable(priv->reg, true); + if (ret) { + debug("%s: Cannot enable regulator for PWM '%s'\n", __func__, + dev->name); + return ret; + } + mdelay(120); + + duty_cycle = priv->period_ns * (priv->default_level - priv->min_level) / + (priv->max_level - priv->min_level + 1); + ret = pwm_set_config(priv->pwm, priv->channel, priv->period_ns, + duty_cycle); + if (ret) + return ret; + ret = pwm_set_enable(priv->pwm, priv->channel, true); + if (ret) + return ret; + mdelay(10); + dm_gpio_set_value(&priv->enable, 1); + + return 0; +} + +static int pwm_backlight_ofdata_to_platdata(struct udevice *dev) +{ + struct pwm_backlight_priv *priv = dev_get_priv(dev); + struct fdtdec_phandle_args args; + const void *blob = gd->fdt_blob; + int node = dev->of_offset; + int index, ret, count, len; + const u32 *cell; + + ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev, + "power-supply", &priv->reg); + if (ret) { + debug("%s: Cannot get power supply: ret=%d\n", __func__, ret); + return ret; + } + ret = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable, + GPIOD_IS_OUT); + if (ret) { + debug("%s: Warning: cannot get enable GPIO: ret=%d\n", + __func__, ret); + if (ret != -ENOENT) + return ret; + } + ret = fdtdec_parse_phandle_with_args(blob, node, "pwms", "#pwm-cells", + 0, 0, &args); + if (ret) { + debug("%s: Cannot get PWM phandle: ret=%d\n", __func__, ret); + return ret; + } + + ret = uclass_get_device_by_of_offset(UCLASS_PWM, args.node, &priv->pwm); + if (ret) { + debug("%s: Cannot get PWM: ret=%d\n", __func__, ret); + return ret; + } + priv->channel = args.args[0]; + priv->period_ns = args.args[1]; + + index = fdtdec_get_int(blob, node, "default-brightness-level", 255); + cell = fdt_getprop(blob, node, "brightness-levels", &len); + count = len / sizeof(u32); + if (cell && count > index) { + priv->default_level = fdt32_to_cpu(cell[index]); + priv->max_level = fdt32_to_cpu(cell[count - 1]); + } else { + priv->default_level = index; + priv->max_level = 255; + } + + + return 0; +} + +static int pwm_backlight_probe(struct udevice *dev) +{ + return 0; +} + +static const struct backlight_ops pwm_backlight_ops = { + .enable = pwm_backlight_enable, +}; + +static const struct udevice_id pwm_backlight_ids[] = { + { .compatible = "pwm-backlight" }, + { } +}; + +U_BOOT_DRIVER(pwm_backlight) = { + .name = "pwm_backlight", + .id = UCLASS_PANEL_BACKLIGHT, + .of_match = pwm_backlight_ids, + .ops = &pwm_backlight_ops, + .ofdata_to_platdata = pwm_backlight_ofdata_to_platdata, + .probe = pwm_backlight_probe, + .priv_auto_alloc_size = sizeof(struct pwm_backlight_priv), +}; diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile new file mode 100644 index 0000000..0e9a8ac --- /dev/null +++ b/drivers/video/rockchip/Makefile @@ -0,0 +1,8 @@ +# +# (C) Copyright 2000-2007 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-y += rk_edp.o rk_hdmi.o rk_vop.o diff --git a/drivers/video/rockchip/rk_edp.c b/drivers/video/rockchip/rk_edp.c new file mode 100644 index 0000000..124ddf6 --- /dev/null +++ b/drivers/video/rockchip/rk_edp.c @@ -0,0 +1,1085 @@ +/* + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <edid.h> +#include <panel.h> +#include <regmap.h> +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/edp_rk3288.h> +#include <asm/arch/grf_rk3288.h> +#include <dt-bindings/clock/rk3288-cru.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define MAX_CR_LOOP 5 +#define MAX_EQ_LOOP 5 +#define DP_LINK_STATUS_SIZE 6 + +static const char * const voltage_names[] = { + "0.4V", "0.6V", "0.8V", "1.2V" +}; +static const char * const pre_emph_names[] = { + "0dB", "3.5dB", "6dB", "9.5dB" +}; + +#define DP_VOLTAGE_MAX DP_TRAIN_VOLTAGE_SWING_1200 +#define DP_PRE_EMPHASIS_MAX DP_TRAIN_PRE_EMPHASIS_9_5 + +struct rk_edp_priv { + struct rk3288_edp *regs; + struct rk3288_grf *grf; + struct udevice *panel; + struct link_train link_train; + u8 train_set[4]; +}; + +static void rk_edp_init_refclk(struct rk3288_edp *regs) +{ + writel(SEL_24M, ®s->analog_ctl_2); + writel(REF_CLK_24M, ®s->pll_reg_1); + + writel(LDO_OUTPUT_V_SEL_145 | KVCO_DEFALUT | CHG_PUMP_CUR_SEL_5US | + V2L_CUR_SEL_1MA, ®s->pll_reg_2); + + writel(LOCK_DET_CNT_SEL_256 | LOOP_FILTER_RESET | PALL_SSC_RESET | + LOCK_DET_BYPASS | PLL_LOCK_DET_MODE | PLL_LOCK_DET_FORCE, + ®s->pll_reg_3); + + writel(REGULATOR_V_SEL_950MV | STANDBY_CUR_SEL | + CHG_PUMP_INOUT_CTRL_1200MV | CHG_PUMP_INPUT_CTRL_OP, + ®s->pll_reg_5); + + writel(SSC_OFFSET | SSC_MODE | SSC_DEPTH, ®s->ssc_reg); + + writel(TX_SWING_PRE_EMP_MODE | PRE_DRIVER_PW_CTRL1 | + LP_MODE_CLK_REGULATOR | RESISTOR_MSB_CTRL | RESISTOR_CTRL, + ®s->tx_common); + + writel(DP_AUX_COMMON_MODE | DP_AUX_EN | AUX_TERM_50OHM, + ®s->dp_aux); + + writel(DP_BG_OUT_SEL | DP_DB_CUR_CTRL | DP_BG_SEL | DP_RESISTOR_TUNE_BG, + ®s->dp_bias); + + writel(CH1_CH3_SWING_EMP_CTRL | CH0_CH2_SWING_EMP_CTRL, + ®s->dp_reserv2); +} + +static void rk_edp_init_interrupt(struct rk3288_edp *regs) +{ + /* Set interrupt pin assertion polarity as high */ + writel(INT_POL, ®s->int_ctl); + + /* Clear pending registers */ + writel(0xff, ®s->common_int_sta_1); + writel(0x4f, ®s->common_int_sta_2); + writel(0xff, ®s->common_int_sta_3); + writel(0x27, ®s->common_int_sta_4); + writel(0x7f, ®s->dp_int_sta); + + /* 0:mask,1: unmask */ + writel(0x00, ®s->common_int_mask_1); + writel(0x00, ®s->common_int_mask_2); + writel(0x00, ®s->common_int_mask_3); + writel(0x00, ®s->common_int_mask_4); + writel(0x00, ®s->int_sta_mask); +} + +static void rk_edp_enable_sw_function(struct rk3288_edp *regs) +{ + clrbits_le32(®s->func_en_1, SW_FUNC_EN_N); +} + +static bool rk_edp_get_pll_locked(struct rk3288_edp *regs) +{ + u32 val; + + val = readl(®s->dp_debug_ctl); + + return val & PLL_LOCK; +} + +static int rk_edp_init_analog_func(struct rk3288_edp *regs) +{ + ulong start; + + writel(0x00, ®s->dp_pd); + writel(PLL_LOCK_CHG, ®s->common_int_sta_1); + + clrbits_le32(®s->dp_debug_ctl, F_PLL_LOCK | PLL_LOCK_CTRL); + + start = get_timer(0); + while (!rk_edp_get_pll_locked(regs)) { + if (get_timer(start) > PLL_LOCK_TIMEOUT) { + printf("%s: PLL is not locked\n", __func__); + return -ETIMEDOUT; + } + } + + /* Enable Serdes FIFO function and Link symbol clock domain module */ + clrbits_le32(®s->func_en_2, SERDES_FIFO_FUNC_EN_N | + LS_CLK_DOMAIN_FUNC_EN_N | AUX_FUNC_EN_N | + SSC_FUNC_EN_N); + + return 0; +} + +static void rk_edp_init_aux(struct rk3288_edp *regs) +{ + /* Clear inerrupts related to AUX channel */ + writel(AUX_FUNC_EN_N, ®s->dp_int_sta); + + /* Disable AUX channel module */ + setbits_le32(®s->func_en_2, AUX_FUNC_EN_N); + + /* Receive AUX Channel DEFER commands equal to DEFFER_COUNT*64 */ + writel(DEFER_CTRL_EN | DEFER_COUNT(1), ®s->aux_ch_defer_dtl); + + /* Enable AUX channel module */ + clrbits_le32(®s->func_en_2, AUX_FUNC_EN_N); +} + +static int rk_edp_aux_enable(struct rk3288_edp *regs) +{ + ulong start; + + setbits_le32(®s->aux_ch_ctl_2, AUX_EN); + start = get_timer(0); + do { + if (!(readl(®s->aux_ch_ctl_2) & AUX_EN)) + return 0; + } while (get_timer(start) < 20); + + return -ETIMEDOUT; +} + +static int rk_edp_is_aux_reply(struct rk3288_edp *regs) +{ + ulong start; + + start = get_timer(0); + while (!(readl(®s->dp_int_sta) & RPLY_RECEIV)) { + if (get_timer(start) > 10) + return -ETIMEDOUT; + } + + writel(RPLY_RECEIV, ®s->dp_int_sta); + + return 0; +} + +static int rk_edp_start_aux_transaction(struct rk3288_edp *regs) +{ + int val, ret; + + /* Enable AUX CH operation */ + ret = rk_edp_aux_enable(regs); + if (ret) { + debug("AUX CH enable timeout!\n"); + return ret; + } + + /* Is AUX CH command reply received? */ + if (rk_edp_is_aux_reply(regs)) { + debug("AUX CH command reply failed!\n"); + return ret; + } + + /* Clear interrupt source for AUX CH access error */ + val = readl(®s->dp_int_sta); + if (val & AUX_ERR) { + writel(AUX_ERR, ®s->dp_int_sta); + return -EIO; + } + + /* Check AUX CH error access status */ + val = readl(®s->dp_int_sta); + if (val & AUX_STATUS_MASK) { + debug("AUX CH error happens: %d\n\n", val & AUX_STATUS_MASK); + return -EIO; + } + + return 0; +} + +static int rk_edp_dpcd_transfer(struct rk3288_edp *regs, + unsigned int val_addr, u8 *in_data, + unsigned int length, + enum dpcd_request request) +{ + int val; + int i, try_times; + u8 *data; + int ret = 0; + u32 len = 0; + + while (length) { + len = min(length, 16U); + for (try_times = 0; try_times < 10; try_times++) { + data = in_data; + /* Clear AUX CH data buffer */ + writel(BUF_CLR, ®s->buf_data_ctl); + + /* Select DPCD device address */ + writel(AUX_ADDR_7_0(val_addr), ®s->aux_addr_7_0); + writel(AUX_ADDR_15_8(val_addr), ®s->aux_addr_15_8); + writel(AUX_ADDR_19_16(val_addr), ®s->aux_addr_19_16); + + /* + * Set DisplayPort transaction and read 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + if (request == DPCD_WRITE) { + val = AUX_LENGTH(len) | + AUX_TX_COMM_DP_TRANSACTION | + AUX_TX_COMM_WRITE; + for (i = 0; i < len; i++) + writel(*data++, ®s->buf_data[i]); + } else + val = AUX_LENGTH(len) | + AUX_TX_COMM_DP_TRANSACTION | + AUX_TX_COMM_READ; + + writel(val, ®s->aux_ch_ctl_1); + + /* Start AUX transaction */ + ret = rk_edp_start_aux_transaction(regs); + if (ret == 0) + break; + else + printf("read dpcd Aux Transaction fail!\n"); + } + + if (ret) + return ret; + + if (request == DPCD_READ) { + for (i = 0; i < len; i++) + *data++ = (u8)readl(®s->buf_data[i]); + } + + length -= len; + val_addr += len; + in_data += len; + } + + return 0; +} + +static int rk_edp_dpcd_read(struct rk3288_edp *regs, u32 addr, u8 *values, + size_t size) +{ + return rk_edp_dpcd_transfer(regs, addr, values, size, DPCD_READ); +} + +static int rk_edp_dpcd_write(struct rk3288_edp *regs, u32 addr, u8 *values, + size_t size) +{ + return rk_edp_dpcd_transfer(regs, addr, values, size, DPCD_WRITE); +} + + +static int rk_edp_link_power_up(struct rk_edp_priv *edp) +{ + u8 value; + int ret; + + /* DP_SET_POWER register is only available on DPCD v1.1 and later */ + if (edp->link_train.revision < 0x11) + return 0; + + ret = rk_edp_dpcd_read(edp->regs, DPCD_LINK_POWER_STATE, &value, 1); + if (ret) + return ret; + + value &= ~DP_SET_POWER_MASK; + value |= DP_SET_POWER_D0; + + ret = rk_edp_dpcd_write(edp->regs, DPCD_LINK_POWER_STATE, &value, 1); + if (ret) + return ret; + + /* + * According to the DP 1.1 specification, a "Sink Device must exit the + * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink + * Control Field" (register 0x600). + */ + mdelay(1); + + return 0; +} + +static int rk_edp_link_configure(struct rk_edp_priv *edp) +{ + u8 values[2]; + + values[0] = edp->link_train.link_rate; + values[1] = edp->link_train.lane_count; + + return rk_edp_dpcd_write(edp->regs, DPCD_LINK_BW_SET, values, + sizeof(values)); +} + +static void rk_edp_set_link_training(struct rk_edp_priv *edp, + const u8 *training_values) +{ + int i; + + for (i = 0; i < edp->link_train.lane_count; i++) + writel(training_values[i], &edp->regs->ln_link_trn_ctl[i]); +} + +static u8 edp_link_status(const u8 *link_status, int r) +{ + return link_status[r - DPCD_LANE0_1_STATUS]; +} + +static int rk_edp_dpcd_read_link_status(struct rk_edp_priv *edp, + u8 *link_status) +{ + return rk_edp_dpcd_read(edp->regs, DPCD_LANE0_1_STATUS, link_status, + DP_LINK_STATUS_SIZE); +} + +static u8 edp_get_lane_status(const u8 *link_status, int lane) +{ + int i = DPCD_LANE0_1_STATUS + (lane >> 1); + int s = (lane & 1) * 4; + u8 l = edp_link_status(link_status, i); + + return (l >> s) & 0xf; +} + +static int rk_edp_clock_recovery(const u8 *link_status, int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = edp_get_lane_status(link_status, lane); + if ((lane_status & DP_LANE_CR_DONE) == 0) + return -EIO; + } + + return 0; +} + +static int rk_edp_channel_eq(const u8 *link_status, int lane_count) +{ + u8 lane_align; + u8 lane_status; + int lane; + + lane_align = edp_link_status(link_status, + DPCD_LANE_ALIGN_STATUS_UPDATED); + if (!(lane_align & DP_INTERLANE_ALIGN_DONE)) + return -EIO; + for (lane = 0; lane < lane_count; lane++) { + lane_status = edp_get_lane_status(link_status, lane); + if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS) + return -EIO; + } + + return 0; +} + +static uint rk_edp_get_adjust_request_voltage(const u8 *link_status, int lane) +{ + int i = DPCD_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : + DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); + u8 l = edp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT; +} + +static uint rk_edp_get_adjust_request_pre_emphasis(const u8 *link_status, + int lane) +{ + int i = DPCD_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : + DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); + u8 l = edp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT; +} + +static void edp_get_adjust_train(const u8 *link_status, int lane_count, + u8 train_set[]) +{ + uint v = 0; + uint p = 0; + int lane; + + for (lane = 0; lane < lane_count; lane++) { + uint this_v, this_p; + + this_v = rk_edp_get_adjust_request_voltage(link_status, lane); + this_p = rk_edp_get_adjust_request_pre_emphasis(link_status, + lane); + + debug("requested signal parameters: lane %d voltage %s pre_emph %s\n", + lane, + voltage_names[this_v >> DP_TRAIN_VOLTAGE_SWING_SHIFT], + pre_emph_names[this_p >> DP_TRAIN_PRE_EMPHASIS_SHIFT]); + + if (this_v > v) + v = this_v; + if (this_p > p) + p = this_p; + } + + if (v >= DP_VOLTAGE_MAX) + v |= DP_TRAIN_MAX_SWING_REACHED; + + if (p >= DP_PRE_EMPHASIS_MAX) + p |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + debug("using signal parameters: voltage %s pre_emph %s\n", + voltage_names[(v & DP_TRAIN_VOLTAGE_SWING_MASK) + >> DP_TRAIN_VOLTAGE_SWING_SHIFT], + pre_emph_names[(p & DP_TRAIN_PRE_EMPHASIS_MASK) + >> DP_TRAIN_PRE_EMPHASIS_SHIFT]); + + for (lane = 0; lane < 4; lane++) + train_set[lane] = v | p; +} + +static int rk_edp_link_train_cr(struct rk_edp_priv *edp) +{ + struct rk3288_edp *regs = edp->regs; + int clock_recovery; + uint voltage, tries = 0; + u8 status[DP_LINK_STATUS_SIZE]; + int i, ret; + u8 value; + + value = DP_TRAINING_PATTERN_1; + writel(value, ®s->dp_training_ptn_set); + ret = rk_edp_dpcd_write(regs, DPCD_TRAINING_PATTERN_SET, &value, 1); + if (ret) + return ret; + memset(edp->train_set, '\0', sizeof(edp->train_set)); + + /* clock recovery loop */ + clock_recovery = 0; + tries = 0; + voltage = 0xff; + + while (1) { + rk_edp_set_link_training(edp, edp->train_set); + ret = rk_edp_dpcd_write(regs, DPCD_TRAINING_LANE0_SET, + edp->train_set, + edp->link_train.lane_count); + if (ret) + return ret; + + mdelay(1); + + ret = rk_edp_dpcd_read_link_status(edp, status); + if (ret) { + printf("displayport link status failed, ret=%d\n", ret); + break; + } + + clock_recovery = rk_edp_clock_recovery(status, + edp->link_train.lane_count); + if (!clock_recovery) + break; + + for (i = 0; i < edp->link_train.lane_count; i++) { + if ((edp->train_set[i] & + DP_TRAIN_MAX_SWING_REACHED) == 0) + break; + } + if (i == edp->link_train.lane_count) { + printf("clock recovery reached max voltage\n"); + break; + } + + if ((edp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == + voltage) { + if (++tries == MAX_CR_LOOP) { + printf("clock recovery tried 5 times\n"); + break; + } + } else { + tries = 0; + } + + voltage = edp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK; + + /* Compute new train_set as requested by sink */ + edp_get_adjust_train(status, edp->link_train.lane_count, + edp->train_set); + } + if (clock_recovery) { + printf("clock recovery failed: %d\n", clock_recovery); + return clock_recovery; + } else { + debug("clock recovery at voltage %d pre-emphasis %d\n", + edp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK, + (edp->train_set[0] & DP_TRAIN_PRE_EMPHASIS_MASK) >> + DP_TRAIN_PRE_EMPHASIS_SHIFT); + return 0; + } +} + +static int rk_edp_link_train_ce(struct rk_edp_priv *edp) +{ + struct rk3288_edp *regs = edp->regs; + int channel_eq; + u8 value; + int tries; + u8 status[DP_LINK_STATUS_SIZE]; + int ret; + + value = DP_TRAINING_PATTERN_2; + writel(value, ®s->dp_training_ptn_set); + ret = rk_edp_dpcd_write(regs, DPCD_TRAINING_PATTERN_SET, &value, 1); + if (ret) + return ret; + + /* channel equalization loop */ + channel_eq = 0; + for (tries = 0; tries < 5; tries++) { + rk_edp_set_link_training(edp, edp->train_set); + udelay(400); + + if (rk_edp_dpcd_read_link_status(edp, status) < 0) { + printf("displayport link status failed\n"); + return -1; + } + + channel_eq = rk_edp_channel_eq(status, + edp->link_train.lane_count); + if (!channel_eq) + break; + edp_get_adjust_train(status, edp->link_train.lane_count, + edp->train_set); + } + + if (channel_eq) { + printf("channel eq failed, ret=%d\n", channel_eq); + return channel_eq; + } + + debug("channel eq at voltage %d pre-emphasis %d\n", + edp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK, + (edp->train_set[0] & DP_TRAIN_PRE_EMPHASIS_MASK) + >> DP_TRAIN_PRE_EMPHASIS_SHIFT); + + return 0; +} + +static int rk_edp_init_training(struct rk_edp_priv *edp) +{ + u8 values[3]; + int ret; + + ret = rk_edp_dpcd_read(edp->regs, DPCD_DPCD_REV, values, + sizeof(values)); + if (ret < 0) + return ret; + + edp->link_train.revision = values[0]; + edp->link_train.link_rate = values[1]; + edp->link_train.lane_count = values[2] & DP_MAX_LANE_COUNT_MASK; + + debug("max link rate:%d.%dGps max number of lanes:%d\n", + edp->link_train.link_rate * 27 / 100, + edp->link_train.link_rate * 27 % 100, + edp->link_train.lane_count); + + if ((edp->link_train.link_rate != LINK_RATE_1_62GBPS) && + (edp->link_train.link_rate != LINK_RATE_2_70GBPS)) { + debug("Rx Max Link Rate is abnormal :%x\n", + edp->link_train.link_rate); + return -EPERM; + } + + if (edp->link_train.lane_count == 0) { + debug("Rx Max Lane count is abnormal :%x\n", + edp->link_train.lane_count); + return -EPERM; + } + + ret = rk_edp_link_power_up(edp); + if (ret) + return ret; + + return rk_edp_link_configure(edp); +} + +static int rk_edp_hw_link_training(struct rk_edp_priv *edp) +{ + ulong start; + u32 val; + int ret; + + /* Set link rate and count as you want to establish */ + writel(edp->link_train.link_rate, &edp->regs->link_bw_set); + writel(edp->link_train.lane_count, &edp->regs->lane_count_set); + + ret = rk_edp_link_train_cr(edp); + if (ret) + return ret; + ret = rk_edp_link_train_ce(edp); + if (ret) + return ret; + + writel(HW_LT_EN, &edp->regs->dp_hw_link_training); + start = get_timer(0); + do { + val = readl(&edp->regs->dp_hw_link_training); + if (!(val & HW_LT_EN)) + break; + } while (get_timer(start) < 10); + + if (val & HW_LT_ERR_CODE_MASK) { + printf("edp hw link training error: %d\n", + val >> HW_LT_ERR_CODE_SHIFT); + return -EIO; + } + + return 0; +} + +static int rk_edp_select_i2c_device(struct rk3288_edp *regs, + unsigned int device_addr, + unsigned int val_addr) +{ + int ret; + + /* Set EDID device address */ + writel(device_addr, ®s->aux_addr_7_0); + writel(0x0, ®s->aux_addr_15_8); + writel(0x0, ®s->aux_addr_19_16); + + /* Set offset from base address of EDID device */ + writel(val_addr, ®s->buf_data[0]); + + /* + * Set I2C transaction and write address + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + writel(AUX_TX_COMM_I2C_TRANSACTION | AUX_TX_COMM_MOT | + AUX_TX_COMM_WRITE, ®s->aux_ch_ctl_1); + + /* Start AUX transaction */ + ret = rk_edp_start_aux_transaction(regs); + if (ret != 0) { + debug("select_i2c_device Aux Transaction fail!\n"); + return ret; + } + + return 0; +} + +static int rk_edp_i2c_read(struct rk3288_edp *regs, unsigned int device_addr, + unsigned int val_addr, unsigned int count, u8 edid[]) +{ + u32 val; + unsigned int i, j; + unsigned int cur_data_idx; + unsigned int defer = 0; + int ret = 0; + + for (i = 0; i < count; i += 16) { + for (j = 0; j < 10; j++) { /* try 10 times */ + /* Clear AUX CH data buffer */ + writel(BUF_CLR, ®s->buf_data_ctl); + + /* Set normal AUX CH command */ + clrbits_le32(®s->aux_ch_ctl_2, ADDR_ONLY); + + /* + * If Rx sends defer, Tx sends only reads + * request without sending addres + */ + if (!defer) { + ret = rk_edp_select_i2c_device(regs, + device_addr, + val_addr + i); + } else { + defer = 0; + } + + /* + * Set I2C transaction and write data + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + writel(AUX_LENGTH(16) | AUX_TX_COMM_I2C_TRANSACTION | + AUX_TX_COMM_READ, ®s->aux_ch_ctl_1); + + /* Start AUX transaction */ + ret = rk_edp_start_aux_transaction(regs); + if (ret == 0) { + break; + } else { + debug("Aux Transaction fail!\n"); + continue; + } + + /* Check if Rx sends defer */ + val = readl(®s->aux_rx_comm); + if (val == AUX_RX_COMM_AUX_DEFER || + val == AUX_RX_COMM_I2C_DEFER) { + debug("Defer: %d\n\n", val); + defer = 1; + } + } + + if (ret) + return ret; + + for (cur_data_idx = 0; cur_data_idx < 16; cur_data_idx++) { + val = readl(®s->buf_data[cur_data_idx]); + edid[i + cur_data_idx] = (u8)val; + } + } + + return 0; +} + +static int rk_edp_set_link_train(struct rk_edp_priv *edp) +{ + int ret; + + ret = rk_edp_init_training(edp); + if (ret) { + printf("DP LT init failed!\n"); + return ret; + } + + ret = rk_edp_hw_link_training(edp); + if (ret) + return ret; + + return 0; +} + +static void rk_edp_init_video(struct rk3288_edp *regs) +{ + writel(VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG, + ®s->common_int_sta_1); + writel(CHA_CRI(4) | CHA_CTRL, ®s->sys_ctl_2); + writel(VID_HRES_TH(2) | VID_VRES_TH(0), ®s->video_ctl_8); +} + +static void rk_edp_config_video_slave_mode(struct rk3288_edp *regs) +{ + clrbits_le32(®s->func_en_1, VID_FIFO_FUNC_EN_N | VID_CAP_FUNC_EN_N); +} + +static void rk_edp_set_video_cr_mn(struct rk3288_edp *regs, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value) +{ + if (type == REGISTER_M) { + setbits_le32(®s->sys_ctl_4, FIX_M_VID); + writel(m_value & 0xff, ®s->m_vid_0); + writel((m_value >> 8) & 0xff, ®s->m_vid_1); + writel((m_value >> 16) & 0xff, ®s->m_vid_2); + + writel(n_value & 0xf, ®s->n_vid_0); + writel((n_value >> 8) & 0xff, ®s->n_vid_1); + writel((n_value >> 16) & 0xff, ®s->n_vid_2); + } else { + clrbits_le32(®s->sys_ctl_4, FIX_M_VID); + + writel(0x00, ®s->n_vid_0); + writel(0x80, ®s->n_vid_1); + writel(0x00, ®s->n_vid_2); + } +} + +static int rk_edp_is_video_stream_clock_on(struct rk3288_edp *regs) +{ + ulong start; + u32 val; + + start = get_timer(0); + do { + val = readl(®s->sys_ctl_1); + + /* must write value to update DET_STA bit status */ + writel(val, ®s->sys_ctl_1); + val = readl(®s->sys_ctl_1); + if (!(val & DET_STA)) + continue; + + val = readl(®s->sys_ctl_2); + + /* must write value to update CHA_STA bit status */ + writel(val, ®s->sys_ctl_2); + val = readl(®s->sys_ctl_2); + if (!(val & CHA_STA)) + return 0; + + } while (get_timer(start) < 100); + + return -ETIMEDOUT; +} + +static int rk_edp_is_video_stream_on(struct rk_edp_priv *edp) +{ + ulong start; + u32 val; + + start = get_timer(0); + do { + val = readl(&edp->regs->sys_ctl_3); + + /* must write value to update STRM_VALID bit status */ + writel(val, &edp->regs->sys_ctl_3); + + val = readl(&edp->regs->sys_ctl_3); + if (!(val & STRM_VALID)) + return 0; + } while (get_timer(start) < 100); + + return -ETIMEDOUT; +} + +static int rk_edp_config_video(struct rk_edp_priv *edp) +{ + int ret; + + rk_edp_config_video_slave_mode(edp->regs); + + if (!rk_edp_get_pll_locked(edp->regs)) { + debug("PLL is not locked yet.\n"); + return -ETIMEDOUT; + } + + ret = rk_edp_is_video_stream_clock_on(edp->regs); + if (ret) + return ret; + + /* Set to use the register calculated M/N video */ + rk_edp_set_video_cr_mn(edp->regs, CALCULATED_M, 0, 0); + + /* For video bist, Video timing must be generated by register */ + clrbits_le32(&edp->regs->video_ctl_10, F_SEL); + + /* Disable video mute */ + clrbits_le32(&edp->regs->video_ctl_1, VIDEO_MUTE); + + /* Enable video at next frame */ + setbits_le32(&edp->regs->video_ctl_1, VIDEO_EN); + + return rk_edp_is_video_stream_on(edp); +} + +static void rockchip_edp_force_hpd(struct rk_edp_priv *edp) +{ + setbits_le32(&edp->regs->sys_ctl_3, F_HPD | HPD_CTRL); +} + +static int rockchip_edp_get_plug_in_status(struct rk_edp_priv *edp) +{ + u32 val; + + val = readl(&edp->regs->sys_ctl_3); + if (val & HPD_STATUS) + return 1; + + return 0; +} + +/* + * support edp HPD function + * some hardware version do not support edp hdp, + * we use 200ms to try to get the hpd single now, + * if we can not get edp hpd single, it will delay 200ms, + * also meet the edp power timing request, to compatible + * all of the hardware version + */ +static void rockchip_edp_wait_hpd(struct rk_edp_priv *edp) +{ + ulong start; + + start = get_timer(0); + do { + if (rockchip_edp_get_plug_in_status(edp)) + return; + udelay(100); + } while (get_timer(start) < 200); + + debug("do not get hpd single, force hpd\n"); + rockchip_edp_force_hpd(edp); +} + +static int rk_edp_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *edid) +{ + struct rk_edp_priv *priv = dev_get_priv(dev); + int ret = 0; + + ret = rk_edp_set_link_train(priv); + if (ret) { + printf("link train failed!\n"); + return ret; + } + + rk_edp_init_video(priv->regs); + ret = rk_edp_config_video(priv); + if (ret) { + printf("config video failed\n"); + return ret; + } + ret = panel_enable_backlight(priv->panel); + if (ret) { + debug("%s: backlight error: %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int rk_edp_read_edid(struct udevice *dev, u8 *buf, int buf_size) +{ + struct rk_edp_priv *priv = dev_get_priv(dev); + u32 edid_size = EDID_LENGTH; + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = rk_edp_i2c_read(priv->regs, EDID_ADDR, EDID_HEADER, + EDID_LENGTH, &buf[EDID_HEADER]); + if (ret) { + debug("EDID read failed\n"); + continue; + } + + /* + * check if the EDID has an extension flag, and read additional + * EDID data if needed + */ + if (buf[EDID_EXTENSION_FLAG]) { + edid_size += EDID_LENGTH; + ret = rk_edp_i2c_read(priv->regs, EDID_ADDR, + EDID_LENGTH, EDID_LENGTH, + &buf[EDID_LENGTH]); + if (ret) { + debug("EDID Read failed!\n"); + continue; + } + } + goto done; + } + + /* After 3 attempts, give up */ + return ret; + +done: + return edid_size; +} + +static int rk_edp_ofdata_to_platdata(struct udevice *dev) +{ + struct rk_edp_priv *priv = dev_get_priv(dev); + + priv->regs = (struct rk3288_edp *)dev_get_addr(dev); + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + + return 0; +} + +int rk_edp_probe(struct udevice *dev) +{ + struct display_plat *uc_plat = dev_get_uclass_platdata(dev); + struct rk_edp_priv *priv = dev_get_priv(dev); + struct rk3288_edp *regs = priv->regs; + struct udevice *clk; + int periph; + int ret; + + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, "rockchip,panel", + &priv->panel); + if (ret) { + debug("%s: Cannot find panel for '%s' (ret=%d)\n", __func__, + dev->name, ret); + return ret; + } + + int vop_id = uc_plat->source_id; + debug("%s, uc_plat=%p, vop_id=%u\n", __func__, uc_plat, vop_id); + + ret = clk_get_by_index(dev, 1, &clk); + if (ret >= 0) { + periph = ret; + ret = clk_set_periph_rate(clk, periph, 0); + } + if (ret) { + debug("%s: Failed to set EDP clock: ret=%d\n", __func__, ret); + return ret; + } + + ret = clk_get_by_index(uc_plat->src_dev, 0, &clk); + if (ret >= 0) { + periph = ret; + ret = clk_set_periph_rate(clk, periph, 192000000); + } + if (ret < 0) { + debug("%s: Failed to set clock in source device '%s': ret=%d\n", + __func__, uc_plat->src_dev->name, ret); + return ret; + } + + /* grf_edp_ref_clk_sel: from internal 24MHz or 27MHz clock */ + rk_setreg(&priv->grf->soc_con12, 1 << 4); + + /* select epd signal from vop0 or vop1 */ + rk_setreg(&priv->grf->soc_con6, (vop_id == 1) ? (1 << 5) : (1 << 5)); + + rockchip_edp_wait_hpd(priv); + + rk_edp_init_refclk(regs); + rk_edp_init_interrupt(regs); + rk_edp_enable_sw_function(regs); + ret = rk_edp_init_analog_func(regs); + if (ret) + return ret; + rk_edp_init_aux(regs); + + return 0; +} + +static const struct dm_display_ops dp_rockchip_ops = { + .read_edid = rk_edp_read_edid, + .enable = rk_edp_enable, +}; + +static const struct udevice_id rockchip_dp_ids[] = { + { .compatible = "rockchip,rk3288-edp" }, + { } +}; + +U_BOOT_DRIVER(dp_rockchip) = { + .name = "edp_rockchip", + .id = UCLASS_DISPLAY, + .of_match = rockchip_dp_ids, + .ops = &dp_rockchip_ops, + .ofdata_to_platdata = rk_edp_ofdata_to_platdata, + .probe = rk_edp_probe, + .priv_auto_alloc_size = sizeof(struct rk_edp_priv), +}; diff --git a/drivers/video/rockchip/rk_hdmi.c b/drivers/video/rockchip/rk_hdmi.c new file mode 100644 index 0000000..5fcb61a --- /dev/null +++ b/drivers/video/rockchip/rk_hdmi.c @@ -0,0 +1,933 @@ +/* + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <edid.h> +#include <regmap.h> +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/grf_rk3288.h> +#include <asm/arch/hdmi_rk3288.h> +#include <power/regulator.h> + +struct tmds_n_cts { + u32 tmds; + u32 cts; + u32 n; +}; + +struct rk_hdmi_priv { + struct rk3288_hdmi *regs; + struct rk3288_grf *grf; +}; + +static const struct tmds_n_cts n_cts_table[] = { + { + .tmds = 25175, .n = 6144, .cts = 25175, + }, { + .tmds = 25200, .n = 6144, .cts = 25200, + }, { + .tmds = 27000, .n = 6144, .cts = 27000, + }, { + .tmds = 27027, .n = 6144, .cts = 27027, + }, { + .tmds = 40000, .n = 6144, .cts = 40000, + }, { + .tmds = 54000, .n = 6144, .cts = 54000, + }, { + .tmds = 54054, .n = 6144, .cts = 54054, + }, { + .tmds = 65000, .n = 6144, .cts = 65000, + }, { + .tmds = 74176, .n = 11648, .cts = 140625, + }, { + .tmds = 74250, .n = 6144, .cts = 74250, + }, { + .tmds = 83500, .n = 6144, .cts = 83500, + }, { + .tmds = 106500, .n = 6144, .cts = 106500, + }, { + .tmds = 108000, .n = 6144, .cts = 108000, + }, { + .tmds = 148352, .n = 5824, .cts = 140625, + }, { + .tmds = 148500, .n = 6144, .cts = 148500, + }, { + .tmds = 297000, .n = 5120, .cts = 247500, + } +}; + +struct hdmi_mpll_config { + u64 mpixelclock; + /* Mode of Operation and PLL Dividers Control Register */ + u32 cpce; + /* PLL Gmp Control Register */ + u32 gmp; + /* PLL Current COntrol Register */ + u32 curr; +}; + +struct hdmi_phy_config { + u64 mpixelclock; + u32 sym_ctr; /* clock symbol and transmitter control */ + u32 term; /* transmission termination value */ + u32 vlev_ctr; /* voltage level control */ +}; + +static const struct hdmi_phy_config rockchip_phy_config[] = { + { + .mpixelclock = 74250, + .sym_ctr = 0x8009, .term = 0x0004, .vlev_ctr = 0x0272, + }, { + .mpixelclock = 148500, + .sym_ctr = 0x802b, .term = 0x0004, .vlev_ctr = 0x028d, + }, { + .mpixelclock = 297000, + .sym_ctr = 0x8039, .term = 0x0005, .vlev_ctr = 0x028d, + }, { + .mpixelclock = ~0ul, + .sym_ctr = 0x0000, .term = 0x0000, .vlev_ctr = 0x0000, + } +}; + +static const struct hdmi_mpll_config rockchip_mpll_cfg[] = { + { + .mpixelclock = 40000, + .cpce = 0x00b3, .gmp = 0x0000, .curr = 0x0018, + }, { + .mpixelclock = 65000, + .cpce = 0x0072, .gmp = 0x0001, .curr = 0x0028, + }, { + .mpixelclock = 66000, + .cpce = 0x013e, .gmp = 0x0003, .curr = 0x0038, + }, { + .mpixelclock = 83500, + .cpce = 0x0072, .gmp = 0x0001, .curr = 0x0028, + }, { + .mpixelclock = 146250, + .cpce = 0x0051, .gmp = 0x0002, .curr = 0x0038, + }, { + .mpixelclock = 148500, + .cpce = 0x0051, .gmp = 0x0003, .curr = 0x0000, + }, { + .mpixelclock = ~0ul, + .cpce = 0x0051, .gmp = 0x0003, .curr = 0x0000, + } +}; + +static const u32 csc_coeff_default[3][4] = { + { 0x2000, 0x0000, 0x0000, 0x0000 }, + { 0x0000, 0x2000, 0x0000, 0x0000 }, + { 0x0000, 0x0000, 0x2000, 0x0000 } +}; + +static void hdmi_set_clock_regenerator(struct rk3288_hdmi *regs, u32 n, u32 cts) +{ + u8 cts3; + u8 n3; + + /* first set ncts_atomic_write (if present) */ + n3 = HDMI_AUD_N3_NCTS_ATOMIC_WRITE; + writel(n3, ®s->aud_n3); + + /* set cts_manual (if present) */ + cts3 = HDMI_AUD_CTS3_CTS_MANUAL; + + cts3 |= HDMI_AUD_CTS3_N_SHIFT_1 << HDMI_AUD_CTS3_N_SHIFT_OFFSET; + cts3 |= (cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK; + + /* write cts values; cts3 must be written first */ + writel(cts3, ®s->aud_cts3); + writel((cts >> 8) & 0xff, ®s->aud_cts2); + writel(cts & 0xff, ®s->aud_cts1); + + /* write n values; n1 must be written last */ + n3 |= (n >> 16) & HDMI_AUD_N3_AUDN19_16_MASK; + writel(n3, ®s->aud_n3); + writel((n >> 8) & 0xff, ®s->aud_n2); + writel(n & 0xff, ®s->aud_n1); + + writel(HDMI_AUD_INPUTCLKFS_128, ®s->aud_inputclkfs); +} + +static int hdmi_lookup_n_cts(u32 pixel_clk) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(n_cts_table); i++) + if (pixel_clk <= n_cts_table[i].tmds) + break; + + if (i >= ARRAY_SIZE(n_cts_table)) + return -1; + + return i; +} + +static void hdmi_audio_set_samplerate(struct rk3288_hdmi *regs, u32 pixel_clk) +{ + u32 clk_n, clk_cts; + int index; + + index = hdmi_lookup_n_cts(pixel_clk); + if (index == -1) { + debug("audio not supported for pixel clk %d\n", pixel_clk); + return; + } + + clk_n = n_cts_table[index].n; + clk_cts = n_cts_table[index].cts; + hdmi_set_clock_regenerator(regs, clk_n, clk_cts); +} + +/* + * this submodule is responsible for the video data synchronization. + * for example, for rgb 4:4:4 input, the data map is defined as + * pin{47~40} <==> r[7:0] + * pin{31~24} <==> g[7:0] + * pin{15~8} <==> b[7:0] + */ +static void hdmi_video_sample(struct rk3288_hdmi *regs) +{ + u32 color_format = 0x01; + u8 val; + + val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE | + ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) & + HDMI_TX_INVID0_VIDEO_MAPPING_MASK); + + writel(val, ®s->tx_invid0); + + /* enable tx stuffing: when de is inactive, fix the output data to 0 */ + val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE; + writel(val, ®s->tx_instuffing); + writel(0x0, ®s->tx_gydata0); + writel(0x0, ®s->tx_gydata1); + writel(0x0, ®s->tx_rcrdata0); + writel(0x0, ®s->tx_rcrdata1); + writel(0x0, ®s->tx_bcbdata0); + writel(0x0, ®s->tx_bcbdata1); +} + +static void hdmi_update_csc_coeffs(struct rk3288_hdmi *regs) +{ + u32 i, j; + u32 csc_scale = 1; + + /* the csc registers are sequential, alternating msb then lsb */ + for (i = 0; i < ARRAY_SIZE(csc_coeff_default); i++) { + for (j = 0; j < ARRAY_SIZE(csc_coeff_default[0]); j++) { + u32 coeff = csc_coeff_default[i][j]; + writel(coeff >> 8, ®s->csc_coef[i][j].msb); + writel(coeff && 0xff, ®s->csc_coef[i][j].lsb); + } + } + + clrsetbits_le32(®s->csc_scale, HDMI_CSC_SCALE_CSCSCALE_MASK, + csc_scale); +} + +static void hdmi_video_csc(struct rk3288_hdmi *regs) +{ + u32 color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP; + u32 interpolation = HDMI_CSC_CFG_INTMODE_DISABLE; + + /* configure the csc registers */ + writel(interpolation, ®s->csc_cfg); + clrsetbits_le32(®s->csc_scale, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK, color_depth); + + hdmi_update_csc_coeffs(regs); +} + +static void hdmi_video_packetize(struct rk3288_hdmi *regs) +{ + u32 output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + u32 remap_size = HDMI_VP_REMAP_YCC422_16BIT; + u32 color_depth = 0; + u8 val, vp_conf; + + /* set the packetizer registers */ + val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) & + HDMI_VP_PR_CD_COLOR_DEPTH_MASK) | + ((0 << HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) & + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK); + writel(val, ®s->vp_pr_cd); + + clrsetbits_le32(®s->vp_stuff, HDMI_VP_STUFF_PR_STUFFING_MASK, + HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE); + + /* data from pixel repeater block */ + vp_conf = HDMI_VP_CONF_PR_EN_DISABLE | + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER; + + clrsetbits_le32(®s->vp_conf, HDMI_VP_CONF_PR_EN_MASK | + HDMI_VP_CONF_BYPASS_SELECT_MASK, vp_conf); + + clrsetbits_le32(®s->vp_stuff, HDMI_VP_STUFF_IDEFAULT_PHASE_MASK, + 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET); + + writel(remap_size, ®s->vp_remap); + + vp_conf = HDMI_VP_CONF_BYPASS_EN_ENABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + + clrsetbits_le32(®s->vp_conf, HDMI_VP_CONF_BYPASS_EN_MASK | + HDMI_VP_CONF_PP_EN_ENMASK | HDMI_VP_CONF_YCC422_EN_MASK, + vp_conf); + + clrsetbits_le32(®s->vp_stuff, HDMI_VP_STUFF_PP_STUFFING_MASK | + HDMI_VP_STUFF_YCC422_STUFFING_MASK, + HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE | + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE); + + clrsetbits_le32(®s->vp_conf, HDMI_VP_CONF_OUTPUT_SELECTOR_MASK, + output_select); +} + +static inline void hdmi_phy_test_clear(struct rk3288_hdmi *regs, u8 bit) +{ + clrsetbits_le32(®s->phy_tst0, HDMI_PHY_TST0_TSTCLR_MASK, + bit << HDMI_PHY_TST0_TSTCLR_OFFSET); +} + +static int hdmi_phy_wait_i2c_done(struct rk3288_hdmi *regs, u32 msec) +{ + ulong start; + u32 val; + + start = get_timer(0); + do { + val = readl(®s->ih_i2cmphy_stat0); + if (val & 0x3) { + writel(val, ®s->ih_i2cmphy_stat0); + return 0; + } + + udelay(100); + } while (get_timer(start) < msec); + + return 1; +} + +static void hdmi_phy_i2c_write(struct rk3288_hdmi *regs, uint data, uint addr) +{ + writel(0xff, ®s->ih_i2cmphy_stat0); + writel(addr, ®s->phy_i2cm_address_addr); + writel((u8)(data >> 8), ®s->phy_i2cm_datao_1_addr); + writel((u8)(data >> 0), ®s->phy_i2cm_datao_0_addr); + writel(HDMI_PHY_I2CM_OPERATION_ADDR_WRITE, + ®s->phy_i2cm_operation_addr); + + hdmi_phy_wait_i2c_done(regs, 1000); +} + +static void hdmi_phy_enable_power(struct rk3288_hdmi *regs, uint enable) +{ + clrsetbits_le32(®s->phy_conf0, HDMI_PHY_CONF0_PDZ_MASK, + enable << HDMI_PHY_CONF0_PDZ_OFFSET); +} + +static void hdmi_phy_enable_tmds(struct rk3288_hdmi *regs, uint enable) +{ + clrsetbits_le32(®s->phy_conf0, HDMI_PHY_CONF0_ENTMDS_MASK, + enable << HDMI_PHY_CONF0_ENTMDS_OFFSET); +} + +static void hdmi_phy_enable_spare(struct rk3288_hdmi *regs, uint enable) +{ + clrsetbits_le32(®s->phy_conf0, HDMI_PHY_CONF0_SPARECTRL_MASK, + enable << HDMI_PHY_CONF0_SPARECTRL_OFFSET); +} + +static void hdmi_phy_gen2_pddq(struct rk3288_hdmi *regs, uint enable) +{ + clrsetbits_le32(®s->phy_conf0, HDMI_PHY_CONF0_GEN2_PDDQ_MASK, + enable << HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET); +} + +static void hdmi_phy_gen2_txpwron(struct rk3288_hdmi *regs, uint enable) +{ + clrsetbits_le32(®s->phy_conf0, + HDMI_PHY_CONF0_GEN2_TXPWRON_MASK, + enable << HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET); +} + +static void hdmi_phy_sel_data_en_pol(struct rk3288_hdmi *regs, uint enable) +{ + clrsetbits_le32(®s->phy_conf0, + HDMI_PHY_CONF0_SELDATAENPOL_MASK, + enable << HDMI_PHY_CONF0_SELDATAENPOL_OFFSET); +} + +static void hdmi_phy_sel_interface_control(struct rk3288_hdmi *regs, + uint enable) +{ + clrsetbits_le32(®s->phy_conf0, HDMI_PHY_CONF0_SELDIPIF_MASK, + enable << HDMI_PHY_CONF0_SELDIPIF_OFFSET); +} + +static int hdmi_phy_configure(struct rk3288_hdmi *regs, u32 mpixelclock) +{ + ulong start; + u8 i, val; + + writel(HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS, + ®s->mc_flowctrl); + + /* gen2 tx power off */ + hdmi_phy_gen2_txpwron(regs, 0); + + /* gen2 pddq */ + hdmi_phy_gen2_pddq(regs, 1); + + /* phy reset */ + writel(HDMI_MC_PHYRSTZ_DEASSERT, ®s->mc_phyrstz); + writel(HDMI_MC_PHYRSTZ_ASSERT, ®s->mc_phyrstz); + writel(HDMI_MC_HEACPHY_RST_ASSERT, ®s->mc_heacphy_rst); + + hdmi_phy_test_clear(regs, 1); + writel(HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2, ®s->phy_i2cm_slave_addr); + hdmi_phy_test_clear(regs, 0); + + /* pll/mpll cfg - always match on final entry */ + for (i = 0; rockchip_mpll_cfg[i].mpixelclock != (~0ul); i++) + if (mpixelclock <= rockchip_mpll_cfg[i].mpixelclock) + break; + + hdmi_phy_i2c_write(regs, rockchip_mpll_cfg[i].cpce, PHY_OPMODE_PLLCFG); + hdmi_phy_i2c_write(regs, rockchip_mpll_cfg[i].gmp, PHY_PLLGMPCTRL); + hdmi_phy_i2c_write(regs, rockchip_mpll_cfg[i].curr, PHY_PLLCURRCTRL); + + hdmi_phy_i2c_write(regs, 0x0000, PHY_PLLPHBYCTRL); + hdmi_phy_i2c_write(regs, 0x0006, PHY_PLLCLKBISTPHASE); + + for (i = 0; rockchip_phy_config[i].mpixelclock != (~0ul); i++) + if (mpixelclock <= rockchip_phy_config[i].mpixelclock) + break; + + /* + * resistance term 133ohm cfg + * preemp cgf 0.00 + * tx/ck lvl 10 + */ + hdmi_phy_i2c_write(regs, rockchip_phy_config[i].term, PHY_TXTERM); + hdmi_phy_i2c_write(regs, rockchip_phy_config[i].sym_ctr, + PHY_CKSYMTXCTRL); + hdmi_phy_i2c_write(regs, rockchip_phy_config[i].vlev_ctr, PHY_VLEVCTRL); + + /* remove clk term */ + hdmi_phy_i2c_write(regs, 0x8000, PHY_CKCALCTRL); + + hdmi_phy_enable_power(regs, 1); + + /* toggle tmds enable */ + hdmi_phy_enable_tmds(regs, 0); + hdmi_phy_enable_tmds(regs, 1); + + /* gen2 tx power on */ + hdmi_phy_gen2_txpwron(regs, 1); + hdmi_phy_gen2_pddq(regs, 0); + + hdmi_phy_enable_spare(regs, 1); + + /* wait for phy pll lock */ + start = get_timer(0); + do { + val = readl(®s->phy_stat0); + if (!(val & HDMI_PHY_TX_PHY_LOCK)) + return 0; + + udelay(100); + } while (get_timer(start) < 5); + + return -1; +} + +static int hdmi_phy_init(struct rk3288_hdmi *regs, uint mpixelclock) +{ + int i, ret; + + /* hdmi phy spec says to do the phy initialization sequence twice */ + for (i = 0; i < 2; i++) { + hdmi_phy_sel_data_en_pol(regs, 1); + hdmi_phy_sel_interface_control(regs, 0); + hdmi_phy_enable_tmds(regs, 0); + hdmi_phy_enable_power(regs, 0); + + /* enable csc */ + ret = hdmi_phy_configure(regs, mpixelclock); + if (ret) { + debug("hdmi phy config failure %d\n", ret); + return ret; + } + } + + return 0; +} + +static void hdmi_av_composer(struct rk3288_hdmi *regs, + const struct display_timing *edid) +{ + u8 mdataenablepolarity = 1; + u8 inv_val; + uint hbl; + uint vbl; + + hbl = edid->hback_porch.typ + edid->hfront_porch.typ + + edid->hsync_len.typ; + vbl = edid->vback_porch.typ + edid->vfront_porch.typ + + edid->vsync_len.typ; + + /* set up hdmi_fc_invidconf */ + inv_val = HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE; + + inv_val |= (edid->flags & DISPLAY_FLAGS_HSYNC_HIGH ? + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW); + + inv_val |= (edid->flags & DISPLAY_FLAGS_VSYNC_HIGH ? + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW); + + inv_val |= (mdataenablepolarity ? + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW); + + /* + * TODO(sjg@chromium.org>: Need to check for HDMI / DVI + * inv_val |= (edid->hdmi_monitor_detected ? + * HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE : + * HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE); + */ + inv_val |= HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE; + + inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW; + + inv_val |= HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE; + + writel(inv_val, ®s->fc_invidconf); + + /* set up horizontal active pixel width */ + writel(edid->hactive.typ >> 8, ®s->fc_inhactv1); + writel(edid->hactive.typ, ®s->fc_inhactv0); + + /* set up vertical active lines */ + writel(edid->vactive.typ >> 8, ®s->fc_invactv1); + writel(edid->vactive.typ, ®s->fc_invactv0); + + /* set up horizontal blanking pixel region width */ + writel(hbl >> 8, ®s->fc_inhblank1); + writel(hbl, ®s->fc_inhblank0); + + /* set up vertical blanking pixel region width */ + writel(vbl, ®s->fc_invblank); + + /* set up hsync active edge delay width (in pixel clks) */ + writel(edid->hfront_porch.typ >> 8, ®s->fc_hsyncindelay1); + writel(edid->hfront_porch.typ, ®s->fc_hsyncindelay0); + + /* set up vsync active edge delay (in lines) */ + writel(edid->vfront_porch.typ, ®s->fc_vsyncindelay); + + /* set up hsync active pulse width (in pixel clks) */ + writel(edid->hsync_len.typ >> 8, ®s->fc_hsyncinwidth1); + writel(edid->hsync_len.typ, ®s->fc_hsyncinwidth0); + + /* set up vsync active edge delay (in lines) */ + writel(edid->vsync_len.typ, ®s->fc_vsyncinwidth); +} + +/* hdmi initialization step b.4 */ +static void hdmi_enable_video_path(struct rk3288_hdmi *regs) +{ + u8 clkdis; + + /* control period minimum duration */ + writel(12, ®s->fc_ctrldur); + writel(32, ®s->fc_exctrldur); + writel(1, ®s->fc_exctrlspac); + + /* set to fill tmds data channels */ + writel(0x0b, ®s->fc_ch0pream); + writel(0x16, ®s->fc_ch1pream); + writel(0x21, ®s->fc_ch2pream); + + /* enable pixel clock and tmds data path */ + clkdis = 0x7f; + clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE; + writel(clkdis, ®s->mc_clkdis); + + clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE; + writel(clkdis, ®s->mc_clkdis); + + clkdis &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE; + writel(clkdis, ®s->mc_clkdis); +} + +/* workaround to clear the overflow condition */ +static void hdmi_clear_overflow(struct rk3288_hdmi *regs) +{ + u8 val, count; + + /* tmds software reset */ + writel((u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, ®s->mc_swrstz); + + val = readl(®s->fc_invidconf); + + for (count = 0; count < 4; count++) + writel(val, ®s->fc_invidconf); +} + +static void hdmi_audio_set_format(struct rk3288_hdmi *regs) +{ + writel(HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_IN_EN_0, + ®s->aud_conf0); + + + writel(HDMI_AUD_CONF1_I2S_MODE_STANDARD_MODE | + HDMI_AUD_CONF1_I2S_WIDTH_16BIT, ®s->aud_conf1); + + writel(0x00, ®s->aud_conf2); +} + +static void hdmi_audio_fifo_reset(struct rk3288_hdmi *regs) +{ + writel((u8)~HDMI_MC_SWRSTZ_II2SSWRST_REQ, ®s->mc_swrstz); + writel(HDMI_AUD_CONF0_SW_AUDIO_FIFO_RST, ®s->aud_conf0); + + writel(0x00, ®s->aud_int); + writel(0x00, ®s->aud_int1); +} + +static void hdmi_init_interrupt(struct rk3288_hdmi *regs) +{ + u8 ih_mute; + + /* + * boot up defaults are: + * hdmi_ih_mute = 0x03 (disabled) + * hdmi_ih_mute_* = 0x00 (enabled) + * + * disable top level interrupt bits in hdmi block + */ + ih_mute = readl(®s->ih_mute) | + HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT; + + writel(ih_mute, ®s->ih_mute); + + /* enable i2c master done irq */ + writel(~0x04, ®s->i2cm_int); + + /* enable i2c client nack % arbitration error irq */ + writel(~0x44, ®s->i2cm_ctlint); + + /* enable phy i2cm done irq */ + writel(HDMI_PHY_I2CM_INT_ADDR_DONE_POL, ®s->phy_i2cm_int_addr); + + /* enable phy i2cm nack & arbitration error irq */ + writel(HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL | + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL, + ®s->phy_i2cm_ctlint_addr); + + /* enable cable hot plug irq */ + writel((u8)~HDMI_PHY_HPD, ®s->phy_mask0); + + /* clear hotplug interrupts */ + writel(HDMI_IH_PHY_STAT0_HPD, ®s->ih_phy_stat0); +} + +static u8 hdmi_get_plug_in_status(struct rk3288_hdmi *regs) +{ + u8 val = readl(®s->phy_stat0) & HDMI_PHY_HPD; + + return !!(val); +} + +static int hdmi_wait_for_hpd(struct rk3288_hdmi *regs) +{ + ulong start; + + start = get_timer(0); + do { + if (hdmi_get_plug_in_status(regs)) + return 0; + udelay(100); + } while (get_timer(start) < 30000); + + return -1; +} + +static int hdmi_ddc_wait_i2c_done(struct rk3288_hdmi *regs, int msec) +{ + u32 val; + ulong start; + + start = get_timer(0); + do { + val = readl(®s->ih_i2cm_stat0); + if (val & 0x2) { + writel(val, ®s->ih_i2cm_stat0); + return 0; + } + + udelay(100); + } while (get_timer(start) < msec); + + return 1; +} + +static void hdmi_ddc_reset(struct rk3288_hdmi *regs) +{ + clrbits_le32(®s->i2cm_softrstz, HDMI_I2CM_SOFTRSTZ); +} + +static int hdmi_read_edid(struct rk3288_hdmi *regs, int block, u8 *buff) +{ + int shift = (block % 2) * 0x80; + int edid_read_err = 0; + u32 trytime = 5; + u32 n, j, val; + + /* set ddc i2c clk which devided from ddc_clk to 100khz */ + writel(0x7a, ®s->i2cm_ss_scl_hcnt_0_addr); + writel(0x8d, ®s->i2cm_ss_scl_lcnt_0_addr); + + /* + * TODO(sjg@chromium.org): The above values don't work - these ones + * work better, but generate lots of errors in the data. + */ + writel(0x0d, ®s->i2cm_ss_scl_hcnt_0_addr); + writel(0x0d, ®s->i2cm_ss_scl_lcnt_0_addr); + clrsetbits_le32(®s->i2cm_div, HDMI_I2CM_DIV_FAST_STD_MODE, + HDMI_I2CM_DIV_STD_MODE); + + writel(HDMI_I2CM_SLAVE_DDC_ADDR, ®s->i2cm_slave); + writel(HDMI_I2CM_SEGADDR_DDC, ®s->i2cm_segaddr); + writel(block >> 1, ®s->i2cm_segptr); + + while (trytime--) { + edid_read_err = 0; + + for (n = 0; n < HDMI_EDID_BLOCK_SIZE / 8; n++) { + writel(shift + 8 * n, ®s->i2c_address); + + if (block == 0) + clrsetbits_le32(®s->i2cm_operation, + HDMI_I2CM_OPT_RD8, + HDMI_I2CM_OPT_RD8); + else + clrsetbits_le32(®s->i2cm_operation, + HDMI_I2CM_OPT_RD8_EXT, + HDMI_I2CM_OPT_RD8_EXT); + + if (hdmi_ddc_wait_i2c_done(regs, 10)) { + hdmi_ddc_reset(regs); + edid_read_err = 1; + break; + } + + for (j = 0; j < 8; j++) { + val = readl(®s->i2cm_buf0 + j); + buff[8 * n + j] = val; + } + } + + if (!edid_read_err) + break; + } + + return edid_read_err; +} + +static u8 pre_buf[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x04, 0x69, 0xfa, 0x23, 0xc8, 0x28, 0x01, 0x00, + 0x10, 0x17, 0x01, 0x03, 0x80, 0x33, 0x1d, 0x78, + 0x2a, 0xd9, 0x45, 0xa2, 0x55, 0x4d, 0xa0, 0x27, + 0x12, 0x50, 0x54, 0xb7, 0xef, 0x00, 0x71, 0x4f, + 0x81, 0x40, 0x81, 0x80, 0x95, 0x00, 0xb3, 0x00, + 0xd1, 0xc0, 0x81, 0xc0, 0x81, 0x00, 0x02, 0x3a, + 0x80, 0x18, 0x71, 0x38, 0x2d, 0x40, 0x58, 0x2c, + 0x45, 0x00, 0xfd, 0x1e, 0x11, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x44, 0x34, 0x4c, + 0x4d, 0x54, 0x46, 0x30, 0x37, 0x35, 0x39, 0x37, + 0x36, 0x0a, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x4b, 0x18, 0x53, 0x11, 0x00, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc, + 0x00, 0x41, 0x53, 0x55, 0x53, 0x20, 0x56, 0x53, + 0x32, 0x33, 0x38, 0x0a, 0x20, 0x20, 0x01, 0xb0, + 0x02, 0x03, 0x22, 0x71, 0x4f, 0x01, 0x02, 0x03, + 0x11, 0x12, 0x13, 0x04, 0x14, 0x05, 0x0e, 0x0f, + 0x1d, 0x1e, 0x1f, 0x10, 0x23, 0x09, 0x17, 0x07, + 0x83, 0x01, 0x00, 0x00, 0x65, 0x03, 0x0c, 0x00, + 0x10, 0x00, 0x8c, 0x0a, 0xd0, 0x8a, 0x20, 0xe0, + 0x2d, 0x10, 0x10, 0x3e, 0x96, 0x00, 0xfd, 0x1e, + 0x11, 0x00, 0x00, 0x18, 0x01, 0x1d, 0x00, 0x72, + 0x51, 0xd0, 0x1e, 0x20, 0x6e, 0x28, 0x55, 0x00, + 0xfd, 0x1e, 0x11, 0x00, 0x00, 0x1e, 0x01, 0x1d, + 0x00, 0xbc, 0x52, 0xd0, 0x1e, 0x20, 0xb8, 0x28, + 0x55, 0x40, 0xfd, 0x1e, 0x11, 0x00, 0x00, 0x1e, + 0x8c, 0x0a, 0xd0, 0x90, 0x20, 0x40, 0x31, 0x20, + 0x0c, 0x40, 0x55, 0x00, 0xfd, 0x1e, 0x11, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, +}; + +static int rk_hdmi_read_edid(struct udevice *dev, u8 *buf, int buf_size) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + u32 edid_size = HDMI_EDID_BLOCK_SIZE; + int ret; + + if (0) { + edid_size = sizeof(pre_buf); + memcpy(buf, pre_buf, edid_size); + } else { + ret = hdmi_read_edid(priv->regs, 0, buf); + if (ret) { + debug("failed to read edid.\n"); + return -1; + } + + if (buf[0x7e] != 0) { + hdmi_read_edid(priv->regs, 1, + buf + HDMI_EDID_BLOCK_SIZE); + edid_size += HDMI_EDID_BLOCK_SIZE; + } + } + + return edid_size; +} + +static int rk_hdmi_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *edid) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + struct rk3288_hdmi *regs = priv->regs; + int ret; + + debug("hdmi, mode info : clock %d hdis %d vdis %d\n", + edid->pixelclock.typ, edid->hactive.typ, edid->vactive.typ); + + hdmi_av_composer(regs, edid); + + ret = hdmi_phy_init(regs, edid->pixelclock.typ); + if (ret) + return ret; + + hdmi_enable_video_path(regs); + + hdmi_audio_fifo_reset(regs); + hdmi_audio_set_format(regs); + hdmi_audio_set_samplerate(regs, edid->pixelclock.typ); + + hdmi_video_packetize(regs); + hdmi_video_csc(regs); + hdmi_video_sample(regs); + + hdmi_clear_overflow(regs); + + return 0; +} + +static int rk_hdmi_ofdata_to_platdata(struct udevice *dev) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + + priv->regs = (struct rk3288_hdmi *)dev_get_addr(dev); + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + + return 0; +} + +static int rk_hdmi_probe(struct udevice *dev) +{ + struct display_plat *uc_plat = dev_get_uclass_platdata(dev); + struct rk_hdmi_priv *priv = dev_get_priv(dev); + struct udevice *reg, *clk; + int periph; + int ret; + int vop_id = uc_plat->source_id; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret >= 0) { + periph = ret; + ret = clk_set_periph_rate(clk, periph, 0); + } + if (ret) { + debug("%s: Failed to set EDP clock: ret=%d\n", __func__, ret); + return ret; + } + + /* + * Configure the maximum clock to permit whatever resolution the + * monitor wants + */ + ret = clk_get_by_index(uc_plat->src_dev, 0, &clk); + if (ret >= 0) { + periph = ret; + ret = clk_set_periph_rate(clk, periph, 384000000); + } + if (ret < 0) { + debug("%s: Failed to set clock in source device '%s': ret=%d\n", + __func__, uc_plat->src_dev->name, ret); + return ret; + } + + ret = regulator_get_by_platname("vcc50_hdmi", ®); + if (!ret) + ret = regulator_set_enable(reg, true); + if (ret) + debug("%s: Cannot set regulator vcc50_hdmi\n", __func__); + + /* hdmi source select hdmi controller */ + rk_setreg(&priv->grf->soc_con6, 1 << 15); + + /* hdmi data from vop id */ + rk_setreg(&priv->grf->soc_con6, (vop_id == 1) ? (1 << 4) : (1 << 4)); + + ret = hdmi_wait_for_hpd(priv->regs); + if (ret < 0) { + debug("hdmi can not get hpd signal\n"); + return -1; + } + + hdmi_init_interrupt(priv->regs); + + return 0; +} + +static const struct dm_display_ops rk_hdmi_ops = { + .read_edid = rk_hdmi_read_edid, + .enable = rk_hdmi_enable, +}; + +static const struct udevice_id rk_hdmi_ids[] = { + { .compatible = "rockchip,rk3288-dw-hdmi" }, + { } +}; + +U_BOOT_DRIVER(hdmi_rockchip) = { + .name = "hdmi_rockchip", + .id = UCLASS_DISPLAY, + .of_match = rk_hdmi_ids, + .ops = &rk_hdmi_ops, + .ofdata_to_platdata = rk_hdmi_ofdata_to_platdata, + .probe = rk_hdmi_probe, + .priv_auto_alloc_size = sizeof(struct rk_hdmi_priv), +}; diff --git a/drivers/video/rockchip/rk_vop.c b/drivers/video/rockchip/rk_vop.c new file mode 100644 index 0000000..adbc68f --- /dev/null +++ b/drivers/video/rockchip/rk_vop.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <edid.h> +#include <regmap.h> +#include <syscon.h> +#include <video.h> +#include <asm/gpio.h> +#include <asm/hardware.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/edp_rk3288.h> +#include <asm/arch/hdmi_rk3288.h> +#include <asm/arch/vop_rk3288.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <dt-bindings/clock/rk3288-cru.h> +#include <power/regulator.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct rk_vop_priv { + struct rk3288_vop *regs; + struct rk3288_grf *grf; +}; + +void rkvop_enable(struct rk3288_vop *regs, ulong fbbase, + int fb_bits_per_pixel, const struct display_timing *edid) +{ + u32 lb_mode; + u32 rgb_mode; + u32 hactive = edid->hactive.typ; + u32 vactive = edid->vactive.typ; + + writel(V_ACT_WIDTH(hactive - 1) | V_ACT_HEIGHT(vactive - 1), + ®s->win0_act_info); + + writel(V_DSP_XST(edid->hsync_len.typ + edid->hback_porch.typ) | + V_DSP_YST(edid->vsync_len.typ + edid->vback_porch.typ), + ®s->win0_dsp_st); + + writel(V_DSP_WIDTH(hactive - 1) | + V_DSP_HEIGHT(vactive - 1), + ®s->win0_dsp_info); + + clrsetbits_le32(®s->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR, + V_WIN0_KEY_EN(0) | V_WIN0_KEY_COLOR(0)); + + switch (fb_bits_per_pixel) { + case 16: + rgb_mode = RGB565; + writel(V_RGB565_VIRWIDTH(hactive), ®s->win0_vir); + break; + case 24: + rgb_mode = RGB888; + writel(V_RGB888_VIRWIDTH(hactive), ®s->win0_vir); + break; + case 32: + default: + rgb_mode = ARGB8888; + writel(V_ARGB888_VIRWIDTH(hactive), ®s->win0_vir); + break; + } + + if (hactive > 2560) + lb_mode = LB_RGB_3840X2; + else if (hactive > 1920) + lb_mode = LB_RGB_2560X4; + else if (hactive > 1280) + lb_mode = LB_RGB_1920X5; + else + lb_mode = LB_RGB_1280X8; + + clrsetbits_le32(®s->win0_ctrl0, + M_WIN0_LB_MODE | M_WIN0_DATA_FMT | M_WIN0_EN, + V_WIN0_LB_MODE(lb_mode) | V_WIN0_DATA_FMT(rgb_mode) | + V_WIN0_EN(1)); + + writel(fbbase, ®s->win0_yrgb_mst); + writel(0x01, ®s->reg_cfg_done); /* enable reg config */ +} + +void rkvop_mode_set(struct rk3288_vop *regs, + const struct display_timing *edid, enum vop_modes mode) +{ + u32 hactive = edid->hactive.typ; + u32 vactive = edid->vactive.typ; + u32 hsync_len = edid->hsync_len.typ; + u32 hback_porch = edid->hback_porch.typ; + u32 vsync_len = edid->vsync_len.typ; + u32 vback_porch = edid->vback_porch.typ; + u32 hfront_porch = edid->hfront_porch.typ; + u32 vfront_porch = edid->vfront_porch.typ; + uint flags; + + switch (mode) { + case VOP_MODE_HDMI: + clrsetbits_le32(®s->sys_ctrl, M_ALL_OUT_EN, + V_HDMI_OUT_EN(1)); + break; + case VOP_MODE_EDP: + default: + clrsetbits_le32(®s->sys_ctrl, M_ALL_OUT_EN, + V_EDP_OUT_EN(1)); + break; + } + + flags = V_DSP_OUT_MODE(15) | + V_DSP_HSYNC_POL(!!(edid->flags & DISPLAY_FLAGS_HSYNC_HIGH)) | + V_DSP_VSYNC_POL(!!(edid->flags & DISPLAY_FLAGS_VSYNC_HIGH)); + + clrsetbits_le32(®s->dsp_ctrl0, + M_DSP_OUT_MODE | M_DSP_VSYNC_POL | M_DSP_HSYNC_POL, + flags); + + writel(V_HSYNC(hsync_len) | + V_HORPRD(hsync_len + hback_porch + hactive + hfront_porch), + ®s->dsp_htotal_hs_end); + + writel(V_HEAP(hsync_len + hback_porch + hactive) | + V_HASP(hsync_len + hback_porch), + ®s->dsp_hact_st_end); + + writel(V_VSYNC(vsync_len) | + V_VERPRD(vsync_len + vback_porch + vactive + vfront_porch), + ®s->dsp_vtotal_vs_end); + + writel(V_VAEP(vsync_len + vback_porch + vactive)| + V_VASP(vsync_len + vback_porch), + ®s->dsp_vact_st_end); + + writel(V_HEAP(hsync_len + hback_porch + hactive) | + V_HASP(hsync_len + hback_porch), + ®s->post_dsp_hact_info); + + writel(V_VAEP(vsync_len + vback_porch + vactive)| + V_VASP(vsync_len + vback_porch), + ®s->post_dsp_vact_info); + + writel(0x01, ®s->reg_cfg_done); /* enable reg config */ +} + +/** + * rk_display_init() - Try to enable the given display device + * + * This function performs many steps: + * - Finds the display device being referenced by @ep_node + * - Puts the VOP's ID into its uclass platform data + * - Probes the device to set it up + * - Reads the EDID timing information + * - Sets up the VOP clocks, etc. for the selected pixel clock and display mode + * - Enables the display (the display device handles this and will do different + * things depending on the display type) + * - Tells the uclass about the display resolution so that the console will + * appear correctly + * + * @dev: VOP device that we want to connect to the display + * @fbbase: Frame buffer address + * @l2bpp Log2 of bits-per-pixels for the display + * @ep_node: Device tree node to process - this is the offset of an endpoint + * node within the VOP's 'port' list. + * @return 0 if OK, -ve if something went wrong + */ +int rk_display_init(struct udevice *dev, ulong fbbase, + enum video_log2_bpp l2bpp, int ep_node) +{ + struct video_priv *uc_priv = dev_get_uclass_priv(dev); + const void *blob = gd->fdt_blob; + struct rk_vop_priv *priv = dev_get_priv(dev); + int vop_id, remote_vop_id; + struct rk3288_vop *regs = priv->regs; + struct display_timing timing; + struct udevice *disp; + int ret, remote, i, offset; + struct display_plat *disp_uc_plat; + struct udevice *clk; + + vop_id = fdtdec_get_int(blob, ep_node, "reg", -1); + debug("vop_id=%d\n", vop_id); + remote = fdtdec_lookup_phandle(blob, ep_node, "remote-endpoint"); + if (remote < 0) + return -EINVAL; + remote_vop_id = fdtdec_get_int(blob, remote, "reg", -1); + debug("remote vop_id=%d\n", remote_vop_id); + + for (i = 0, offset = remote; i < 3 && offset > 0; i++) + offset = fdt_parent_offset(blob, offset); + if (offset < 0) { + debug("%s: Invalid remote-endpoint position\n", dev->name); + return -EINVAL; + } + + ret = uclass_find_device_by_of_offset(UCLASS_DISPLAY, offset, &disp); + if (ret) { + debug("%s: device '%s' display not found (ret=%d)\n", __func__, + dev->name, ret); + return ret; + } + + disp_uc_plat = dev_get_uclass_platdata(disp); + debug("Found device '%s', disp_uc_priv=%p\n", disp->name, disp_uc_plat); + disp_uc_plat->source_id = remote_vop_id; + disp_uc_plat->src_dev = dev; + + ret = device_probe(disp); + if (ret) { + debug("%s: device '%s' display won't probe (ret=%d)\n", + __func__, dev->name, ret); + return ret; + } + + ret = display_read_timing(disp, &timing); + if (ret) { + debug("%s: Failed to read timings\n", __func__); + return ret; + } + + ret = rkclk_get_clk(CLK_NEW, &clk); + if (!ret) { + ret = clk_set_periph_rate(clk, DCLK_VOP0 + vop_id, + timing.pixelclock.typ); + } + if (ret) { + debug("%s: Failed to set pixel clock: ret=%d\n", __func__, ret); + return ret; + } + + rkvop_mode_set(regs, &timing, vop_id); + + rkvop_enable(regs, fbbase, 1 << l2bpp, &timing); + + ret = display_enable(disp, 1 << l2bpp, &timing); + if (ret) + return ret; + + uc_priv->xsize = timing.hactive.typ; + uc_priv->ysize = timing.vactive.typ; + uc_priv->bpix = l2bpp; + debug("fb=%lx, size=%d %d\n", fbbase, uc_priv->xsize, uc_priv->ysize); + + return 0; +} + +static int rk_vop_probe(struct udevice *dev) +{ + struct video_uc_platdata *plat = dev_get_uclass_platdata(dev); + const void *blob = gd->fdt_blob; + struct rk_vop_priv *priv = dev_get_priv(dev); + struct udevice *reg; + int ret, port, node; + + /* Before relocation we don't need to do anything */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + priv->regs = (struct rk3288_vop *)dev_get_addr(dev); + + /* lcdc(vop) iodomain select 1.8V */ + rk_setreg(&priv->grf->io_vsel, 1 << 0); + + /* + * Try some common regulators. We should really get these from the + * device tree somehow. + */ + ret = regulator_autoset_by_name("vcc18_lcd", ®); + if (ret) + debug("%s: Cannot autoset regulator vcc18_lcd\n", __func__); + ret = regulator_autoset_by_name("VCC18_LCD", ®); + if (ret) + debug("%s: Cannot autoset regulator VCC18_LCD\n", __func__); + ret = regulator_autoset_by_name("vdd10_lcd_pwren_h", ®); + if (ret) { + debug("%s: Cannot autoset regulator vdd10_lcd_pwren_h\n", + __func__); + } + ret = regulator_autoset_by_name("vdd10_lcd", ®); + if (ret) { + debug("%s: Cannot autoset regulator vdd10_lcd\n", + __func__); + } + ret = regulator_autoset_by_name("VDD10_LCD", ®); + if (ret) { + debug("%s: Cannot autoset regulator VDD10_LCD\n", + __func__); + } + ret = regulator_autoset_by_name("vcc33_lcd", ®); + if (ret) + debug("%s: Cannot autoset regulator vcc33_lcd\n", __func__); + + /* + * Try all the ports until we find one that works. In practice this + * tries EDP first if available, then HDMI. + */ + port = fdt_subnode_offset(blob, dev->of_offset, "port"); + if (port < 0) + return -EINVAL; + for (node = fdt_first_subnode(blob, port); + node > 0; + node = fdt_next_subnode(blob, node)) { + ret = rk_display_init(dev, plat->base, VIDEO_BPP16, node); + if (ret) + debug("Device failed: ret=%d\n", ret); + if (!ret) + break; + } + + return ret; +} + +static int rk_vop_bind(struct udevice *dev) +{ + struct video_uc_platdata *plat = dev_get_uclass_platdata(dev); + + plat->size = 1920 * 1080 * 2; + + return 0; +} + +static const struct video_ops rk_vop_ops = { +}; + +static const struct udevice_id rk_vop_ids[] = { + { .compatible = "rockchip,rk3288-vop" }, + { } +}; + +U_BOOT_DRIVER(rk_vop) = { + .name = "rk_vop", + .id = UCLASS_VIDEO, + .of_match = rk_vop_ids, + .ops = &rk_vop_ops, + .bind = rk_vop_bind, + .probe = rk_vop_probe, + .priv_auto_alloc_size = sizeof(struct rk_vop_priv), +}; diff --git a/drivers/video/simple_panel.c b/drivers/video/simple_panel.c new file mode 100644 index 0000000..b161517 --- /dev/null +++ b/drivers/video/simple_panel.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <backlight.h> +#include <dm.h> +#include <panel.h> +#include <asm/gpio.h> +#include <power/regulator.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct simple_panel_priv { + struct udevice *reg; + struct udevice *backlight; + struct gpio_desc enable; +}; + +static int simple_panel_enable_backlight(struct udevice *dev) +{ + struct simple_panel_priv *priv = dev_get_priv(dev); + int ret; + + dm_gpio_set_value(&priv->enable, 1); + ret = backlight_enable(priv->backlight); + if (ret) + return ret; + + return 0; +} + +static int simple_panel_ofdata_to_platdata(struct udevice *dev) +{ + struct simple_panel_priv *priv = dev_get_priv(dev); + int ret; + + ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev, + "power-supply", &priv->reg); + if (ret) { + debug("%s: Warning: cnnot get power supply: ret=%d\n", + __func__, ret); + if (ret != -ENOENT) + return ret; + } + ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, + "backlight", &priv->backlight); + if (ret) { + debug("%s: Cannot get backlight: ret=%d\n", __func__, ret); + return ret; + } + ret = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable, + GPIOD_IS_OUT); + if (ret) { + debug("%s: Warning: cannot get enable GPIO: ret=%d\n", + __func__, ret); + if (ret != -ENOENT) + return ret; + } + + return 0; +} + +static int simple_panel_probe(struct udevice *dev) +{ + struct simple_panel_priv *priv = dev_get_priv(dev); + int ret; + + if (priv->reg) { + debug("%s: Enable regulator '%s'\n", __func__, priv->reg->name); + ret = regulator_set_enable(priv->reg, true); + if (ret) + return ret; + } + + return 0; +} + +static const struct panel_ops simple_panel_ops = { + .enable_backlight = simple_panel_enable_backlight, +}; + +static const struct udevice_id simple_panel_ids[] = { + { .compatible = "simple-panel" }, + { } +}; + +U_BOOT_DRIVER(simple_panel) = { + .name = "simple_panel", + .id = UCLASS_PANEL, + .of_match = simple_panel_ids, + .ops = &simple_panel_ops, + .ofdata_to_platdata = simple_panel_ofdata_to_platdata, + .probe = simple_panel_probe, + .priv_auto_alloc_size = sizeof(struct simple_panel_priv), +}; diff --git a/drivers/video/tegra124/display.c b/drivers/video/tegra124/display.c index 7179dbf..610ffa9 100644 --- a/drivers/video/tegra124/display.c +++ b/drivers/video/tegra124/display.c @@ -10,7 +10,7 @@ #include <dm.h> #include <edid.h> #include <errno.h> -#include <displayport.h> +#include <display.h> #include <edid.h> #include <fdtdec.h> #include <lcd.h> @@ -324,20 +324,12 @@ static int display_update_config_from_edid(struct udevice *dp_dev, int *panel_bppp, struct display_timing *timing) { - u8 buf[EDID_SIZE]; - int bpc, ret; + int ret; - ret = display_port_read_edid(dp_dev, buf, sizeof(buf)); - if (ret < 0) - return ret; - ret = edid_get_timing(buf, ret, timing, &bpc); + ret = display_read_timing(dp_dev, timing); if (ret) return ret; - /* Use this information if valid */ - if (bpc != -1) - *panel_bppp = bpc * 3; - return 0; } @@ -398,7 +390,7 @@ int display_init(void *lcdbase, int fb_bits_per_pixel, int node; int ret; - ret = uclass_get_device(UCLASS_DISPLAY_PORT, 0, &dp_dev); + ret = uclass_get_device(UCLASS_DISPLAY, 0, &dp_dev); if (ret) return ret; @@ -450,7 +442,7 @@ int display_init(void *lcdbase, int fb_bits_per_pixel, } /* Enable dp */ - ret = display_port_enable(dp_dev, panel_bpp, timing); + ret = display_enable(dp_dev, panel_bpp, timing); if (ret) return ret; diff --git a/drivers/video/tegra124/dp.c b/drivers/video/tegra124/dp.c index 1bf9202..bb1805a 100644 --- a/drivers/video/tegra124/dp.c +++ b/drivers/video/tegra124/dp.c @@ -6,16 +6,17 @@ */ #include <common.h> -#include <displayport.h> +#include <display.h> #include <dm.h> #include <div64.h> #include <errno.h> #include <fdtdec.h> #include <asm/io.h> #include <asm/arch-tegra/dc.h> -#include "displayport.h" +#include "display.h" #include "edid.h" #include "sor.h" +#include "displayport.h" DECLARE_GLOBAL_DATA_PTR; @@ -1573,7 +1574,7 @@ static int tegra_dp_read_edid(struct udevice *dev, u8 *buf, int buf_size) buf_size, &aux_stat); } -static const struct dm_display_port_ops dp_tegra_ops = { +static const struct dm_display_ops dp_tegra_ops = { .read_edid = tegra_dp_read_edid, .enable = tegra_dp_enable, }; @@ -1596,7 +1597,7 @@ static const struct udevice_id tegra_dp_ids[] = { U_BOOT_DRIVER(dp_tegra) = { .name = "dpaux_tegra", - .id = UCLASS_DISPLAY_PORT, + .id = UCLASS_DISPLAY, .of_match = tegra_dp_ids, .ofdata_to_platdata = tegra_dp_ofdata_to_platdata, .probe = dp_tegra_probe, diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index ea10189..706a189 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -161,7 +161,12 @@ static int vidconsole_post_probe(struct udevice *dev) struct stdio_dev *sdev = &priv->sdev; int ret; - strlcpy(sdev->name, dev->name, sizeof(sdev->name)); + if (dev->seq) { + snprintf(sdev->name, sizeof(sdev->name), "vidconsole%d", + dev->seq); + } else { + strcpy(sdev->name, "vidconsole"); + } sdev->flags = DEV_FLAGS_OUTPUT; sdev->putc = vidconsole_putc; sdev->puts = vidconsole_puts; diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 63d0d9d..24d537e 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -42,6 +42,13 @@ */ DECLARE_GLOBAL_DATA_PTR; +void video_set_flush_dcache(struct udevice *dev, bool flush) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + + priv->flush_dcache = flush; +} + static ulong alloc_fb(struct udevice *dev, ulong *addrp) { struct video_uc_platdata *plat = dev_get_uclass_platdata(dev); |