diff options
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/Kconfig | 23 | ||||
-rw-r--r-- | drivers/video/Makefile | 8 | ||||
-rw-r--r-- | drivers/video/backlight-uclass.c | 25 | ||||
-rw-r--r-- | drivers/video/bridge/video-bridge-uclass.c | 11 | ||||
-rw-r--r-- | drivers/video/display-uclass.c | 52 | ||||
-rw-r--r-- | drivers/video/dp-uclass.c | 34 | ||||
-rw-r--r-- | drivers/video/panel-uclass.c | 25 | ||||
-rw-r--r-- | drivers/video/pwm_backlight.c | 134 | ||||
-rw-r--r-- | drivers/video/rockchip/Makefile | 8 | ||||
-rw-r--r-- | drivers/video/rockchip/rk_edp.c | 1085 | ||||
-rw-r--r-- | drivers/video/rockchip/rk_hdmi.c | 933 | ||||
-rw-r--r-- | drivers/video/rockchip/rk_vop.c | 346 | ||||
-rw-r--r-- | drivers/video/simple_panel.c | 99 | ||||
-rw-r--r-- | drivers/video/tegra124/display.c | 18 | ||||
-rw-r--r-- | drivers/video/tegra124/dp.c | 9 | ||||
-rw-r--r-- | drivers/video/vidconsole-uclass.c | 7 | ||||
-rw-r--r-- | drivers/video/video-uclass.c | 7 |
17 files changed, 2762 insertions, 62 deletions
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); |