diff options
| author | Svyatoslav Ryhel <[email protected]> | 2025-03-29 16:49:53 +0200 |
|---|---|---|
| committer | Svyatoslav Ryhel <[email protected]> | 2025-04-12 09:42:36 +0300 |
| commit | 901f249fdb6b0ab287ac23dbdcada44c4c697dca (patch) | |
| tree | ba682147bb768803a3a18abaf724ccc143ec52d6 /drivers/video/tegra | |
| parent | e69eeffb59c9cc338dcc518450f370aebe5a7374 (diff) | |
video: rename tegra20 to tegra
Since this set of drivers suports four Tegra SoC generations, lets name it
just 'tegra'.
Signed-off-by: Svyatoslav Ryhel <[email protected]>
Reviewed-by: Peter Robinson <[email protected]>
Diffstat (limited to 'drivers/video/tegra')
| -rw-r--r-- | drivers/video/tegra/Kconfig | 38 | ||||
| -rw-r--r-- | drivers/video/tegra/Makefile | 7 | ||||
| -rw-r--r-- | drivers/video/tegra/mipi-phy.c | 133 | ||||
| -rw-r--r-- | drivers/video/tegra/mipi-phy.h | 48 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-dc.c | 682 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-dc.h | 40 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-dsi.c | 1148 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-dsi.h | 246 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-hdmi.c | 623 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-hdmi.h | 648 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-host1x.c | 86 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-mipi.c | 294 | ||||
| -rw-r--r-- | drivers/video/tegra/tegra-pwm-backlight.c | 150 |
13 files changed, 4143 insertions, 0 deletions
diff --git a/drivers/video/tegra/Kconfig b/drivers/video/tegra/Kconfig new file mode 100644 index 00000000000..598f9ea1f21 --- /dev/null +++ b/drivers/video/tegra/Kconfig @@ -0,0 +1,38 @@ +config HOST1X_TEGRA + bool "NVIDIA Tegra host1x BUS support" + depends on SIMPLE_BUS + +config VIDEO_TEGRA20 + bool "Enable Display Controller support on Tegra20 and Tegra 30" + depends on OF_CONTROL + select HOST1X_TEGRA + help + T20/T30 support video output to an attached LCD panel as well as + other options such as HDMI. Only the LCD is supported in U-Boot. + This option enables this support which can be used on devices which + have an LCD display connected. + +config VIDEO_DSI_TEGRA30 + bool "Enable Tegra 30 DSI support" + depends on VIDEO_BRIDGE && PANEL && DM_GPIO + select VIDEO_TEGRA20 + select VIDEO_MIPI_DSI + help + T30 has native support for DSI panels. This option enables support + for such panels which can be used on endeavoru and tf600t. + +config VIDEO_HDMI_TEGRA + bool "Enable Tegra HDMI support" + depends on VIDEO_BRIDGE && DM_I2C + select I2C_EDID + select VIDEO_TEGRA20 + help + Tegra has native support for HDMI. This option enables support + for such connection and can be used for any supported device. + +config TEGRA_BACKLIGHT_PWM + bool "Enable Tegra DC PWM backlight support" + depends on BACKLIGHT + select VIDEO_TEGRA20 + help + Tegra DC dependent backlight. diff --git a/drivers/video/tegra/Makefile b/drivers/video/tegra/Makefile new file mode 100644 index 00000000000..78521405749 --- /dev/null +++ b/drivers/video/tegra/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_HOST1X_TEGRA) += tegra-host1x.o +obj-$(CONFIG_VIDEO_TEGRA20) += tegra-dc.o +obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o tegra-mipi.o mipi-phy.o +obj-$(CONFIG_VIDEO_HDMI_TEGRA) += tegra-hdmi.o +obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += tegra-pwm-backlight.o diff --git a/drivers/video/tegra/mipi-phy.c b/drivers/video/tegra/mipi-phy.c new file mode 100644 index 00000000000..576262e405d --- /dev/null +++ b/drivers/video/tegra/mipi-phy.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 NVIDIA Corporation + */ + +#include <linux/err.h> + +#include "mipi-phy.h" + +/* + * Default D-PHY timings based on MIPI D-PHY specification. Derived from the + * valid ranges specified in Section 6.9, Table 14, Page 40 of the D-PHY + * specification (v1.2) with minor adjustments. + */ +int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, + unsigned long period) +{ + timing->clkmiss = 0; + timing->clkpost = 70 + 52 * period; + timing->clkpre = 8; + timing->clkprepare = 65; + timing->clksettle = 95; + timing->clktermen = 0; + timing->clktrail = 80; + timing->clkzero = 260; + timing->dtermen = 0; + timing->eot = 0; + timing->hsexit = 120; + timing->hsprepare = 65 + 5 * period; + timing->hszero = 145 + 5 * period; + timing->hssettle = 85 + 6 * period; + timing->hsskip = 40; + + /* + * The MIPI D-PHY specification (Section 6.9, v1.2, Table 14, Page 40) + * contains this formula as: + * + * T_HS-TRAIL = max(n * 8 * period, 60 + n * 4 * period) + * + * where n = 1 for forward-direction HS mode and n = 4 for reverse- + * direction HS mode. There's only one setting and this function does + * not parameterize on anything other that period, so this code will + * assumes that reverse-direction HS mode is supported and uses n = 4. + */ + timing->hstrail = max(4 * 8 * period, 60 + 4 * 4 * period); + + timing->init = 100000; + timing->lpx = 60; + timing->taget = 5 * timing->lpx; + timing->tago = 4 * timing->lpx; + timing->tasure = 2 * timing->lpx; + timing->wakeup = 1000000; + + return 0; +} + +/* + * Validate D-PHY timing according to MIPI D-PHY specification + * (v1.2, Section 6.9 "Global Operation Timing Parameters"). + */ +int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, + unsigned long period) +{ + if (timing->clkmiss > 60) + return -EINVAL; + + if (timing->clkpost < (60 + 52 * period)) + return -EINVAL; + + if (timing->clkpre < 8) + return -EINVAL; + + if (timing->clkprepare < 38 || timing->clkprepare > 95) + return -EINVAL; + + if (timing->clksettle < 95 || timing->clksettle > 300) + return -EINVAL; + + if (timing->clktermen > 38) + return -EINVAL; + + if (timing->clktrail < 60) + return -EINVAL; + + if (timing->clkprepare + timing->clkzero < 300) + return -EINVAL; + + if (timing->dtermen > 35 + 4 * period) + return -EINVAL; + + if (timing->eot > 105 + 12 * period) + return -EINVAL; + + if (timing->hsexit < 100) + return -EINVAL; + + if (timing->hsprepare < 40 + 4 * period || + timing->hsprepare > 85 + 6 * period) + return -EINVAL; + + if (timing->hsprepare + timing->hszero < 145 + 10 * period) + return -EINVAL; + + if ((timing->hssettle < 85 + 6 * period) || + (timing->hssettle > 145 + 10 * period)) + return -EINVAL; + + if (timing->hsskip < 40 || timing->hsskip > 55 + 4 * period) + return -EINVAL; + + if (timing->hstrail < max(8 * period, 60 + 4 * period)) + return -EINVAL; + + if (timing->init < 100000) + return -EINVAL; + + if (timing->lpx < 50) + return -EINVAL; + + if (timing->taget != 5 * timing->lpx) + return -EINVAL; + + if (timing->tago != 4 * timing->lpx) + return -EINVAL; + + if (timing->tasure < timing->lpx || timing->tasure > 2 * timing->lpx) + return -EINVAL; + + if (timing->wakeup < 1000000) + return -EINVAL; + + return 0; +} diff --git a/drivers/video/tegra/mipi-phy.h b/drivers/video/tegra/mipi-phy.h new file mode 100644 index 00000000000..41889a75035 --- /dev/null +++ b/drivers/video/tegra/mipi-phy.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 NVIDIA Corporation + */ + +#ifndef DRM_TEGRA_MIPI_PHY_H +#define DRM_TEGRA_MIPI_PHY_H + +/* + * D-PHY timing parameters + * + * A detailed description of these parameters can be found in the MIPI + * Alliance Specification for D-PHY, Section 5.9 "Global Operation Timing + * Parameters". + * + * All parameters are specified in nanoseconds. + */ +struct mipi_dphy_timing { + unsigned int clkmiss; + unsigned int clkpost; + unsigned int clkpre; + unsigned int clkprepare; + unsigned int clksettle; + unsigned int clktermen; + unsigned int clktrail; + unsigned int clkzero; + unsigned int dtermen; + unsigned int eot; + unsigned int hsexit; + unsigned int hsprepare; + unsigned int hszero; + unsigned int hssettle; + unsigned int hsskip; + unsigned int hstrail; + unsigned int init; + unsigned int lpx; + unsigned int taget; + unsigned int tago; + unsigned int tasure; + unsigned int wakeup; +}; + +int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, + unsigned long period); +int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, + unsigned long period); + +#endif diff --git a/drivers/video/tegra/tegra-dc.c b/drivers/video/tegra/tegra-dc.c new file mode 100644 index 00000000000..1f43153ff27 --- /dev/null +++ b/drivers/video/tegra/tegra-dc.c @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * Copyright (c) 2024 Svyatoslav Ryhel <[email protected]> + */ + +#include <backlight.h> +#include <cpu_func.h> +#include <clk.h> +#include <dm.h> +#include <dm/ofnode_graph.h> +#include <fdtdec.h> +#include <log.h> +#include <panel.h> +#include <video.h> +#include <video_bridge.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/powergate.h> + +#include "tegra-dc.h" + +/* Holder of Tegra per-SOC DC differences */ +struct tegra_dc_soc_info { + bool has_timer; + bool has_rgb; + bool has_pgate; +}; + +/* Information about the display controller */ +struct tegra_lcd_priv { + int width; /* width in pixels */ + int height; /* height in pixels */ + enum video_log2_bpp log2_bpp; /* colour depth */ + struct display_timing timing; + struct udevice *panel; /* Panels attached to RGB */ + struct udevice *bridge; /* Bridge linked with DC */ + struct dc_ctlr *dc; /* Display controller regmap */ + const struct tegra_dc_soc_info *soc; + fdt_addr_t frame_buffer; /* Address of frame buffer */ + unsigned pixel_clock; /* Pixel clock in Hz */ + struct clk *clk; + struct clk *clk_parent; + ulong scdiv; /* Clock divider used by disp_clk_ctrl */ + bool rotation; /* 180 degree panel turn */ + int pipe; /* DC controller: 0 for A, 1 for B */ +}; + +enum { + /* Maximum LCD size we support */ + LCD_MAX_WIDTH = 2560, + LCD_MAX_HEIGHT = 1600, + LCD_MAX_LOG2_BPP = VIDEO_BPP16, +}; + +static void update_window(struct tegra_lcd_priv *priv, + struct disp_ctl_win *win) +{ + struct dc_ctlr *dc = priv->dc; + unsigned h_dda, v_dda; + unsigned long val; + + val = readl(&dc->cmd.disp_win_header); + val |= WINDOW_A_SELECT; + writel(val, &dc->cmd.disp_win_header); + + writel(win->fmt, &dc->win.color_depth); + + clrsetbits_le32(&dc->win.byte_swap, BYTE_SWAP_MASK, + BYTE_SWAP_NOSWAP << BYTE_SWAP_SHIFT); + + val = win->out_x << H_POSITION_SHIFT; + val |= win->out_y << V_POSITION_SHIFT; + writel(val, &dc->win.pos); + + val = win->out_w << H_SIZE_SHIFT; + val |= win->out_h << V_SIZE_SHIFT; + writel(val, &dc->win.size); + + val = (win->w * win->bpp / 8) << H_PRESCALED_SIZE_SHIFT; + val |= win->h << V_PRESCALED_SIZE_SHIFT; + writel(val, &dc->win.prescaled_size); + + writel(0, &dc->win.h_initial_dda); + writel(0, &dc->win.v_initial_dda); + + h_dda = (win->w * 0x1000) / max(win->out_w - 1, 1U); + v_dda = (win->h * 0x1000) / max(win->out_h - 1, 1U); + + val = h_dda << H_DDA_INC_SHIFT; + val |= v_dda << V_DDA_INC_SHIFT; + writel(val, &dc->win.dda_increment); + + writel(win->stride, &dc->win.line_stride); + writel(0, &dc->win.buf_stride); + + val = WIN_ENABLE; + if (win->bpp < 24) + val |= COLOR_EXPAND; + + if (priv->rotation) + val |= H_DIRECTION | V_DIRECTION; + + writel(val, &dc->win.win_opt); + + writel((unsigned long)win->phys_addr, &dc->winbuf.start_addr); + writel(win->x, &dc->winbuf.addr_h_offset); + writel(win->y, &dc->winbuf.addr_v_offset); + + writel(0xff00, &dc->win.blend_nokey); + writel(0xff00, &dc->win.blend_1win); + + val = GENERAL_ACT_REQ | WIN_A_ACT_REQ; + val |= GENERAL_UPDATE | WIN_A_UPDATE; + writel(val, &dc->cmd.state_ctrl); +} + +static int update_display_mode(struct tegra_lcd_priv *priv) +{ + struct dc_disp_reg *disp = &priv->dc->disp; + struct display_timing *dt = &priv->timing; + unsigned long val; + + writel(0x0, &disp->disp_timing_opt); + + writel(1 | 1 << 16, &disp->ref_to_sync); + writel(dt->hsync_len.typ | dt->vsync_len.typ << 16, &disp->sync_width); + writel(dt->hback_porch.typ | dt->vback_porch.typ << 16, + &disp->back_porch); + writel((dt->hfront_porch.typ - 1) | (dt->vfront_porch.typ - 1) << 16, + &disp->front_porch); + writel(dt->hactive.typ | (dt->vactive.typ << 16), &disp->disp_active); + + if (priv->soc->has_rgb) { + val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT; + val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT; + writel(val, &disp->data_enable_opt); + + val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT; + val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT; + val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT; + writel(val, &disp->disp_interface_ctrl); + + writel(0x00010001, &disp->shift_clk_opt); + } + + val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT; + val |= priv->scdiv << SHIFT_CLK_DIVIDER_SHIFT; + writel(val, &disp->disp_clk_ctrl); + + return 0; +} + +/* Start up the display and turn on power to PWMs */ +static void basic_init(struct dc_cmd_reg *cmd) +{ + u32 val; + + writel(0x00000100, &cmd->gen_incr_syncpt_ctrl); + writel(0x0000011a, &cmd->cont_syncpt_vsync); + writel(0x00000000, &cmd->int_type); + writel(0x00000000, &cmd->int_polarity); + writel(0x00000000, &cmd->int_mask); + writel(0x00000000, &cmd->int_enb); + + val = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE; + val |= PW3_ENABLE | PW4_ENABLE | PM0_ENABLE; + val |= PM1_ENABLE; + writel(val, &cmd->disp_pow_ctrl); + + val = readl(&cmd->disp_cmd); + val &= ~CTRL_MODE_MASK; + val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT; + writel(val, &cmd->disp_cmd); +} + +static void basic_init_timer(struct dc_disp_reg *disp) +{ + writel(0x00000020, &disp->mem_high_pri); + writel(0x00000001, &disp->mem_high_pri_timer); +} + +static const u32 rgb_enb_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_polarity_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x01000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_data_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00210222, + 0x00002200, + 0x00020000, +}; + +static void rgb_enable(struct tegra_lcd_priv *priv) +{ + struct dc_com_reg *com = &priv->dc->com; + struct display_timing *dt = &priv->timing; + u32 value; + int i; + + for (i = 0; i < PIN_REG_COUNT; i++) { + writel(rgb_enb_tab[i], &com->pin_output_enb[i]); + writel(rgb_polarity_tab[i], &com->pin_output_polarity[i]); + writel(rgb_data_tab[i], &com->pin_output_data[i]); + } + + /* configure H- and V-sync signal polarities */ + value = readl(&com->pin_output_polarity[1]); + + if (dt->flags & DISPLAY_FLAGS_HSYNC_LOW) + value |= LHS_OUTPUT_POLARITY_LOW; + else + value &= ~LHS_OUTPUT_POLARITY_LOW; + + if (dt->flags & DISPLAY_FLAGS_VSYNC_LOW) + value |= LVS_OUTPUT_POLARITY_LOW; + else + value &= ~LVS_OUTPUT_POLARITY_LOW; + + writel(value, &com->pin_output_polarity[1]); + + for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++) + writel(rgb_sel_tab[i], &com->pin_output_sel[i]); +} + +static int setup_window(struct tegra_lcd_priv *priv, + struct disp_ctl_win *win) +{ + if (priv->rotation) { + win->x = priv->width * 2 - 1; + win->y = priv->height - 1; + } else { + win->x = 0; + win->y = 0; + } + + win->w = priv->width; + win->h = priv->height; + win->out_x = 0; + win->out_y = 0; + win->out_w = priv->width; + win->out_h = priv->height; + win->phys_addr = priv->frame_buffer; + win->stride = priv->width * (1 << priv->log2_bpp) / 8; + + log_debug("%s: depth = %d\n", __func__, priv->log2_bpp); + + switch (priv->log2_bpp) { + case VIDEO_BPP32: + win->fmt = COLOR_DEPTH_R8G8B8A8; + win->bpp = 32; + break; + case VIDEO_BPP16: + win->fmt = COLOR_DEPTH_B5G6R5; + win->bpp = 16; + break; + + default: + log_debug("Unsupported LCD bit depth\n"); + return -1; + } + + return 0; +} + +/** + * Register a new display based on device tree configuration. + * + * The frame buffer can be positioned by U-Boot or overridden by the fdt. + * You should pass in the U-Boot address here, and check the contents of + * struct tegra_lcd_priv to see what was actually chosen. + * + * @param priv Driver's private data + * @param default_lcd_base Default address of LCD frame buffer + * Return: 0 if ok, -1 on error (unsupported bits per pixel) + */ +static int tegra_display_probe(struct tegra_lcd_priv *priv, + void *default_lcd_base) +{ + struct disp_ctl_win window; + unsigned long rate = clk_get_rate(priv->clk_parent); + int ret; + + priv->frame_buffer = (u32)default_lcd_base; + + /* + * We halve the rate if DISP1 parent is PLLD, since actual parent + * is plld_out0 which is PLLD divided by 2. + */ + if (priv->clk_parent->id == CLOCK_ID_DISPLAY || + priv->clk_parent->id == CLOCK_ID_DISPLAY2) + rate /= 2; + + /* + * The pixel clock divider is in 7.1 format (where the bottom bit + * represents 0.5). Here we calculate the divider needed to get from + * the display clock (typically 600MHz) to the pixel clock. We round + * up or down as required. + */ + if (!priv->scdiv) + priv->scdiv = ((rate * 2 + priv->pixel_clock / 2) + / priv->pixel_clock) - 2; + log_debug("Display clock %lu, divider %lu\n", rate, priv->scdiv); + + clock_start_periph_pll(priv->clk->id, priv->clk_parent->id, + rate); + + basic_init(&priv->dc->cmd); + + if (priv->soc->has_timer) + basic_init_timer(&priv->dc->disp); + + if (priv->soc->has_rgb) + rgb_enable(priv); + + if (priv->pixel_clock) + update_display_mode(priv); + + ret = setup_window(priv, &window); + if (ret) + return ret; + + update_window(priv, &window); + + return 0; +} + +static int tegra_lcd_probe(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + struct video_priv *uc_priv = dev_get_uclass_priv(dev); + struct tegra_lcd_priv *priv = dev_get_priv(dev); + int ret; + + /* Initialize the Tegra display controller */ + if (priv->soc->has_pgate) { + uint powergate; + + if (priv->pipe) + powergate = TEGRA_POWERGATE_DISB; + else + powergate = TEGRA_POWERGATE_DIS; + + ret = tegra_powergate_power_off(powergate); + if (ret < 0) { + log_debug("failed to power off DISP gate: %d", ret); + return ret; + } + + ret = tegra_powergate_sequence_power_up(powergate, + priv->clk->id); + if (ret < 0) { + log_debug("failed to power up DISP gate: %d", ret); + return ret; + } + } + + /* Get shift clock divider from Tegra DSI if used */ + if (priv->bridge) { + if (!strcmp(priv->bridge->driver->name, "tegra_dsi")) { + struct tegra_dc_plat *dc_plat = dev_get_plat(priv->bridge); + + priv->scdiv = dc_plat->scdiv; + } + } + + /* Clean the framebuffer area */ + memset((u8 *)plat->base, 0, plat->size); + flush_dcache_all(); + + ret = tegra_display_probe(priv, (void *)plat->base); + if (ret) { + log_debug("%s: Failed to probe display driver\n", __func__); + return ret; + } + + if (priv->panel) { + ret = panel_enable_backlight(priv->panel); + if (ret) { + log_debug("%s: Cannot enable backlight, ret=%d\n", __func__, ret); + return ret; + } + } + + if (priv->bridge) { + ret = video_bridge_attach(priv->bridge); + if (ret) { + log_debug("%s: Cannot attach bridge, ret=%d\n", __func__, ret); + return ret; + } + } + + mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size, + DCACHE_WRITETHROUGH); + + /* Enable flushing after LCD writes if requested */ + video_set_flush_dcache(dev, true); + + uc_priv->xsize = priv->width; + uc_priv->ysize = priv->height; + uc_priv->bpix = priv->log2_bpp; + log_debug("LCD frame buffer at %08x, size %x\n", priv->frame_buffer, + plat->size); + + if (priv->panel) { + ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT); + if (ret) + return ret; + } + + if (priv->bridge) { + ret = video_bridge_set_backlight(priv->bridge, BACKLIGHT_DEFAULT); + if (ret) + return ret; + } + + return 0; +} + +static int tegra_lcd_configure_rgb(struct udevice *dev, ofnode rgb) +{ + struct tegra_lcd_priv *priv = dev_get_priv(dev); + ofnode remote; + int ret; + + /* DC can have only 1 port */ + remote = ofnode_graph_get_remote_node(rgb, -1, -1); + + ret = uclass_get_device_by_ofnode(UCLASS_PANEL, remote, &priv->panel); + if (!ret) + return 0; + + ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, remote, &priv->bridge); + if (!ret) + return 0; + + /* Try legacy method if graph did not work */ + remote = ofnode_parse_phandle(rgb, "nvidia,panel", 0); + if (!ofnode_valid(remote)) + return -EINVAL; + + ret = uclass_get_device_by_ofnode(UCLASS_PANEL, remote, &priv->panel); + if (ret) { + log_debug("%s: Cannot find panel for '%s' (ret=%d)\n", + __func__, dev->name, ret); + + ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, remote, + &priv->bridge); + if (ret) { + log_err("%s: Cannot find panel or bridge for '%s' (ret=%d)\n", + __func__, dev->name, ret); + return ret; + } + } + + return 0; +} + +static int tegra_lcd_configure_internal(struct udevice *dev) +{ + struct tegra_lcd_priv *priv = dev_get_priv(dev); + struct tegra_dc_plat *dc_plat; + ofnode host1x = ofnode_get_parent(dev_ofnode(dev)); + ofnode node; + int ret; + + switch (priv->pipe) { + case 0: /* DC0 is usually used for DSI */ + /* Check for ganged DSI configuration */ + ofnode_for_each_subnode(node, host1x) + if (ofnode_name_eq(node, "dsi") && ofnode_is_enabled(node) && + ofnode_read_bool(node, "nvidia,ganged-mode")) + goto exit; + + /* If no master DSI found loop for any active DSI */ + ofnode_for_each_subnode(node, host1x) + if (ofnode_name_eq(node, "dsi") && ofnode_is_enabled(node)) + goto exit; + + log_err("%s: failed to find DSI device for '%s'\n", + __func__, dev->name); + + return -ENODEV; + case 1: /* DC1 is usually used for HDMI */ + ofnode_for_each_subnode(node, host1x) + if (ofnode_name_eq(node, "hdmi")) + goto exit; + + log_err("%s: failed to find HDMI device for '%s'\n", + __func__, dev->name); + + return -ENODEV; + default: + log_debug("Unsupported DC selection\n"); + return -EINVAL; + } + +exit: + ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, node, &priv->bridge); + if (ret) { + log_err("%s: failed to get DSI/HDMI device for '%s' (ret %d)\n", + __func__, dev->name, ret); + return ret; + } + + priv->clk_parent = devm_clk_get(priv->bridge, "parent"); + if (IS_ERR(priv->clk_parent)) { + log_debug("%s: Could not get DC clock parent from DSI/HDMI: %ld\n", + __func__, PTR_ERR(priv->clk_parent)); + return PTR_ERR(priv->clk_parent); + } + + dc_plat = dev_get_plat(priv->bridge); + + /* Fill the platform data for internal devices */ + dc_plat->dev = dev; + dc_plat->dc = priv->dc; + dc_plat->pipe = priv->pipe; + + return 0; +} + +static int tegra_lcd_of_to_plat(struct udevice *dev) +{ + struct tegra_lcd_priv *priv = dev_get_priv(dev); + struct display_timing *timing; + ofnode rgb; + int ret; + + priv->dc = (struct dc_ctlr *)dev_read_addr_ptr(dev); + if (!priv->dc) { + log_debug("%s: No display controller address\n", __func__); + return -EINVAL; + } + + priv->soc = (struct tegra_dc_soc_info *)dev_get_driver_data(dev); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + log_debug("%s: Could not get DC clock: %ld\n", + __func__, PTR_ERR(priv->clk)); + return PTR_ERR(priv->clk); + } + + priv->clk_parent = devm_clk_get(dev, "parent"); + if (IS_ERR(priv->clk_parent)) { + log_debug("%s: Could not get DC clock parent: %ld\n", + __func__, PTR_ERR(priv->clk_parent)); + return PTR_ERR(priv->clk_parent); + } + + priv->rotation = dev_read_bool(dev, "nvidia,180-rotation"); + priv->pipe = dev_read_u32_default(dev, "nvidia,head", 0); + + /* + * Usual logic of Tegra video routing should be next: + * 1. Check rgb subnode for RGB/LVDS panels or bridges + * 2. If none found, then iterate through bridges bound, + * looking for DSIA or DSIB for DC0 and HDMI for DC1. + * If none of above is valid, then configuration is not + * valid. + */ + + rgb = dev_read_subnode(dev, "rgb"); + if (ofnode_valid(rgb) && ofnode_is_enabled(rgb)) { + /* RGB is available, use it */ + ret = tegra_lcd_configure_rgb(dev, rgb); + if (ret) + return ret; + } else { + /* RGB is not available, check for internal devices */ + ret = tegra_lcd_configure_internal(dev); + if (ret) + return ret; + } + + if (priv->panel) { + ret = panel_get_display_timing(priv->panel, &priv->timing); + if (ret) { + ret = ofnode_decode_display_timing(rgb, 0, &priv->timing); + if (ret) { + log_debug("%s: Cannot read display timing for '%s' (ret=%d)\n", + __func__, dev->name, ret); + return -EINVAL; + } + } + } + + if (priv->bridge) { + ret = video_bridge_get_display_timing(priv->bridge, &priv->timing); + if (ret) { + log_debug("%s: Cannot read display timing for '%s' (ret=%d)\n", + __func__, dev->name, ret); + return -EINVAL; + } + } + + timing = &priv->timing; + priv->width = timing->hactive.typ; + priv->height = timing->vactive.typ; + priv->pixel_clock = timing->pixelclock.typ; + priv->log2_bpp = VIDEO_BPP16; + + return 0; +} + +static int tegra_lcd_bind(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->size = LCD_MAX_WIDTH * LCD_MAX_HEIGHT * + (1 << LCD_MAX_LOG2_BPP) / 8; + + return dm_scan_fdt_dev(dev); +} + +static const struct tegra_dc_soc_info tegra20_dc_soc_info = { + .has_timer = true, + .has_rgb = true, + .has_pgate = false, +}; + +static const struct tegra_dc_soc_info tegra30_dc_soc_info = { + .has_timer = false, + .has_rgb = true, + .has_pgate = false, +}; + +static const struct tegra_dc_soc_info tegra114_dc_soc_info = { + .has_timer = false, + .has_rgb = false, + .has_pgate = true, +}; + +static const struct udevice_id tegra_lcd_ids[] = { + { + .compatible = "nvidia,tegra20-dc", + .data = (ulong)&tegra20_dc_soc_info + }, { + .compatible = "nvidia,tegra30-dc", + .data = (ulong)&tegra30_dc_soc_info + }, { + .compatible = "nvidia,tegra114-dc", + .data = (ulong)&tegra114_dc_soc_info + }, { + .compatible = "nvidia,tegra124-dc", + .data = (ulong)&tegra114_dc_soc_info + }, { + /* sentinel */ + } +}; + +U_BOOT_DRIVER(tegra_lcd) = { + .name = "tegra_lcd", + .id = UCLASS_VIDEO, + .of_match = tegra_lcd_ids, + .bind = tegra_lcd_bind, + .probe = tegra_lcd_probe, + .of_to_plat = tegra_lcd_of_to_plat, + .priv_auto = sizeof(struct tegra_lcd_priv), +}; diff --git a/drivers/video/tegra/tegra-dc.h b/drivers/video/tegra/tegra-dc.h new file mode 100644 index 00000000000..2a4013b3355 --- /dev/null +++ b/drivers/video/tegra/tegra-dc.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation <www.nvidia.com> + */ + +#ifndef _TEGRA_DC_H +#define _TEGRA_DC_H + +#ifndef __ASSEMBLY__ +#include <linux/bitops.h> +#endif + +/* arch-tegra/dc exists only because T124 uses it */ +#include <asm/arch-tegra/dc.h> + +struct tegra_dc_plat { + struct udevice *dev; /* Display controller device */ + struct dc_ctlr *dc; /* Display controller regmap */ + int pipe; /* DC number: 0 for A, 1 for B */ + ulong scdiv; /* Shift clock divider */ +}; + +/* This holds information about a window which can be displayed */ +struct disp_ctl_win { + enum win_color_depth_id fmt; /* Color depth/format */ + unsigned int bpp; /* Bits per pixel */ + phys_addr_t phys_addr; /* Physical address in memory */ + unsigned int x; /* Horizontal address offset (bytes) */ + unsigned int y; /* Veritical address offset (bytes) */ + unsigned int w; /* Width of source window */ + unsigned int h; /* Height of source window */ + unsigned int stride; /* Number of bytes per line */ + unsigned int out_x; /* Left edge of output window (col) */ + unsigned int out_y; /* Top edge of output window (row) */ + unsigned int out_w; /* Width of output window in pixels */ + unsigned int out_h; /* Height of output window in pixels */ +}; + +#endif /* _TEGRA_DC_H */ diff --git a/drivers/video/tegra/tegra-dsi.c b/drivers/video/tegra/tegra-dsi.c new file mode 100644 index 00000000000..a96fba01ee4 --- /dev/null +++ b/drivers/video/tegra/tegra-dsi.c @@ -0,0 +1,1148 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 NVIDIA Corporation + * Copyright (c) 2022 Svyatoslav Ryhel <[email protected]> + */ + +#include <dm.h> +#include <clk.h> +#include <log.h> +#include <misc.h> +#include <mipi_display.h> +#include <mipi_dsi.h> +#include <backlight.h> +#include <video_bridge.h> +#include <panel.h> +#include <reset.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/time.h> +#include <power/regulator.h> + +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch-tegra/clk_rst.h> + +#include "tegra-dc.h" +#include "tegra-dsi.h" +#include "mipi-phy.h" + +/* List of supported DSI bridges */ +enum { + DSI_V0, + DSI_V1, +}; + +struct tegra_dsi_priv { + struct mipi_dsi_host host; + struct mipi_dsi_device device; + struct mipi_dphy_timing dphy_timing; + + struct udevice *panel; + struct udevice *mipi; + struct display_timing timing; + + struct dsi_ctlr *dsi; + struct udevice *avdd; + + enum tegra_dsi_format format; + + struct clk *clk; + struct clk *clk_parent; + + int video_fifo_depth; + int host_fifo_depth; + + u32 calibration_pads; + u32 version; + + /* for ganged-mode support */ + struct udevice *master; + struct udevice *slave; +}; + +static void tegra_dc_enable_controller(struct udevice *dev) +{ + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct dc_ctlr *dc = dc_plat->dc; + u32 value; + + value = readl(&dc->disp.disp_win_opt); + value |= DSI_ENABLE; + writel(value, &dc->disp.disp_win_opt); + + writel(GENERAL_UPDATE, &dc->cmd.state_ctrl); + writel(GENERAL_ACT_REQ, &dc->cmd.state_ctrl); +} + +static const char * const error_report[16] = { + "SoT Error", + "SoT Sync Error", + "EoT Sync Error", + "Escape Mode Entry Command Error", + "Low-Power Transmit Sync Error", + "Peripheral Timeout Error", + "False Control Error", + "Contention Detected", + "ECC Error, single-bit", + "ECC Error, multi-bit", + "Checksum Error", + "DSI Data Type Not Recognized", + "DSI VC ID Invalid", + "Invalid Transmission Length", + "Reserved", + "DSI Protocol Violation", +}; + +static ssize_t tegra_dsi_read_response(struct dsi_misc_reg *misc, + const struct mipi_dsi_msg *msg, + size_t count) +{ + u8 *rx = msg->rx_buf; + unsigned int i, j, k; + size_t size = 0; + u16 errors; + u32 value; + + /* read and parse packet header */ + value = readl(&misc->dsi_rd_data); + + switch (value & 0x3f) { + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + errors = (value >> 8) & 0xffff; + printf("%s: Acknowledge and error report: %04x\n", + __func__, errors); + for (i = 0; i < ARRAY_SIZE(error_report); i++) + if (errors & BIT(i)) + printf("%s: %2u: %s\n", __func__, i, + error_report[i]); + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + rx[0] = (value >> 8) & 0xff; + size = 1; + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + rx[0] = (value >> 8) & 0xff; + rx[1] = (value >> 16) & 0xff; + size = 2; + break; + + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + default: + printf("%s: unhandled response type: %02x\n", + __func__, value & 0x3f); + return -EPROTO; + } + + size = min(size, msg->rx_len); + + if (msg->rx_buf && size > 0) { + for (i = 0, j = 0; i < count - 1; i++, j += 4) { + u8 *rx = msg->rx_buf + j; + + value = readl(&misc->dsi_rd_data); + + for (k = 0; k < 4 && (j + k) < msg->rx_len; k++) + rx[j + k] = (value >> (k << 3)) & 0xff; + } + } + + return size; +} + +static int tegra_dsi_transmit(struct dsi_misc_reg *misc, + unsigned long timeout) +{ + writel(DSI_TRIGGER_HOST, &misc->dsi_trigger); + + while (timeout--) { + u32 value = readl(&misc->dsi_trigger); + + if ((value & DSI_TRIGGER_HOST) == 0) + return 0; + + udelay(1000); + } + + debug("timeout waiting for transmission to complete\n"); + return -ETIMEDOUT; +} + +static int tegra_dsi_wait_for_response(struct dsi_misc_reg *misc, + unsigned long timeout) +{ + while (timeout--) { + u32 value = readl(&misc->dsi_status); + u8 count = value & 0x1f; + + if (count > 0) + return count; + + udelay(1000); + } + + debug("peripheral returned no data\n"); + return -ETIMEDOUT; +} + +static void tegra_dsi_writesl(struct dsi_misc_reg *misc, + const void *buffer, size_t size) +{ + const u8 *buf = buffer; + size_t i, j; + u32 value; + + for (j = 0; j < size; j += 4) { + value = 0; + + for (i = 0; i < 4 && j + i < size; i++) + value |= buf[j + i] << (i << 3); + + writel(value, &misc->dsi_wr_data); + } +} + +static ssize_t tegra_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct udevice *dev = (struct udevice *)host->dev; + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_misc_reg *misc = &priv->dsi->misc; + struct mipi_dsi_packet packet; + const u8 *header; + size_t count; + ssize_t err; + u32 value; + + err = mipi_dsi_create_packet(&packet, msg); + if (err < 0) + return err; + + header = packet.header; + + /* maximum FIFO depth is 1920 words */ + if (packet.size > priv->video_fifo_depth * 4) + return -ENOSPC; + + /* reset underflow/overflow flags */ + value = readl(&misc->dsi_status); + if (value & (DSI_STATUS_UNDERFLOW | DSI_STATUS_OVERFLOW)) { + value = DSI_HOST_CONTROL_FIFO_RESET; + writel(value, &misc->host_dsi_ctrl); + udelay(10); + } + + value = readl(&misc->dsi_pwr_ctrl); + value |= DSI_POWER_CONTROL_ENABLE; + writel(value, &misc->dsi_pwr_ctrl); + + mdelay(5); + + value = DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST | + DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC; + + if ((msg->flags & MIPI_DSI_MSG_USE_LPM) == 0) + value |= DSI_HOST_CONTROL_HS; + + /* + * The host FIFO has a maximum of 64 words, so larger transmissions + * need to use the video FIFO. + */ + if (packet.size > priv->host_fifo_depth * 4) + value |= DSI_HOST_CONTROL_FIFO_SEL; + + writel(value, &misc->host_dsi_ctrl); + + /* + * For reads and messages with explicitly requested ACK, generate a + * BTA sequence after the transmission of the packet. + */ + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + value = readl(&misc->host_dsi_ctrl); + value |= DSI_HOST_CONTROL_PKT_BTA; + writel(value, &misc->host_dsi_ctrl); + } + + value = DSI_CONTROL_LANES(0) | DSI_CONTROL_HOST_ENABLE; + writel(value, &misc->dsi_ctrl); + + /* write packet header, ECC is generated by hardware */ + value = header[2] << 16 | header[1] << 8 | header[0]; + writel(value, &misc->dsi_wr_data); + + /* write payload (if any) */ + if (packet.payload_length > 0) + tegra_dsi_writesl(misc, packet.payload, + packet.payload_length); + + err = tegra_dsi_transmit(misc, 250); + if (err < 0) + return err; + + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + err = tegra_dsi_wait_for_response(misc, 250); + if (err < 0) + return err; + + count = err; + + value = readl(&misc->dsi_rd_data); + switch (value) { + case 0x84: + debug("%s: ACK\n", __func__); + break; + + case 0x87: + debug("%s: ESCAPE\n", __func__); + break; + + default: + printf("%s: unknown status: %08x\n", __func__, value); + break; + } + + if (count > 1) { + err = tegra_dsi_read_response(misc, msg, count); + if (err < 0) { + printf("%s: failed to parse response: %zd\n", + __func__, err); + } else { + /* + * For read commands, return the number of + * bytes returned by the peripheral. + */ + count = err; + } + } + } else { + /* + * For write commands, we have transmitted the 4-byte header + * plus the variable-length payload. + */ + count = 4 + packet.payload_length; + } + + return count; +} + +struct mipi_dsi_host_ops tegra_dsi_bridge_host_ops = { + .transfer = tegra_dsi_host_transfer, +}; + +#define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9)) +#define PKT_LEN0(len) (((len) & 0x07) << 0) +#define PKT_ID1(id) ((((id) & 0x3f) << 13) | (1 << 19)) +#define PKT_LEN1(len) (((len) & 0x07) << 10) +#define PKT_ID2(id) ((((id) & 0x3f) << 23) | (1 << 29)) +#define PKT_LEN2(len) (((len) & 0x07) << 20) + +#define PKT_LP BIT(30) +#define NUM_PKT_SEQ 12 + +/* + * non-burst mode with sync pulses + */ +static const u32 pkt_seq_video_non_burst_sync_pulses[NUM_PKT_SEQ] = { + [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 1] = 0, + [ 2] = PKT_ID0(MIPI_DSI_V_SYNC_END) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 3] = 0, + [ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0), + [ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) | + PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) | + PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), + [ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0), + [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) | + PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) | + PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), +}; + +/* + * non-burst mode with sync events + */ +static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = { + [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 1] = 0, + [ 2] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 3] = 0, + [ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) | + PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3), + [ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), + [ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) | + PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3), + [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), +}; + +static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = { + [ 0] = 0, + [ 1] = 0, + [ 2] = 0, + [ 3] = 0, + [ 4] = 0, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(3) | PKT_LP, + [ 7] = 0, + [ 8] = 0, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(5) | PKT_LP, + [11] = 0, +}; + +static void tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format, + unsigned int *mulp, unsigned int *divp) +{ + switch (format) { + case MIPI_DSI_FMT_RGB666_PACKED: + case MIPI_DSI_FMT_RGB888: + *mulp = 3; + *divp = 1; + break; + + case MIPI_DSI_FMT_RGB565: + *mulp = 2; + *divp = 1; + break; + + case MIPI_DSI_FMT_RGB666: + *mulp = 9; + *divp = 4; + break; + + default: + break; + } +} + +static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format, + enum tegra_dsi_format *fmt) +{ + switch (format) { + case MIPI_DSI_FMT_RGB888: + *fmt = TEGRA_DSI_FORMAT_24P; + break; + + case MIPI_DSI_FMT_RGB666: + *fmt = TEGRA_DSI_FORMAT_18NP; + break; + + case MIPI_DSI_FMT_RGB666_PACKED: + *fmt = TEGRA_DSI_FORMAT_18P; + break; + + case MIPI_DSI_FMT_RGB565: + *fmt = TEGRA_DSI_FORMAT_16P; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void tegra_dsi_pad_calibrate(struct dsi_pad_ctrl_reg *pad) +{ + u32 value; + + /* start calibration */ + value = DSI_PAD_CONTROL_PAD_LPUPADJ(0x1) | + DSI_PAD_CONTROL_PAD_LPDNADJ(0x1) | + DSI_PAD_CONTROL_PAD_PREEMP_EN(0x1) | + DSI_PAD_CONTROL_PAD_SLEWDNADJ(0x6) | + DSI_PAD_CONTROL_PAD_SLEWUPADJ(0x6) | + DSI_PAD_CONTROL_PAD_PDIO(0) | + DSI_PAD_CONTROL_PAD_PDIO_CLK(0) | + DSI_PAD_CONTROL_PAD_PULLDN_ENAB(0); + writel(value, &pad->pad_ctrl); + + clock_enable(PERIPH_ID_VI); + clock_enable(PERIPH_ID_CSI); + udelay(2); + reset_set_enable(PERIPH_ID_VI, 0); + reset_set_enable(PERIPH_ID_CSI, 0); + + value = MIPI_CAL_TERMOSA(0x4); + writel(value, TEGRA_VI_BASE + (CSI_CILA_MIPI_CAL_CONFIG_0 << 2)); + + value = MIPI_CAL_TERMOSB(0x4); + writel(value, TEGRA_VI_BASE + (CSI_CILB_MIPI_CAL_CONFIG_0 << 2)); + + value = MIPI_CAL_HSPUOSD(0x3) | MIPI_CAL_HSPDOSD(0x4); + writel(value, TEGRA_VI_BASE + (CSI_DSI_MIPI_CAL_CONFIG << 2)); + + value = PAD_DRIV_DN_REF(0x5) | PAD_DRIV_UP_REF(0x7); + writel(value, TEGRA_VI_BASE + (CSI_MIPIBIAS_PAD_CONFIG << 2)); + + value = PAD_CIL_PDVREG(0x0); + writel(value, TEGRA_VI_BASE + (CSI_CIL_PAD_CONFIG << 2)); +} + +static void tegra_dsi_mipi_calibrate(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_pad_ctrl_reg *pad = &priv->dsi->pad; + u32 value; + int ret; + + ret = misc_set_enabled(priv->mipi, true); + if (ret) + log_debug("%s: failed to enable MIPI calibration: %d\n", + __func__, ret); + + writel(0, &pad->pad_ctrl); + writel(0, &pad->pad_ctrl_1); + writel(0, &pad->pad_ctrl_2); + writel(0, &pad->pad_ctrl_3); + writel(0, &pad->pad_ctrl_4); + + /* DSI pad enable */ + value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); + writel(value, &pad->pad_ctrl); + + value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | + DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | + DSI_PAD_OUT_CLK(0x0); + writel(value, &pad->pad_ctrl_2); + + value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | + DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3); + writel(value, &pad->pad_ctrl_3); + + ret = misc_write(priv->mipi, priv->calibration_pads, NULL, 0); + if (ret) + log_debug("%s: MIPI calibration failed %d\n", __func__, ret); + + if (priv->slave) + tegra_dsi_mipi_calibrate(priv->slave); +} + +static void tegra_dsi_set_timeout(struct udevice *dev, + unsigned long bclk, + unsigned int vrefresh) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_timeout_reg *rtimeout = &priv->dsi->timeout; + unsigned int timeout; + u32 value; + + /* one frame high-speed transmission timeout */ + timeout = (bclk / vrefresh) / 512; + value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout); + writel(value, &rtimeout->dsi_timeout_0); + + /* 2 ms peripheral timeout for panel */ + timeout = 2 * bclk / 512 * 1000; + value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000); + writel(value, &rtimeout->dsi_timeout_1); + + value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); + writel(value, &rtimeout->dsi_to_tally); + + if (priv->slave) + tegra_dsi_set_timeout(priv->slave, bclk, vrefresh); +} + +static void tegra_dsi_set_phy_timing(struct udevice *dev, + unsigned long period, + const struct mipi_dphy_timing *dphy_timing) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_timing_reg *ptiming = &priv->dsi->ptiming; + u32 value; + + value = DSI_TIMING_FIELD(dphy_timing->hsexit, period, 1) << 24 | + DSI_TIMING_FIELD(dphy_timing->hstrail, period, 0) << 16 | + DSI_TIMING_FIELD(dphy_timing->hszero, period, 3) << 8 | + DSI_TIMING_FIELD(dphy_timing->hsprepare, period, 1); + writel(value, &ptiming->dsi_phy_timing_0); + + value = DSI_TIMING_FIELD(dphy_timing->clktrail, period, 1) << 24 | + DSI_TIMING_FIELD(dphy_timing->clkpost, period, 1) << 16 | + DSI_TIMING_FIELD(dphy_timing->clkzero, period, 1) << 8 | + DSI_TIMING_FIELD(dphy_timing->lpx, period, 1); + writel(value, &ptiming->dsi_phy_timing_1); + + value = DSI_TIMING_FIELD(dphy_timing->clkprepare, period, 1) << 16 | + DSI_TIMING_FIELD(dphy_timing->clkpre, period, 1) << 8 | + DSI_TIMING_FIELD(0xff * period, period, 0) << 0; + writel(value, &ptiming->dsi_phy_timing_2); + + value = DSI_TIMING_FIELD(dphy_timing->taget, period, 1) << 16 | + DSI_TIMING_FIELD(dphy_timing->tasure, period, 1) << 8 | + DSI_TIMING_FIELD(dphy_timing->tago, period, 1); + writel(value, &ptiming->dsi_bta_timing); + + if (priv->slave) + tegra_dsi_set_phy_timing(priv->slave, period, dphy_timing); +} + +static u32 tegra_dsi_get_lanes(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + + if (priv->master) { + struct tegra_dsi_priv *mpriv = dev_get_priv(priv->master); + struct mipi_dsi_device *mdevice = &mpriv->device; + + return mdevice->lanes + device->lanes; + } + + if (priv->slave) { + struct tegra_dsi_priv *spriv = dev_get_priv(priv->slave); + struct mipi_dsi_device *sdevice = &spriv->device; + + return device->lanes + sdevice->lanes; + } + + return device->lanes; +} + +static void tegra_dsi_ganged_enable(struct udevice *dev, unsigned int start, + unsigned int size) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_ganged_mode_reg *ganged = &priv->dsi->ganged; + + writel(start, &ganged->ganged_mode_start); + writel(size << 16 | size, &ganged->ganged_mode_size); + writel(DSI_GANGED_MODE_CONTROL_ENABLE, &ganged->ganged_mode_ctrl); +} + +static void tegra_dsi_configure(struct udevice *dev, unsigned int pipe, + unsigned long mode_flags) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct display_timing *timing = &priv->timing; + + struct dsi_misc_reg *misc = &priv->dsi->misc; + struct dsi_pkt_seq_reg *pkt = &priv->dsi->pkt; + struct dsi_pkt_len_reg *len = &priv->dsi->len; + + unsigned int hact, hsw, hbp, hfp, i, mul, div; + const u32 *pkt_seq; + u32 value; + + tegra_dsi_get_muldiv(device->format, &mul, &div); + + if (mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + printf("[DSI] Non-burst video mode with sync pulses\n"); + pkt_seq = pkt_seq_video_non_burst_sync_pulses; + } else if (mode_flags & MIPI_DSI_MODE_VIDEO) { + printf("[DSI] Non-burst video mode with sync events\n"); + pkt_seq = pkt_seq_video_non_burst_sync_events; + } else { + printf("[DSI] Command mode\n"); + pkt_seq = pkt_seq_command_mode; + } + + value = DSI_CONTROL_CHANNEL(0) | + DSI_CONTROL_FORMAT(priv->format) | + DSI_CONTROL_LANES(device->lanes - 1) | + DSI_CONTROL_SOURCE(pipe); + writel(value, &misc->dsi_ctrl); + + writel(priv->video_fifo_depth, &misc->dsi_max_threshold); + + value = DSI_HOST_CONTROL_HS; + writel(value, &misc->host_dsi_ctrl); + + value = readl(&misc->dsi_ctrl); + + if (mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) + value |= DSI_CONTROL_HS_CLK_CTRL; + + value &= ~DSI_CONTROL_TX_TRIG(3); + + /* enable DCS commands for command mode */ + if (mode_flags & MIPI_DSI_MODE_VIDEO) + value &= ~DSI_CONTROL_DCS_ENABLE; + else + value |= DSI_CONTROL_DCS_ENABLE; + + value |= DSI_CONTROL_VIDEO_ENABLE; + value &= ~DSI_CONTROL_HOST_ENABLE; + writel(value, &misc->dsi_ctrl); + + for (i = 0; i < NUM_PKT_SEQ; i++) + writel(pkt_seq[i], &pkt->dsi_pkt_seq_0_lo + i); + + if (mode_flags & MIPI_DSI_MODE_VIDEO) { + /* horizontal active pixels */ + hact = timing->hactive.typ * mul / div; + + /* horizontal sync width */ + hsw = timing->hsync_len.typ * mul / div; + + /* horizontal back porch */ + hbp = timing->hback_porch.typ * mul / div; + + /* horizontal front porch */ + hfp = timing->hfront_porch.typ * mul / div; + + if (priv->master || priv->slave) { + hact /= 2; + hsw /= 2; + hbp = hbp / 2 - 1; + hfp /= 2; + } + + if ((mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) == 0) + hbp += hsw; + + /* subtract packet overhead */ + hsw -= 10; + hbp -= 14; + hfp -= 8; + + writel(hsw << 16 | 0, &len->dsi_pkt_len_0_1); + writel(hact << 16 | hbp, &len->dsi_pkt_len_2_3); + writel(hfp, &len->dsi_pkt_len_4_5); + writel(0x0f0f << 16, &len->dsi_pkt_len_6_7); + } else { + if (priv->master || priv->slave) { + /* + * For ganged mode, assume symmetric left-right mode. + */ + value = 1 + (timing->hactive.typ / 2) * mul / div; + } else { + /* 1 byte (DCS command) + pixel data */ + value = 1 + timing->hactive.typ * mul / div; + } + + writel(0, &len->dsi_pkt_len_0_1); + writel(value << 16, &len->dsi_pkt_len_2_3); + writel(value << 16, &len->dsi_pkt_len_4_5); + writel(0, &len->dsi_pkt_len_6_7); + + value = MIPI_DCS_WRITE_MEMORY_START << 8 | + MIPI_DCS_WRITE_MEMORY_CONTINUE; + writel(value, &len->dsi_dcs_cmds); + } + + /* set SOL delay */ + if (priv->master || priv->slave) { + unsigned long delay, bclk, bclk_ganged; + unsigned int lanes = tegra_dsi_get_lanes(dev); + unsigned long htotal = timing->hactive.typ + timing->hfront_porch.typ + + timing->hback_porch.typ + timing->hsync_len.typ; + + /* SOL to valid, valid to FIFO and FIFO write delay */ + delay = 4 + 4 + 2; + delay = DIV_ROUND_UP(delay * mul, div * lanes); + /* FIFO read delay */ + delay = delay + 6; + + bclk = DIV_ROUND_UP(htotal * mul, div * lanes); + bclk_ganged = DIV_ROUND_UP(bclk * lanes / 2, lanes); + value = bclk - bclk_ganged + delay + 20; + } else { + /* set SOL delay (for non-burst mode only) */ + value = 8 * mul / div; + } + + writel(value, &misc->dsi_sol_delay); + + if (priv->slave) { + tegra_dsi_configure(priv->slave, pipe, mode_flags); + /* + * TODO: Support modes other than symmetrical left-right + * split. + */ + tegra_dsi_ganged_enable(dev, 0, timing->hactive.typ / 2); + tegra_dsi_ganged_enable(priv->slave, timing->hactive.typ / 2, + timing->hactive.typ / 2); + } +} + +static void tegra_dsi_enable(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_misc_reg *misc = &priv->dsi->misc; + u32 value; + + /* enable DSI controller */ + value = readl(&misc->dsi_pwr_ctrl); + value |= DSI_POWER_CONTROL_ENABLE; + writel(value, &misc->dsi_pwr_ctrl); + + if (priv->slave) + tegra_dsi_enable(priv->slave); +} + +static int tegra_dsi_encoder_enable(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct mipi_dsi_device *device = &priv->device; + struct display_timing *timing = &priv->timing; + struct dsi_misc_reg *misc = &priv->dsi->misc; + unsigned int mul, div; + unsigned long bclk, plld, period; + u32 value, lanes; + int ret; + + /* If for some reasone DSI is enabled then it needs to + * be disabled in order for the panel initialization + * commands to be properly sent. + */ + value = readl(&misc->dsi_pwr_ctrl); + + if (value & DSI_POWER_CONTROL_ENABLE) { + value = readl(&misc->dsi_pwr_ctrl); + value &= ~DSI_POWER_CONTROL_ENABLE; + writel(value, &misc->dsi_pwr_ctrl); + } + + /* Disable interrupt */ + writel(0, &misc->int_enable); + + if (priv->version) + tegra_dsi_mipi_calibrate(dev); + else + tegra_dsi_pad_calibrate(&priv->dsi->pad); + + tegra_dsi_get_muldiv(device->format, &mul, &div); + + /* compute byte clock */ + lanes = tegra_dsi_get_lanes(dev); + bclk = (timing->pixelclock.typ * mul) / (div * lanes); + + tegra_dsi_set_timeout(dev, bclk, 60); + + /* + * Compute bit clock and round up to the next MHz. + */ + plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC; + period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld); + + ret = mipi_dphy_timing_get_default(&priv->dphy_timing, period); + if (ret < 0) { + printf("%s: failed to get D-PHY timing: %d\n", __func__, ret); + return ret; + } + + ret = mipi_dphy_timing_validate(&priv->dphy_timing, period); + if (ret < 0) { + printf("%s: failed to validate D-PHY timing: %d\n", __func__, ret); + return ret; + } + + /* + * The D-PHY timing fields are expressed in byte-clock cycles, so + * multiply the period by 8. + */ + tegra_dsi_set_phy_timing(dev, period * 8, &priv->dphy_timing); + + /* Perform panel HW setup */ + ret = panel_enable_backlight(priv->panel); + if (ret) + return ret; + + tegra_dsi_configure(dev, dc_plat->pipe, device->mode_flags); + + tegra_dc_enable_controller(dev); + + tegra_dsi_enable(dev); + + return 0; +} + +static int tegra_dsi_bridge_set_panel(struct udevice *dev, int percent) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + + /* Turn on/off backlight */ + return panel_set_backlight(priv->panel, percent); +} + +static int tegra_dsi_panel_timings(struct udevice *dev, + struct display_timing *timing) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + + return 0; +} + +static void tegra_dsi_init_clocks(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct mipi_dsi_device *device = &priv->device; + unsigned int mul, div, lanes; + unsigned long bclk, plld; + + /* Switch parents of DSI clocks in case of not standard parent */ + if (priv->clk->id == PERIPH_ID_DSI && + priv->clk_parent->id == CLOCK_ID_DISPLAY2) { + /* Change DSIA clock parent to PLLD2 */ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + + /* DSIA_CLK_SRC */ + setbits_le32(&clkrst->crc_pll[CLOCK_ID_DISPLAY].pll_base, + BIT(25)); + } + + if (priv->clk->id == PERIPH_ID_DSIB && + priv->clk_parent->id == CLOCK_ID_DISPLAY) { + /* Change DSIB clock parent to match DSIA */ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + + /* DSIB_CLK_SRC */ + clrbits_le32(&clkrst->plld2.pll_base, BIT(25)); + } + + tegra_dsi_get_muldiv(device->format, &mul, &div); + + lanes = tegra_dsi_get_lanes(dev); + bclk = (priv->timing.pixelclock.typ * mul) / (div * lanes); + + plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC); + + dc_plat->scdiv = ((plld * USEC_PER_SEC + + priv->timing.pixelclock.typ / 2) / + priv->timing.pixelclock.typ) - 2; + + /* + * BUG: If DISP1 is a PLLD/D2 child, it cannot go over 370MHz. The + * cause of this is not quite clear. This can be overcomed by + * halving the PLLD/D2 if the target rate is > 800MHz. This way + * DISP1 and DSI clocks will be equal. + */ + if (plld > 800) + plld /= 2; + + switch (clock_get_osc_freq()) { + case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */ + case CLOCK_OSC_FREQ_48_0: /* OSC is 48Mhz */ + clock_set_rate(priv->clk_parent->id, plld, 12, 0, 8); + break; + + case CLOCK_OSC_FREQ_26_0: /* OSC is 26Mhz */ + clock_set_rate(priv->clk_parent->id, plld, 26, 0, 8); + break; + + case CLOCK_OSC_FREQ_13_0: /* OSC is 13Mhz */ + case CLOCK_OSC_FREQ_16_8: /* OSC is 16.8Mhz */ + clock_set_rate(priv->clk_parent->id, plld, 13, 0, 8); + break; + + case CLOCK_OSC_FREQ_19_2: + case CLOCK_OSC_FREQ_38_4: + default: + /* + * These are not supported. + */ + break; + } + + clk_enable(priv->clk); +} + +static int tegra_dsi_ganged_probe(struct udevice *dev) +{ + struct tegra_dsi_priv *mpriv = dev_get_priv(dev); + struct udevice *gangster; + + uclass_get_device_by_phandle(UCLASS_VIDEO_BRIDGE, dev, + "nvidia,ganged-mode", &gangster); + if (gangster) { + /* Ganged mode is set */ + struct tegra_dsi_priv *spriv = dev_get_priv(gangster); + + mpriv->slave = gangster; + spriv->master = dev; + } + + return 0; +} + +static int tegra_dsi_bridge_probe(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct mipi_dsi_panel_plat *mipi_plat; + struct reset_ctl reset_ctl; + int ret; + + priv->version = dev_get_driver_data(dev); + + priv->dsi = (struct dsi_ctlr *)dev_read_addr_ptr(dev); + if (!priv->dsi) { + printf("%s: No display controller address\n", __func__); + return -EINVAL; + } + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + log_debug("%s: Could not get DSI clock: %ld\n", + __func__, PTR_ERR(priv->clk)); + return PTR_ERR(priv->clk); + } + + priv->clk_parent = devm_clk_get(dev, "parent"); + if (IS_ERR(priv->clk_parent)) { + log_debug("%s: Could not get DSI clock parent: %ld\n", + __func__, PTR_ERR(priv->clk_parent)); + return PTR_ERR(priv->clk_parent); + } + + priv->video_fifo_depth = 1920; + priv->host_fifo_depth = 64; + + tegra_dsi_ganged_probe(dev); + + ret = reset_get_by_name(dev, "dsi", &reset_ctl); + if (ret) { + log_debug("%s: reset_get_by_name() failed: %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev, + "avdd-dsi-csi-supply", &priv->avdd); + if (ret) + debug("%s: Cannot get avdd-dsi-csi-supply: error %d\n", + __func__, ret); + + /* Check all DSI children */ + device_foreach_child(priv->panel, dev) { + if (device_get_uclass_id(priv->panel) == UCLASS_PANEL) + break; + } + + /* if loop exits without panel device return error */ + if (device_get_uclass_id(priv->panel) != UCLASS_PANEL) { + log_debug("%s: panel not found, ret %d\n", __func__, ret); + return -EINVAL; + } + + ret = uclass_get_device_by_ofnode(UCLASS_PANEL, dev_ofnode(priv->panel), + &priv->panel); + if (ret) { + log_debug("%s: Cannot get panel: error %d\n", __func__, ret); + return log_ret(ret); + } + + if (priv->version) { + ret = uclass_get_device_by_phandle(UCLASS_MISC, dev, + "nvidia,mipi-calibrate", + &priv->mipi); + if (ret) { + log_debug("%s: cannot get MIPI: error %d\n", __func__, ret); + return ret; + } + + ret = dev_read_u32_index(dev, "nvidia,mipi-calibrate", 1, + &priv->calibration_pads); + if (ret) { + log_debug("%s: cannot get calibration pads: error %d\n", + __func__, ret); + return ret; + } + } + + panel_get_display_timing(priv->panel, &priv->timing); + + mipi_plat = dev_get_plat(priv->panel); + mipi_plat->device = device; + + priv->host.dev = (struct device *)dev; + priv->host.ops = &tegra_dsi_bridge_host_ops; + + device->host = &priv->host; + device->lanes = mipi_plat->lanes; + device->format = mipi_plat->format; + device->mode_flags = mipi_plat->mode_flags; + + tegra_dsi_get_format(device->format, &priv->format); + + reset_assert(&reset_ctl); + + ret = regulator_set_enable_if_allowed(priv->avdd, true); + if (ret && ret != -ENOSYS) + return ret; + + tegra_dsi_init_clocks(dev); + + mdelay(2); + reset_deassert(&reset_ctl); + + return 0; +} + +static const struct video_bridge_ops tegra_dsi_bridge_ops = { + .attach = tegra_dsi_encoder_enable, + .set_backlight = tegra_dsi_bridge_set_panel, + .get_display_timing = tegra_dsi_panel_timings, +}; + +static const struct udevice_id tegra_dsi_bridge_ids[] = { + { .compatible = "nvidia,tegra20-dsi", .data = DSI_V0 }, + { .compatible = "nvidia,tegra30-dsi", .data = DSI_V0 }, + { .compatible = "nvidia,tegra114-dsi", .data = DSI_V1 }, + { .compatible = "nvidia,tegra124-dsi", .data = DSI_V1 }, + { } +}; + +U_BOOT_DRIVER(tegra_dsi) = { + .name = "tegra_dsi", + .id = UCLASS_VIDEO_BRIDGE, + .of_match = tegra_dsi_bridge_ids, + .ops = &tegra_dsi_bridge_ops, + .bind = dm_scan_fdt_dev, + .probe = tegra_dsi_bridge_probe, + .plat_auto = sizeof(struct tegra_dc_plat), + .priv_auto = sizeof(struct tegra_dsi_priv), +}; diff --git a/drivers/video/tegra/tegra-dsi.h b/drivers/video/tegra/tegra-dsi.h new file mode 100644 index 00000000000..683c5e31a34 --- /dev/null +++ b/drivers/video/tegra/tegra-dsi.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation <www.nvidia.com> + */ + +#ifndef _TEGRA_DSI_H +#define _TEGRA_DSI_H + +#ifndef __ASSEMBLY__ +#include <linux/bitops.h> +#endif + +/* Register definitions for the Tegra display serial interface */ + +/* DSI syncpoint register 0x000 ~ 0x002 */ +struct dsi_syncpt_reg { + /* Address 0x000 ~ 0x002 */ + uint incr_syncpt; /* _INCR_SYNCPT_0 */ + uint incr_syncpt_ctrl; /* _INCR_SYNCPT_CNTRL_0 */ + uint incr_syncpt_err; /* _INCR_SYNCPT_ERROR_0 */ +}; + +/* DSI misc register 0x008 ~ 0x015 */ +struct dsi_misc_reg { + /* Address 0x008 ~ 0x015 */ + uint ctxsw; /* _CTXSW_0 */ + uint dsi_rd_data; /* _DSI_RD_DATA_0 */ + uint dsi_wr_data; /* _DSI_WR_DATA_0 */ + uint dsi_pwr_ctrl; /* _DSI_POWER_CONTROL_0 */ + uint int_enable; /* _INT_ENABLE_0 */ + uint int_status; /* _INT_STATUS_0 */ + uint int_mask; /* _INT_MASK_0 */ + uint host_dsi_ctrl; /* _HOST_DSI_CONTROL_0 */ + uint dsi_ctrl; /* _DSI_CONTROL_0 */ + uint dsi_sol_delay; /* _DSI_SOL_DELAY_0 */ + uint dsi_max_threshold; /* _DSI_MAX_THRESHOLD_0 */ + uint dsi_trigger; /* _DSI_TRIGGER_0 */ + uint dsi_tx_crc; /* _DSI_TX_CRC_0 */ + uint dsi_status; /* _DSI_STATUS_0 */ +}; + +/* DSI init sequence register 0x01a ~ 0x022 */ +struct dsi_init_seq_reg { + /* Address 0x01a ~ 0x022 */ + uint dsi_init_seq_ctrl; /* _DSI_INIT_SEQ_CONTROL_0 */ + uint dsi_init_seq_data_0; /* _DSI_INIT_SEQ_DATA_0_0 */ + uint dsi_init_seq_data_1; /* _DSI_INIT_SEQ_DATA_1_0 */ + uint dsi_init_seq_data_2; /* _DSI_INIT_SEQ_DATA_2_0 */ + uint dsi_init_seq_data_3; /* _DSI_INIT_SEQ_DATA_3_0 */ + uint dsi_init_seq_data_4; /* _DSI_INIT_SEQ_DATA_4_0 */ + uint dsi_init_seq_data_5; /* _DSI_INIT_SEQ_DATA_5_0 */ + uint dsi_init_seq_data_6; /* _DSI_INIT_SEQ_DATA_6_0 */ + uint dsi_init_seq_data_7; /* _DSI_INIT_SEQ_DATA_7_0 */ +}; + +/* DSI packet sequence register 0x023 ~ 0x02e */ +struct dsi_pkt_seq_reg { + /* Address 0x023 ~ 0x02e */ + uint dsi_pkt_seq_0_lo; /* _DSI_PKT_SEQ_0_LO_0 */ + uint dsi_pkt_seq_0_hi; /* _DSI_PKT_SEQ_0_HI_0 */ + uint dsi_pkt_seq_1_lo; /* _DSI_PKT_SEQ_1_LO_0 */ + uint dsi_pkt_seq_1_hi; /* _DSI_PKT_SEQ_1_HI_0 */ + uint dsi_pkt_seq_2_lo; /* _DSI_PKT_SEQ_2_LO_0 */ + uint dsi_pkt_seq_2_hi; /* _DSI_PKT_SEQ_2_HI_0 */ + uint dsi_pkt_seq_3_lo; /* _DSI_PKT_SEQ_3_LO_0 */ + uint dsi_pkt_seq_3_hi; /* _DSI_PKT_SEQ_3_HI_0 */ + uint dsi_pkt_seq_4_lo; /* _DSI_PKT_SEQ_4_LO_0 */ + uint dsi_pkt_seq_4_hi; /* _DSI_PKT_SEQ_4_HI_0 */ + uint dsi_pkt_seq_5_lo; /* _DSI_PKT_SEQ_5_LO_0 */ + uint dsi_pkt_seq_5_hi; /* _DSI_PKT_SEQ_5_HI_0 */ +}; + +/* DSI packet length register 0x033 ~ 0x037 */ +struct dsi_pkt_len_reg { + /* Address 0x033 ~ 0x037 */ + uint dsi_dcs_cmds; /* _DSI_DCS_CMDS_0 */ + uint dsi_pkt_len_0_1; /* _DSI_PKT_LEN_0_1_0 */ + uint dsi_pkt_len_2_3; /* _DSI_PKT_LEN_2_3_0 */ + uint dsi_pkt_len_4_5; /* _DSI_PKT_LEN_4_5_0 */ + uint dsi_pkt_len_6_7; /* _DSI_PKT_LEN_6_7_0 */ +}; + +/* DSI PHY timing register 0x03c ~ 0x03f */ +struct dsi_timing_reg { + /* Address 0x03c ~ 0x03f */ + uint dsi_phy_timing_0; /* _DSI_PHY_TIMING_0_0 */ + uint dsi_phy_timing_1; /* _DSI_PHY_TIMING_1_0 */ + uint dsi_phy_timing_2; /* _DSI_PHY_TIMING_2_0 */ + uint dsi_bta_timing; /* _DSI_BTA_TIMING_0 */ +}; + +/* DSI timeout register 0x044 ~ 0x046 */ +struct dsi_timeout_reg { + /* Address 0x044 ~ 0x046 */ + uint dsi_timeout_0; /* _DSI_TIMEOUT_0_0 */ + uint dsi_timeout_1; /* _DSI_TIMEOUT_1_0 */ + uint dsi_to_tally; /* _DSI_TO_TALLY_0 */ +}; + +/* DSI PAD control register 0x04b ~ 0x052 */ +struct dsi_pad_ctrl_reg { + /* Address 0x04b ~ 0x052 */ + uint pad_ctrl; /* _PAD_CONTROL_0 */ + uint pad_ctrl_cd; /* _PAD_CONTROL_CD_0 */ + uint pad_cd_status; /* _PAD_CD_STATUS_0 */ + uint dsi_vid_mode_control; /* _DSI_VID_MODE_CONTROL_0 */ + uint pad_ctrl_1; /* _PAD_CONTROL_1 */ + uint pad_ctrl_2; /* _PAD_CONTROL_2 */ + uint pad_ctrl_3; /* _PAD_CONTROL_3 */ + uint pad_ctrl_4; /* _PAD_CONTROL_4 */ +}; + +/* DSI ganged mode register 0x053 ~ 0x04e */ +struct dsi_ganged_mode_reg { + /* Address 0x053 ~ 0x055 */ + uint ganged_mode_ctrl; /* _DSI_GANGED_MODE_CONTROL_0 */ + uint ganged_mode_start; /* _DSI_GANGED_MODE_START_0 */ + uint ganged_mode_size; /* _DSI_GANGED_MODE_SIZE_0 */ +}; + +/* Display Serial Interface (DSI_) regs */ +struct dsi_ctlr { + struct dsi_syncpt_reg syncpt; /* SYNCPT register 0x000 ~ 0x002 */ + uint reserved0[5]; /* reserved_0[5] */ + + struct dsi_misc_reg misc; /* MISC register 0x008 ~ 0x015 */ + uint reserved1[4]; /* reserved_1[4] */ + + struct dsi_init_seq_reg init; /* INIT register 0x01a ~ 0x022 */ + struct dsi_pkt_seq_reg pkt; /* PKT register 0x023 ~ 0x02e */ + uint reserved2[4]; /* reserved_2[4] */ + + struct dsi_pkt_len_reg len; /* LEN registers 0x033 ~ 0x037 */ + uint reserved3[4]; /* reserved_3[4] */ + + struct dsi_timing_reg ptiming; /* TIMING registers 0x03c ~ 0x03f */ + uint reserved4[4]; /* reserved_4[4] */ + + struct dsi_timeout_reg timeout; /* TIMEOUT registers 0x044 ~ 0x046 */ + uint reserved5[4]; /* reserved_5[4] */ + + struct dsi_pad_ctrl_reg pad; /* PAD registers 0x04b ~ 0x04e */ + struct dsi_ganged_mode_reg ganged; /* GANGED registers 0x053 ~ 0x055 */ +}; + +#define DSI_POWER_CONTROL_ENABLE BIT(0) + +#define DSI_HOST_CONTROL_FIFO_RESET BIT(21) +#define DSI_HOST_CONTROL_CRC_RESET BIT(20) +#define DSI_HOST_CONTROL_TX_TRIG_SOL (0 << 12) +#define DSI_HOST_CONTROL_TX_TRIG_FIFO (1 << 12) +#define DSI_HOST_CONTROL_TX_TRIG_HOST (2 << 12) +#define DSI_HOST_CONTROL_RAW BIT(6) +#define DSI_HOST_CONTROL_HS BIT(5) +#define DSI_HOST_CONTROL_FIFO_SEL BIT(4) +#define DSI_HOST_CONTROL_IMM_BTA BIT(3) +#define DSI_HOST_CONTROL_PKT_BTA BIT(2) +#define DSI_HOST_CONTROL_CS BIT(1) +#define DSI_HOST_CONTROL_ECC BIT(0) + +#define DSI_CONTROL_HS_CLK_CTRL BIT(20) +#define DSI_CONTROL_CHANNEL(c) (((c) & 0x3) << 16) +#define DSI_CONTROL_FORMAT(f) (((f) & 0x3) << 12) +#define DSI_CONTROL_TX_TRIG(x) (((x) & 0x3) << 8) +#define DSI_CONTROL_LANES(n) (((n) & 0x3) << 4) +#define DSI_CONTROL_DCS_ENABLE BIT(3) +#define DSI_CONTROL_SOURCE(s) (((s) & 0x1) << 2) +#define DSI_CONTROL_VIDEO_ENABLE BIT(1) +#define DSI_CONTROL_HOST_ENABLE BIT(0) + +#define DSI_TRIGGER_HOST BIT(1) +#define DSI_TRIGGER_VIDEO BIT(0) + +#define DSI_STATUS_IDLE BIT(10) +#define DSI_STATUS_UNDERFLOW BIT(9) +#define DSI_STATUS_OVERFLOW BIT(8) + +#define DSI_TIMING_FIELD(value, period, hwinc) \ + ((DIV_ROUND_CLOSEST(value, period) - (hwinc)) & 0xff) + +#define DSI_TIMEOUT_LRX(x) (((x) & 0xffff) << 16) +#define DSI_TIMEOUT_HTX(x) (((x) & 0xffff) << 0) +#define DSI_TIMEOUT_PR(x) (((x) & 0xffff) << 16) +#define DSI_TIMEOUT_TA(x) (((x) & 0xffff) << 0) + +#define DSI_TALLY_TA(x) (((x) & 0xff) << 16) +#define DSI_TALLY_LRX(x) (((x) & 0xff) << 8) +#define DSI_TALLY_HTX(x) (((x) & 0xff) << 0) + +#define DSI_PAD_CONTROL_PAD_PULLDN_ENAB(x) (((x) & 0x1) << 28) +#define DSI_PAD_CONTROL_PAD_SLEWUPADJ(x) (((x) & 0x7) << 24) +#define DSI_PAD_CONTROL_PAD_SLEWDNADJ(x) (((x) & 0x7) << 20) +#define DSI_PAD_CONTROL_PAD_PREEMP_EN(x) (((x) & 0x1) << 19) +#define DSI_PAD_CONTROL_PAD_PDIO_CLK(x) (((x) & 0x1) << 18) +#define DSI_PAD_CONTROL_PAD_PDIO(x) (((x) & 0x3) << 16) +#define DSI_PAD_CONTROL_PAD_LPUPADJ(x) (((x) & 0x3) << 14) +#define DSI_PAD_CONTROL_PAD_LPDNADJ(x) (((x) & 0x3) << 12) + +#define DSI_PAD_CONTROL_VS1_PDIO(x) (((x) & 0xf) << 0) +#define DSI_PAD_CONTROL_VS1_PULLDN(x) (((x) & 0xf) << 16) + +#define DSI_PAD_OUT_CLK(x) (((x) & 0x7) << 0) +#define DSI_PAD_LP_DN(x) (((x) & 0x7) << 4) +#define DSI_PAD_LP_UP(x) (((x) & 0x7) << 8) +#define DSI_PAD_SLEW_DN(x) (((x) & 0x7) << 12) +#define DSI_PAD_SLEW_UP(x) (((x) & 0x7) << 16) + +#define DSI_PAD_PREEMP_PD_CLK(x) (((x) & 0x3) << 12) +#define DSI_PAD_PREEMP_PU_CLK(x) (((x) & 0x3) << 8) +#define DSI_PAD_PREEMP_PD(x) (((x) & 0x3) << 4) +#define DSI_PAD_PREEMP_PU(x) (((x) & 0x3) << 0) + +#define DSI_GANGED_MODE_CONTROL_ENABLE BIT(0) + +/* + * pixel format as used in the DSI_CONTROL_FORMAT field + */ +enum tegra_dsi_format { + TEGRA_DSI_FORMAT_16P, + TEGRA_DSI_FORMAT_18NP, + TEGRA_DSI_FORMAT_18P, + TEGRA_DSI_FORMAT_24P, +}; + +/* DSI calibration in VI region */ +#define TEGRA_VI_BASE 0x54080000 + +#define CSI_CILA_MIPI_CAL_CONFIG_0 0x22a +#define MIPI_CAL_TERMOSA(x) (((x) & 0x1f) << 0) + +#define CSI_CILB_MIPI_CAL_CONFIG_0 0x22b +#define MIPI_CAL_TERMOSB(x) (((x) & 0x1f) << 0) + +#define CSI_CIL_PAD_CONFIG 0x229 +#define PAD_CIL_PDVREG(x) (((x) & 0x01) << 1) + +#define CSI_DSI_MIPI_CAL_CONFIG 0x234 +#define MIPI_CAL_HSPDOSD(x) (((x) & 0x1f) << 16) +#define MIPI_CAL_HSPUOSD(x) (((x) & 0x1f) << 8) + +#define CSI_MIPIBIAS_PAD_CONFIG 0x235 +#define PAD_DRIV_DN_REF(x) (((x) & 0x7) << 16) +#define PAD_DRIV_UP_REF(x) (((x) & 0x7) << 8) + +#endif /* _TEGRA_DSI_H */ diff --git a/drivers/video/tegra/tegra-hdmi.c b/drivers/video/tegra/tegra-hdmi.c new file mode 100644 index 00000000000..bda69919d92 --- /dev/null +++ b/drivers/video/tegra/tegra-hdmi.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 NVIDIA Corporation + * Copyright (c) 2023 Svyatoslav Ryhel <[email protected]> + */ + +#include <clk.h> +#include <dm.h> +#include <edid.h> +#include <i2c.h> +#include <log.h> +#include <misc.h> +#include <panel.h> +#include <reset.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/time.h> +#include <power/regulator.h> +#include <video_bridge.h> + +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/clock.h> + +#include "tegra-dc.h" +#include "tegra-hdmi.h" + +#define DDCCI_ENTRY_ADDR 0x37 +#define DDCCI_SOURSE_ADDR 0x51 +#define DDCCI_COMMAND_WRITE 0x03 +#define DDCCI_CTRL_BRIGHTNESS 0x10 + +#define HDMI_EDID_I2C_ADDR 0x50 +#define HDMI_REKEY_DEFAULT 56 + +static const char * const hdmi_supplies[] = { + "hdmi-supply", "pll-supply", "vdd-supply" +}; + +struct tmds_config { + unsigned int pclk; + u32 pll0; + u32 pll1; + u32 pe_current; + u32 drive_current; + u32 peak_current; +}; + +struct tegra_hdmi_config { + const struct tmds_config *tmds; + unsigned int num_tmds; + unsigned int max_pclk; + + /* to be filled */ +}; + +struct tegra_hdmi_priv { + struct hdmi_ctlr *hdmi_regmap; + + struct udevice *supplies[ARRAY_SIZE(hdmi_supplies)]; + struct udevice *hdmi_ddc; + + struct gpio_desc hpd; /* hotplug detection gpio */ + struct display_timing timing; + + struct clk *clk; + struct clk *clk_parent; + + int panel_bits_per_colourp; + const struct tegra_hdmi_config *config; +}; + +/* 1280x720p 60hz: EIA/CEA-861-B Format 4 */ +static struct display_timing default_720p_timing = { + .pixelclock.typ = 74250000, + .hactive.typ = 1280, + .hfront_porch.typ = 110, + .hback_porch.typ = 220, + .hsync_len.typ = 40, + .vactive.typ = 720, + .vfront_porch.typ = 5, + .vback_porch.typ = 20, + .vsync_len.typ = 5, + .flags = DISPLAY_FLAGS_HSYNC_HIGH | + DISPLAY_FLAGS_VSYNC_HIGH, +}; + +static const struct tmds_config tegra20_tmds_config[] = { + { /* slow pixel clock modes */ + .pclk = 27000000, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(0) | + SOR_PLL_TX_REG_LOAD(3), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE, + .pe_current = PE_CURRENT0(PE_CURRENT_0_0_mA) | + PE_CURRENT1(PE_CURRENT_0_0_mA) | + PE_CURRENT2(PE_CURRENT_0_0_mA) | + PE_CURRENT3(PE_CURRENT_0_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_7_125_mA), + }, + { /* high pixel clock modes */ + .pclk = UINT_MAX, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(1) | + SOR_PLL_TX_REG_LOAD(3), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, + .pe_current = PE_CURRENT0(PE_CURRENT_6_0_mA) | + PE_CURRENT1(PE_CURRENT_6_0_mA) | + PE_CURRENT2(PE_CURRENT_6_0_mA) | + PE_CURRENT3(PE_CURRENT_6_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_7_125_mA), + }, +}; + +static const struct tmds_config tegra30_tmds_config[] = { + { /* 480p modes */ + .pclk = 27000000, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(0) | + SOR_PLL_TX_REG_LOAD(0), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE, + .pe_current = PE_CURRENT0(PE_CURRENT_0_0_mA) | + PE_CURRENT1(PE_CURRENT_0_0_mA) | + PE_CURRENT2(PE_CURRENT_0_0_mA) | + PE_CURRENT3(PE_CURRENT_0_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), + }, { /* 720p modes */ + .pclk = 74250000, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(1) | + SOR_PLL_TX_REG_LOAD(0), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, + .pe_current = PE_CURRENT0(PE_CURRENT_5_0_mA) | + PE_CURRENT1(PE_CURRENT_5_0_mA) | + PE_CURRENT2(PE_CURRENT_5_0_mA) | + PE_CURRENT3(PE_CURRENT_5_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), + }, { /* 1080p modes */ + .pclk = UINT_MAX, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(3) | + SOR_PLL_TX_REG_LOAD(0), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, + .pe_current = PE_CURRENT0(PE_CURRENT_5_0_mA) | + PE_CURRENT1(PE_CURRENT_5_0_mA) | + PE_CURRENT2(PE_CURRENT_5_0_mA) | + PE_CURRENT3(PE_CURRENT_5_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), + }, +}; + +static void tegra_dc_enable_controller(struct udevice *dev) +{ + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct dc_ctlr *dc = dc_plat->dc; + u32 value; + + value = readl(&dc->disp.disp_win_opt); + value |= HDMI_ENABLE; + writel(value, &dc->disp.disp_win_opt); + + writel(GENERAL_UPDATE, &dc->cmd.state_ctrl); + writel(GENERAL_ACT_REQ, &dc->cmd.state_ctrl); +} + +static void tegra_hdmi_setup_tmds(struct tegra_hdmi_priv *priv, + const struct tmds_config *tmds) +{ + struct hdmi_ctlr *hdmi = priv->hdmi_regmap; + u32 value; + + writel(tmds->pll0, &hdmi->nv_pdisp_sor_pll0); + writel(tmds->pll1, &hdmi->nv_pdisp_sor_pll1); + writel(tmds->pe_current, &hdmi->nv_pdisp_pe_current); + + writel(tmds->drive_current, &hdmi->nv_pdisp_sor_lane_drive_current); + + value = readl(&hdmi->nv_pdisp_sor_lane_drive_current); + value |= BIT(31); + writel(value, &hdmi->nv_pdisp_sor_lane_drive_current); +} + +static int tegra_hdmi_encoder_enable(struct udevice *dev) +{ + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + struct dc_ctlr *dc = dc_plat->dc; + struct display_timing *dt = &priv->timing; + struct hdmi_ctlr *hdmi = priv->hdmi_regmap; + unsigned long rate, div82; + unsigned int pulse_start, rekey; + int retries = 1000; + u32 value; + int i; + + /* power up sequence */ + value = readl(&hdmi->nv_pdisp_sor_pll0); + value &= ~SOR_PLL_PDBG; + writel(value, &hdmi->nv_pdisp_sor_pll0); + + udelay(20); + + value = readl(&hdmi->nv_pdisp_sor_pll0); + value &= ~SOR_PLL_PWR; + writel(value, &hdmi->nv_pdisp_sor_pll0); + + writel(VSYNC_H_POSITION(1), &dc->disp.disp_timing_opt); + writel(DITHER_CONTROL_DISABLE | BASE_COLOR_SIZE_888, + &dc->disp.disp_color_ctrl); + + /* video_preamble uses h_pulse2 */ + pulse_start = 1 + dt->hsync_len.typ + dt->hback_porch.typ - 10; + + writel(H_PULSE2_ENABLE, &dc->disp.disp_signal_opt0); + + value = PULSE_MODE_NORMAL | PULSE_POLARITY_HIGH | + PULSE_QUAL_VACTIVE | PULSE_LAST_END_A; + writel(value, &dc->disp.h_pulse[H_PULSE2].h_pulse_ctrl); + + value = PULSE_START(pulse_start) | PULSE_END(pulse_start + 8); + writel(value, &dc->disp.h_pulse[H_PULSE2].h_pulse_pos[H_PULSE0_POSITION_A]); + + value = VSYNC_WINDOW_END(0x210) | VSYNC_WINDOW_START(0x200) | + VSYNC_WINDOW_ENABLE; + writel(value, &hdmi->nv_pdisp_hdmi_vsync_window); + + if (dc_plat->pipe) + value = HDMI_SRC_DISPLAYB; + else + value = HDMI_SRC_DISPLAYA; + + if (dt->hactive.typ == 720 && (dt->vactive.typ == 480 || + dt->vactive.typ == 576)) + writel(value | ARM_VIDEO_RANGE_FULL, + &hdmi->nv_pdisp_input_control); + else + writel(value | ARM_VIDEO_RANGE_LIMITED, + &hdmi->nv_pdisp_input_control); + + rate = clock_get_periph_rate(priv->clk->id, priv->clk_parent->id); + div82 = rate / USEC_PER_SEC * 4; + value = SOR_REFCLK_DIV_INT(div82 >> 2) | SOR_REFCLK_DIV_FRAC(div82); + writel(value, &hdmi->nv_pdisp_sor_refclk); + + rekey = HDMI_REKEY_DEFAULT; + value = HDMI_CTRL_REKEY(rekey); + value |= HDMI_CTRL_MAX_AC_PACKET((dt->hsync_len.typ + dt->hback_porch.typ + + dt->hfront_porch.typ - rekey - 18) / 32); + writel(value, &hdmi->nv_pdisp_hdmi_ctrl); + + /* TMDS CONFIG */ + for (i = 0; i < priv->config->num_tmds; i++) { + if (dt->pixelclock.typ <= priv->config->tmds[i].pclk) { + tegra_hdmi_setup_tmds(priv, &priv->config->tmds[i]); + break; + } + } + + writel(SOR_SEQ_PU_PC(0) | SOR_SEQ_PU_PC_ALT(0) | SOR_SEQ_PD_PC(8) | + SOR_SEQ_PD_PC_ALT(8), &hdmi->nv_pdisp_sor_seq_ctl); + + value = SOR_SEQ_INST_WAIT_TIME(1) | SOR_SEQ_INST_WAIT_UNITS_VSYNC | + SOR_SEQ_INST_HALT | SOR_SEQ_INST_PIN_A_LOW | + SOR_SEQ_INST_PIN_B_LOW | SOR_SEQ_INST_DRIVE_PWM_OUT_LO; + + writel(value, &hdmi->nv_pdisp_sor_seq_inst0); + writel(value, &hdmi->nv_pdisp_sor_seq_inst8); + + value = readl(&hdmi->nv_pdisp_sor_cstm); + + value &= ~SOR_CSTM_ROTCLK(~0); + value |= SOR_CSTM_ROTCLK(2); + value |= SOR_CSTM_PLLDIV; + value &= ~SOR_CSTM_LVDS_ENABLE; + value &= ~SOR_CSTM_MODE_MASK; + value |= SOR_CSTM_MODE_TMDS; + + writel(value, &hdmi->nv_pdisp_sor_cstm); + + /* start SOR */ + writel(SOR_PWR_NORMAL_STATE_PU | SOR_PWR_NORMAL_START_NORMAL | + SOR_PWR_SAFE_STATE_PD | SOR_PWR_SETTING_NEW_TRIGGER, + &hdmi->nv_pdisp_sor_pwr); + writel(SOR_PWR_NORMAL_STATE_PU | SOR_PWR_NORMAL_START_NORMAL | + SOR_PWR_SAFE_STATE_PD | SOR_PWR_SETTING_NEW_DONE, + &hdmi->nv_pdisp_sor_pwr); + + do { + if (--retries < 0) + return -ETIME; + value = readl(&hdmi->nv_pdisp_sor_pwr); + } while (value & SOR_PWR_SETTING_NEW_PENDING); + + value = SOR_STATE_ASY_CRCMODE_COMPLETE | + SOR_STATE_ASY_OWNER_HEAD0 | + SOR_STATE_ASY_SUBOWNER_BOTH | + SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A | + SOR_STATE_ASY_DEPOL_POS; + + /* setup sync polarities */ + if (dt->flags & DISPLAY_FLAGS_HSYNC_HIGH) + value |= SOR_STATE_ASY_HSYNCPOL_POS; + + if (dt->flags & DISPLAY_FLAGS_HSYNC_LOW) + value |= SOR_STATE_ASY_HSYNCPOL_NEG; + + if (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH) + value |= SOR_STATE_ASY_VSYNCPOL_POS; + + if (dt->flags & DISPLAY_FLAGS_VSYNC_LOW) + value |= SOR_STATE_ASY_VSYNCPOL_NEG; + + writel(value, &hdmi->nv_pdisp_sor_state2); + + value = SOR_STATE_ASY_HEAD_OPMODE_AWAKE | SOR_STATE_ASY_ORMODE_NORMAL; + writel(value, &hdmi->nv_pdisp_sor_state1); + + writel(0, &hdmi->nv_pdisp_sor_state0); + writel(SOR_STATE_UPDATE, &hdmi->nv_pdisp_sor_state0); + writel(value | SOR_STATE_ATTACHED, + &hdmi->nv_pdisp_sor_state1); + writel(0, &hdmi->nv_pdisp_sor_state0); + + tegra_dc_enable_controller(dev); + + return 0; +} + +/* DDC/CI backlight control */ +static int tegra_hdmi_set_connector(struct udevice *dev, int percent) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + struct udevice *ddc_entry; + struct i2c_msg msg[1]; + u8 checksum = DDCCI_ENTRY_ADDR << 1; + int i, ret; + + ret = dm_i2c_probe(priv->hdmi_ddc, DDCCI_ENTRY_ADDR, 0, &ddc_entry); + if (ret) { + log_debug("%s: cannot probe DDC/CI entry: error %d\n", + __func__, ret); + return 0; + } + + /* + * payload[1] is length: hithest bit OR last 4 bits indicate + * the number of following bytes (excluding checksum) + */ + u8 payload[7] = { DDCCI_SOURSE_ADDR, BIT(7) | (sizeof(payload) - 3), + DDCCI_COMMAND_WRITE, DDCCI_CTRL_BRIGHTNESS, + (u8)(percent & 0xff), (u8)(percent & 0xff), 0 }; + + /* DDC/CI checksum is a simple XOR of all preceding bytes */ + for (i = 0; i < (sizeof(payload) - 1); i++) + checksum ^= payload[i]; + + payload[6] = checksum; + + msg->addr = DDCCI_ENTRY_ADDR; + msg->flags = 0; + msg->len = sizeof(payload); + msg->buf = payload; + + dm_i2c_xfer(ddc_entry, msg, 1); + + return 0; +} + +static int tegra_hdmi_timings(struct udevice *dev, + struct display_timing *timing) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + + return 0; +} + +static void tegra_hdmi_init_clocks(struct udevice *dev) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + u32 n = priv->timing.pixelclock.typ * 2 / USEC_PER_SEC; + + switch (clock_get_osc_freq()) { + case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */ + case CLOCK_OSC_FREQ_48_0: /* OSC is 48Mhz */ + clock_set_rate(priv->clk_parent->id, n, 12, 0, 8); + break; + + case CLOCK_OSC_FREQ_26_0: /* OSC is 26Mhz */ + clock_set_rate(priv->clk_parent->id, n, 26, 0, 8); + break; + + case CLOCK_OSC_FREQ_13_0: /* OSC is 13Mhz */ + case CLOCK_OSC_FREQ_16_8: /* OSC is 16.8Mhz */ + clock_set_rate(priv->clk_parent->id, n, 13, 0, 8); + break; + + case CLOCK_OSC_FREQ_19_2: + case CLOCK_OSC_FREQ_38_4: + default: + /* + * These are not supported. + */ + break; + } + + clock_start_periph_pll(priv->clk->id, priv->clk_parent->id, + priv->timing.pixelclock.typ); +} + +static bool tegra_hdmi_mode_valid(void *hdmi_priv, const struct display_timing *timing) +{ + struct tegra_hdmi_priv *priv = hdmi_priv; + + if (timing->pixelclock.typ > priv->config->max_pclk) + return false; + + return true; +} + +static int tegra_hdmi_decode_edid(struct udevice *dev) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + struct udevice *hdmi_edid; + uchar edid_buf[EDID_SIZE] = { 0 }; + int i, ret; + + /* Poll for 1 sec in case EDID is not ready right after hpd */ + for (i = 0; i < 10; i++) { + ret = dm_i2c_probe(priv->hdmi_ddc, HDMI_EDID_I2C_ADDR, 0, + &hdmi_edid); + if (!ret) + break; + + mdelay(100); + } + if (ret) { + log_debug("%s: cannot probe EDID: error %d\n", + __func__, ret); + return ret; + } + + ret = dm_i2c_read(hdmi_edid, 0, edid_buf, sizeof(edid_buf)); + if (ret) { + log_debug("%s: cannot dump EDID buffer: error %d\n", + __func__, ret); + return ret; + } + + ret = edid_get_timing_validate(edid_buf, sizeof(edid_buf), &priv->timing, + &priv->panel_bits_per_colourp, + tegra_hdmi_mode_valid, priv); + if (ret) { + log_debug("%s: cannot decode EDID info: error %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int tegra_hdmi_wait_hpd(struct tegra_hdmi_priv *priv) +{ + int i; + + /* Poll 1 second for HPD signal */ + for (i = 0; i < 10; i++) { + if (dm_gpio_get_value(&priv->hpd)) + return 0; + + mdelay(100); + } + + return -ETIMEDOUT; +} + +static int tegra_hdmi_probe(struct udevice *dev) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + struct reset_ctl reset_ctl; + int i, ret; + + priv->hdmi_regmap = (struct hdmi_ctlr *)dev_read_addr_ptr(dev); + if (!priv->hdmi_regmap) { + log_debug("%s: no display controller address\n", __func__); + return -EINVAL; + } + + priv->config = (struct tegra_hdmi_config *)dev_get_driver_data(dev); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + log_debug("%s: Could not get HDMI clock: %ld\n", + __func__, PTR_ERR(priv->clk)); + return PTR_ERR(priv->clk); + } + + priv->clk_parent = devm_clk_get(dev, "parent"); + if (IS_ERR(priv->clk_parent)) { + log_debug("%s: Could not get HDMI clock parent: %ld\n", + __func__, PTR_ERR(priv->clk_parent)); + return PTR_ERR(priv->clk_parent); + } + + for (i = 0; i < ARRAY_SIZE(hdmi_supplies); i++) { + ret = device_get_supply_regulator(dev, hdmi_supplies[i], + &priv->supplies[i]); + if (ret) { + log_debug("%s: cannot get %s %d\n", __func__, + hdmi_supplies[i], ret); + if (ret != -ENOENT) + return log_ret(ret); + } + + ret = regulator_set_enable_if_allowed(priv->supplies[i], true); + if (ret && ret != -ENOSYS) { + log_debug("%s: cannot enable %s: error %d\n", + __func__, hdmi_supplies[i], ret); + return ret; + } + } + + ret = reset_get_by_name(dev, "hdmi", &reset_ctl); + if (ret) { + log_debug("%s: reset_get_by_name() failed: %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_I2C, dev, + "nvidia,ddc-i2c-bus", + &priv->hdmi_ddc); + if (ret) { + log_debug("%s: cannot get hdmi ddc i2c bus: error %d\n", + __func__, ret); + return ret; + } + + ret = gpio_request_by_name(dev, "nvidia,hpd-gpio", 0, + &priv->hpd, GPIOD_IS_IN); + if (ret) { + log_debug("%s: Could not decode hpd-gpios (%d)\n", + __func__, ret); + return ret; + } + + /* wait for connector */ + ret = tegra_hdmi_wait_hpd(priv); + if (ret) { + /* HPD failed, use default timings */ + memcpy(&priv->timing, &default_720p_timing, + sizeof(default_720p_timing)); + } else { + ret = tegra_hdmi_decode_edid(dev); + if (ret) + memcpy(&priv->timing, &default_720p_timing, + sizeof(default_720p_timing)); + } + + reset_assert(&reset_ctl); + tegra_hdmi_init_clocks(dev); + + mdelay(2); + reset_deassert(&reset_ctl); + + return 0; +} + +static const struct tegra_hdmi_config tegra20_hdmi_config = { + .tmds = tegra20_tmds_config, + .num_tmds = ARRAY_SIZE(tegra20_tmds_config), + .max_pclk = 148500000, /* 1080p */ +}; + +static const struct tegra_hdmi_config tegra30_hdmi_config = { + .tmds = tegra30_tmds_config, + .num_tmds = ARRAY_SIZE(tegra30_tmds_config), + .max_pclk = 148500000, /* 1080p */ +}; + +static const struct video_bridge_ops tegra_hdmi_ops = { + .attach = tegra_hdmi_encoder_enable, + .set_backlight = tegra_hdmi_set_connector, + .get_display_timing = tegra_hdmi_timings, +}; + +static const struct udevice_id tegra_hdmi_ids[] = { + { + .compatible = "nvidia,tegra20-hdmi", + .data = (ulong)&tegra20_hdmi_config + }, { + .compatible = "nvidia,tegra30-hdmi", + .data = (ulong)&tegra30_hdmi_config + }, { + /* sentinel */ + } +}; + +U_BOOT_DRIVER(tegra_hdmi) = { + .name = "tegra_hdmi", + .id = UCLASS_VIDEO_BRIDGE, + .of_match = tegra_hdmi_ids, + .ops = &tegra_hdmi_ops, + .probe = tegra_hdmi_probe, + .plat_auto = sizeof(struct tegra_dc_plat), + .priv_auto = sizeof(struct tegra_hdmi_priv), +}; diff --git a/drivers/video/tegra/tegra-hdmi.h b/drivers/video/tegra/tegra-hdmi.h new file mode 100644 index 00000000000..d17655973e3 --- /dev/null +++ b/drivers/video/tegra/tegra-hdmi.h @@ -0,0 +1,648 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation <www.nvidia.com> + */ + +#ifndef _TEGRA_HDMI_H +#define _TEGRA_HDMI_H + +#ifndef __ASSEMBLY__ +#include <linux/bitops.h> +#endif + +/* Register definitions for the Tegra high-definition multimedia interface */ + +/* High-Definition Multimedia Interface (HDMI_) regs */ +struct hdmi_ctlr { + /* Address 0x000 ~ 0x0d2 */ + uint ctxsw; /* _CTXSW */ /* 0x00 */ + + uint nv_pdisp_sor_state0; /* _NV_PDISP_SOR_STATE0 */ + uint nv_pdisp_sor_state1; /* _NV_PDISP_SOR_STATE1 */ + uint nv_pdisp_sor_state2; /* _NV_PDISP_SOR_STATE2 */ + + uint nv_pdisp_rg_hdcp_an_msb; /* _NV_PDISP_RG_HDCP_AN_MSB */ + uint nv_pdisp_rg_hdcp_an_lsb; /* _NV_PDISP_RG_HDCP_AN_LSB */ + uint nv_pdisp_rg_hdcp_cn_msb; /* _NV_PDISP_RG_HDCP_CN_MSB */ + uint nv_pdisp_rg_hdcp_cn_lsb; /* _NV_PDISP_RG_HDCP_CN_LSB */ + uint nv_pdisp_rg_hdcp_aksv_msb; /* _NV_PDISP_RG_HDCP_AKSV_MSB */ + uint nv_pdisp_rg_hdcp_aksv_lsb; /* _NV_PDISP_RG_HDCP_AKSV_LSB */ + uint nv_pdisp_rg_hdcp_bksv_msb; /* _NV_PDISP_RG_HDCP_BKSV_MSB */ + uint nv_pdisp_rg_hdcp_bksv_lsb; /* _NV_PDISP_RG_HDCP_BKSV_LSB */ + uint nv_pdisp_rg_hdcp_cksv_msb; /* _NV_PDISP_RG_HDCP_CKSV_MSB */ + uint nv_pdisp_rg_hdcp_cksv_lsb; /* _NV_PDISP_RG_HDCP_CKSV_LSB */ + uint nv_pdisp_rg_hdcp_dksv_msb; /* _NV_PDISP_RG_HDCP_DKSV_MSB */ + uint nv_pdisp_rg_hdcp_dksv_lsb; /* _NV_PDISP_RG_HDCP_DKSV_LSB */ + uint nv_pdisp_rg_hdcp_ctrl; /* _NV_PDISP_RG_HDCP_CTRL */ /* 0x10 */ + uint nv_pdisp_rg_hdcp_cmode; /* _NV_PDISP_RG_HDCP_CMODE */ + uint nv_pdisp_rg_hdcp_mprime_msb; /* _NV_PDISP_RG_HDCP_MPRIME_MSB */ + uint nv_pdisp_rg_hdcp_mprime_lsb; /* _NV_PDISP_RG_HDCP_MPRIME_LSB */ + uint nv_pdisp_rg_hdcp_sprime_msb; /* _NV_PDISP_RG_HDCP_SPRIME_MSB */ + uint nv_pdisp_rg_hdcp_sprime_lsb2; /* _NV_PDISP_RG_HDCP_SPRIME_LSB2 */ + uint nv_pdisp_rg_hdcp_sprime_lsb1; /* _NV_PDISP_RG_HDCP_SPRIME_LSB1 */ + uint nv_pdisp_rg_hdcp_ri; /* _NV_PDISP_RG_HDCP_RI */ + uint nv_pdisp_rg_hdcp_cs_msb; /* _NV_PDISP_RG_HDCP_CS_MSB */ + uint nv_pdisp_rg_hdcp_cs_lsb; /* _NV_PDISP_RG_HDCP_CS_LSB */ + + uint nv_pdisp_hdmi_audio_emu0; /* _NV_PDISP_HDMI_AUDIO_EMU0 */ + uint nv_pdisp_hdmi_audio_emu_rdata0; /* _NV_PDISP_HDMI_AUDIO_EMU_RDATA0 */ + uint nv_pdisp_hdmi_audio_emu1; /* _NV_PDISP_HDMI_AUDIO_EMU1 */ + uint nv_pdisp_hdmi_audio_emu2; /* _NV_PDISP_HDMI_AUDIO_EMU2 */ + uint nv_pdisp_hdmi_audio_infoframe_ctrl; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL */ + uint nv_pdisp_hdmi_audio_infoframe_status; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_STATUS */ + uint nv_pdisp_hdmi_audio_infoframe_header; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER */ /* 0x20 */ + uint nv_pdisp_hdmi_audio_infoframe_subpack0_low; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW */ + uint nv_pdisp_hdmi_audio_infoframe_subpack0_high; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH */ + + uint nv_pdisp_hdmi_avi_infoframe_ctrl; /* _NV_PDISP_HDMI_AVI_INFOFRAME_CTRL */ + uint nv_pdisp_hdmi_avi_infoframe_status; /* _NV_PDISP_HDMI_AVI_INFOFRAME_STATUS */ + uint nv_pdisp_hdmi_avi_infoframe_header; /* _NV_PDISP_HDMI_AVI_INFOFRAME_HEADER */ + uint nv_pdisp_hdmi_avi_infoframe_subpack0_low; /* _NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW */ + uint nv_pdisp_hdmi_avi_infoframe_subpack0_high; /* _NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH */ + uint nv_pdisp_hdmi_avi_infoframe_subpack1_low; /* _NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW */ + uint nv_pdisp_hdmi_avi_infoframe_subpack1_high; /* _NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH */ + + uint nv_pdisp_hdmi_generic_ctrl; /* _NV_PDISP_HDMI_GENERIC_CTRL */ + uint nv_pdisp_hdmi_generic_status; /* _NV_PDISP_HDMI_GENERIC_STATUS */ + uint nv_pdisp_hdmi_generic_header; /* _NV_PDISP_HDMI_GENERIC_HEADER */ + uint nv_pdisp_hdmi_generic_subpack0_low; /* _NV_PDISP_HDMI_GENERIC_SUBPACK0_LOW */ + uint nv_pdisp_hdmi_generic_subpack0_high; /* _NV_PDISP_HDMI_GENERIC_SUBPACK0_HIGH */ + uint nv_pdisp_hdmi_generic_subpack1_low; /* _NV_PDISP_HDMI_GENERIC_SUBPACK1_LOW */ + uint nv_pdisp_hdmi_generic_subpack1_high; /* _NV_PDISP_HDMI_GENERIC_SUBPACK1_HIGH */ + uint nv_pdisp_hdmi_generic_subpack2_low; /* _NV_PDISP_HDMI_GENERIC_SUBPACK2_LOW */ + uint nv_pdisp_hdmi_generic_subpack2_high; /* _NV_PDISP_HDMI_GENERIC_SUBPACK2_HIGH */ + uint nv_pdisp_hdmi_generic_subpack3_low; /* _NV_PDISP_HDMI_GENERIC_SUBPACK3_LOW */ + uint nv_pdisp_hdmi_generic_subpack3_high; /* _NV_PDISP_HDMI_GENERIC_SUBPACK3_HIGH */ + + uint nv_pdisp_hdmi_acr_ctrl; /* _NV_PDISP_HDMI_ACR_CTRL */ + uint nv_pdisp_hdmi_acr_0320_subpack_low; /* _NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0320_subpack_high; /* _NV_PDISP_HDMI_ACR_0320_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_0441_subpack_low; /* _NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0441_subpack_high; /* _NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_0882_subpack_low; /* _NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0882_subpack_high; /* _NV_PDISP_HDMI_ACR_0882_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_1764_subpack_low; /* _NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_1764_subpack_high; /* _NV_PDISP_HDMI_ACR_1764_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_0480_subpack_low; /* _NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0480_subpack_high; /* _NV_PDISP_HDMI_ACR_0480_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_0960_subpack_low; /* _NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0960_subpack_high; /* _NV_PDISP_HDMI_ACR_0960_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_1920_subpack_low; /* _NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_1920_subpack_high; /* _NV_PDISP_HDMI_ACR_1920_SUBPACK_HIGH */ + + uint nv_pdisp_hdmi_ctrl; /* _NV_PDISP_HDMI_CTRL */ + uint nv_pdisp_hdmi_vsync_keepout; /* _NV_PDISP_HDMI_VSYNC_KEEPOUT */ + uint nv_pdisp_hdmi_vsync_window; /* _NV_PDISP_HDMI_VSYNC_WINDOW */ + uint nv_pdisp_hdmi_gcp_ctrl; /* _NV_PDISP_HDMI_GCP_CTRL */ + uint nv_pdisp_hdmi_gcp_status; /* _NV_PDISP_HDMI_GCP_STATUS */ + uint nv_pdisp_hdmi_gcp_subpack; /* _NV_PDISP_HDMI_GCP_SUBPACK */ + uint nv_pdisp_hdmi_channel_status1; /* _NV_PDISP_HDMI_CHANNEL_STATUS1 */ + uint nv_pdisp_hdmi_channel_status2; /* _NV_PDISP_HDMI_CHANNEL_STATUS2 */ + uint nv_pdisp_hdmi_emu0; /* _NV_PDISP_HDMI_EMU0 */ + uint nv_pdisp_hdmi_emu1; /* _NV_PDISP_HDMI_EMU1 */ + uint nv_pdisp_hdmi_emu1_rdata; /* _NV_PDISP_HDMI_EMU1_RDATA */ + uint nv_pdisp_hdmi_spare; /* _NV_PDISP_HDMI_SPARE */ + uint nv_pdisp_hdmi_spdif_chn_status1; /* _NV_PDISP_HDMI_SPDIF_CHN_STATUS1 */ + uint nv_pdisp_hdmi_spdif_chn_status2; /* _NV_PDISP_HDMI_SPDIF_CHN_STATUS2 */ + + uint nv_pdisp_hdcprif_rom_ctrl; /* _NV_PDISP_HDCPRIF_ROM_CTRL */ + + uint unused; + + uint nv_pdisp_sor_cap; /* _NV_PDISP_SOR_CAP */ + uint nv_pdisp_sor_pwr; /* _NV_PDISP_SOR_PWR */ + uint nv_pdisp_sor_test; /* _NV_PDISP_SOR_TEST */ + uint nv_pdisp_sor_pll0; /* _NV_PDISP_SOR_PLL0 */ + uint nv_pdisp_sor_pll1; /* _NV_PDISP_SOR_PLL1 */ + uint nv_pdisp_sor_pll2; /* _NV_PDISP_SOR_PLL2 */ + uint nv_pdisp_sor_cstm; /* _NV_PDISP_SOR_CSTM */ + uint nv_pdisp_sor_lvds; /* _NV_PDISP_SOR_LVDS */ + uint nv_pdisp_sor_crca; /* _NV_PDISP_SOR_CRCA */ + uint nv_pdisp_sor_crcb; /* _NV_PDISP_SOR_CRCB */ + uint nv_pdisp_sor_blank; /* _NV_PDISP_SOR_BLANK */ + + uint nv_pdisp_sor_seq_ctl; /* _NV_PDISP_SOR_SEQ_CTL */ + uint nv_pdisp_sor_seq_inst0; /* _NV_PDISP_SOR_SEQ_INST0 */ + uint nv_pdisp_sor_seq_inst1; /* _NV_PDISP_SOR_SEQ_INST1 */ + uint nv_pdisp_sor_seq_inst2; /* _NV_PDISP_SOR_SEQ_INST2 */ + uint nv_pdisp_sor_seq_inst3; /* _NV_PDISP_SOR_SEQ_INST3 */ + uint nv_pdisp_sor_seq_inst4; /* _NV_PDISP_SOR_SEQ_INST4 */ + uint nv_pdisp_sor_seq_inst5; /* _NV_PDISP_SOR_SEQ_INST5 */ + uint nv_pdisp_sor_seq_inst6; /* _NV_PDISP_SOR_SEQ_INST6 */ + uint nv_pdisp_sor_seq_inst7; /* _NV_PDISP_SOR_SEQ_INST7 */ + uint nv_pdisp_sor_seq_inst8; /* _NV_PDISP_SOR_SEQ_INST8 */ + uint nv_pdisp_sor_seq_inst9; /* _NV_PDISP_SOR_SEQ_INST9 */ + uint nv_pdisp_sor_seq_insta; /* _NV_PDISP_SOR_SEQ_INSTA */ + uint nv_pdisp_sor_seq_instb; /* _NV_PDISP_SOR_SEQ_INSTB */ + uint nv_pdisp_sor_seq_instc; /* _NV_PDISP_SOR_SEQ_INSTC */ + uint nv_pdisp_sor_seq_instd; /* _NV_PDISP_SOR_SEQ_INSTD */ + uint nv_pdisp_sor_seq_inste; /* _NV_PDISP_SOR_SEQ_INSTE */ + uint nv_pdisp_sor_seq_instf; /* _NV_PDISP_SOR_SEQ_INSTF */ + + uint unused1[2]; + + uint nv_pdisp_sor_vcrca0; /* _NV_PDISP_SOR_VCRCA0 */ + uint nv_pdisp_sor_vcrca1; /* _NV_PDISP_SOR_VCRCA1 */ + uint nv_pdisp_sor_ccrca0; /* _NV_PDISP_SOR_CCRCA0 */ + uint nv_pdisp_sor_ccrca1; /* _NV_PDISP_SOR_CCRCA1 */ + + uint nv_pdisp_sor_edataa0; /* _NV_PDISP_SOR_EDATAA0 */ + uint nv_pdisp_sor_edataa1; /* _NV_PDISP_SOR_EDATAA1 */ + + uint nv_pdisp_sor_counta0; /* _NV_PDISP_SOR_COUNTA0 */ + uint nv_pdisp_sor_counta1; /* _NV_PDISP_SOR_COUNTA1 */ + + uint nv_pdisp_sor_debuga0; /* _NV_PDISP_SOR_DEBUGA0 */ + uint nv_pdisp_sor_debuga1; /* _NV_PDISP_SOR_DEBUGA1 */ + + uint nv_pdisp_sor_trig; /* _NV_PDISP_SOR_TRIG */ + uint nv_pdisp_sor_mscheck; /* _NV_PDISP_SOR_MSCHECK */ + uint nv_pdisp_sor_lane_drive_current; /* _NV_PDISP_SOR_LANE_DRIVE_CURRENT */ + + uint nv_pdisp_audio_debug0; /* _NV_PDISP_AUDIO_DEBUG0 0x7f */ + uint nv_pdisp_audio_debug1; /* _NV_PDISP_AUDIO_DEBUG1 0x80 */ + uint nv_pdisp_audio_debug2; /* _NV_PDISP_AUDIO_DEBUG2 0x81 */ + + uint nv_pdisp_audio_fs1; /* _NV_PDISP_AUDIO_FS1 0x82 */ + uint nv_pdisp_audio_fs2; /* _NV_PDISP_AUDIO_FS2 */ + uint nv_pdisp_audio_fs3; /* _NV_PDISP_AUDIO_FS3 */ + uint nv_pdisp_audio_fs4; /* _NV_PDISP_AUDIO_FS4 */ + uint nv_pdisp_audio_fs5; /* _NV_PDISP_AUDIO_FS5 */ + uint nv_pdisp_audio_fs6; /* _NV_PDISP_AUDIO_FS6 */ + uint nv_pdisp_audio_fs7; /* _NV_PDISP_AUDIO_FS7 0x88 */ + + uint nv_pdisp_audio_pulse_width; /* _NV_PDISP_AUDIO_PULSE_WIDTH */ + uint nv_pdisp_audio_threshold; /* _NV_PDISP_AUDIO_THRESHOLD */ + uint nv_pdisp_audio_cntrl0; /* _NV_PDISP_AUDIO_CNTRL0 */ + uint nv_pdisp_audio_n; /* _NV_PDISP_AUDIO_N */ + uint nv_pdisp_audio_nval[7]; /* _NV_PDISP_AUDIO_NVAL */ + + uint nv_pdisp_hdcprif_rom_timing; /* _NV_PDISP_HDCPRIF_ROM_TIMING */ + uint nv_pdisp_sor_refclk; /* _NV_PDISP_SOR_REFCLK */ + uint nv_pdisp_crc_control; /* _NV_PDISP_CRC_CONTROL */ + uint nv_pdisp_input_control; /* _NV_PDISP_INPUT_CONTROL */ + uint nv_pdisp_scratch; /* _NV_PDISP_SCRATCH */ + uint nv_pdisp_pe_current; /* _NV_PDISP_PE_CURRENT */ + + uint nv_pdisp_key_ctrl; /* _NV_PDISP_KEY_CTRL */ + uint nv_pdisp_key_debug0; /* _NV_PDISP_KEY_DEBUG0 */ + uint nv_pdisp_key_debug1; /* _NV_PDISP_KEY_DEBUG1 */ + uint nv_pdisp_key_debug2; /* _NV_PDISP_KEY_DEBUG2 */ + uint nv_pdisp_key_hdcp_key_0; /* _NV_PDISP_KEY_HDCP_KEY_0 */ + uint nv_pdisp_key_hdcp_key_1; /* _NV_PDISP_KEY_HDCP_KEY_1 */ + uint nv_pdisp_key_hdcp_key_2; /* _NV_PDISP_KEY_HDCP_KEY_2 */ + uint nv_pdisp_key_hdcp_key_3; /* _NV_PDISP_KEY_HDCP_KEY_3 */ + uint nv_pdisp_key_hdcp_key_trig; /* _NV_PDISP_KEY_HDCP_KEY_3 */ + uint nv_pdisp_key_skey_index; /* _NV_PDISP_KEY_HDCP_KEY_3 */ /* 0xa3 */ + + uint unused2[8]; + + uint nv_pdisp_sor_audio_cntrl0; /* _NV_PDISP_SOR_AUDIO_CNTRL0 */ /* 0xac */ + uint nv_pdisp_sor_audio_debug; /* _NV_PDISP_SOR_AUDIO_DEBUG */ + uint nv_pdisp_sor_audio_spare0; /* _NV_PDISP_SOR_AUDIO_SPARE0 */ + uint nv_pdisp_sor_audio_nval[7]; /* _NV_PDISP_SOR_AUDIO_NVAL 0xaf ~ 0xb5 */ + uint nv_pdisp_sor_audio_hda_scratch[4]; /* _NV_PDISP_SOR_AUDIO_HDA_SCRATCH 0xb6 ~ 0xb9 */ + uint nv_pdisp_sor_audio_hda_codec_scratch[2]; /* _NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH 0xba ~ 0xbb */ + + uint nv_pdisp_sor_audio_hda_eld_bufwr; /* _NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR */ + uint nv_pdisp_sor_audio_hda_presense; /* _NV_PDISP_SOR_AUDIO_HDA_PRESENSE */ + uint nv_pdisp_sor_audio_hda_cp; /* _NV_PDISP_SOR_AUDIO_HDA_CP */ + uint nv_pdisp_sor_audio_aval[8]; /* _NV_PDISP_SOR_AUDIO_AVAL */ + uint nv_pdisp_sor_audio_gen_ctrl; /* _NV_PDISP_SOR_AUDIO_GEN_CTRL */ + + uint unused3[4]; + + uint nv_pdisp_int_status; /* _NV_PDISP_INT_STATUS */ + uint nv_pdisp_int_mask; /* _NV_PDISP_INT_MASK */ + uint nv_pdisp_int_enable; /* _NV_PDISP_INT_ENABLE */ + + uint unused4[2]; + + uint nv_pdisp_sor_io_peak_current; /* _NV_PDISP_SOR_IO_PEAK_CURRENT */ + uint nv_pdisp_sor_pad_ctls0; /* _NV_PDISP_SOR_PAD_CTLS0 */ +}; + +/* HDMI_NV_PDISP_SOR_STATE0 0x01 */ +#define SOR_STATE_UPDATE BIT(0) + +/* HDMI_NV_PDISP_SOR_STATE1 0x02 */ +#define SOR_STATE_ASY_HEAD_OPMODE_AWAKE BIT(1) +#define SOR_STATE_ASY_ORMODE_NORMAL BIT(2) +#define SOR_STATE_ATTACHED BIT(3) + +/* HDMI_NV_PDISP_SOR_STATE2 0x03 */ +#define SOR_STATE_ASY_OWNER_NONE (0 << 0) +#define SOR_STATE_ASY_OWNER_HEAD0 (1 << 0) +#define SOR_STATE_ASY_SUBOWNER_NONE (0 << 4) +#define SOR_STATE_ASY_SUBOWNER_SUBHEAD0 (1 << 4) +#define SOR_STATE_ASY_SUBOWNER_SUBHEAD1 (2 << 4) +#define SOR_STATE_ASY_SUBOWNER_BOTH (3 << 4) +#define SOR_STATE_ASY_CRCMODE_ACTIVE (0 << 6) +#define SOR_STATE_ASY_CRCMODE_COMPLETE (1 << 6) +#define SOR_STATE_ASY_CRCMODE_NON_ACTIVE (2 << 6) +#define SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A (1 << 8) +#define SOR_STATE_ASY_PROTOCOL_CUSTOM (15 << 8) +#define SOR_STATE_ASY_HSYNCPOL_POS (0 << 12) +#define SOR_STATE_ASY_HSYNCPOL_NEG (1 << 12) +#define SOR_STATE_ASY_VSYNCPOL_POS (0 << 13) +#define SOR_STATE_ASY_VSYNCPOL_NEG (1 << 13) +#define SOR_STATE_ASY_DEPOL_POS (0 << 14) +#define SOR_STATE_ASY_DEPOL_NEG (1 << 14) + +#define INFOFRAME_CTRL_ENABLE BIT(0) +#define INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0) +#define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8) +#define INFOFRAME_HEADER_LEN(x) (((x) & 0x0f) << 16) + +/* HDMI_NV_PDISP_HDMI_GENERIC_CTRL 0x2a */ +#define GENERIC_CTRL_ENABLE BIT(0) +#define GENERIC_CTRL_OTHER BIT(4) +#define GENERIC_CTRL_SINGLE BIT(8) +#define GENERIC_CTRL_HBLANK BIT(12) +#define GENERIC_CTRL_AUDIO BIT(16) + +/* HDMI_NV_PDISP_HDMI_ACR_* */ +#define ACR_SUBPACK_CTS(x) (((x) & 0xffffff) << 8) +#define ACR_SUBPACK_N(x) (((x) & 0xffffff) << 0) +#define ACR_ENABLE BIT(31) + +/* HDMI_NV_PDISP_HDMI_CTRL 0x44 */ +#define HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0) +#define HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16) +#define HDMI_CTRL_ENABLE BIT(30) + +/* HDMI_NV_PDISP_HDMI_VSYNC_* */ +#define VSYNC_WINDOW_END(x) (((x) & 0x3ff) << 0) +#define VSYNC_WINDOW_START(x) (((x) & 0x3ff) << 16) +#define VSYNC_WINDOW_ENABLE BIT(31) + +/* HDMI_NV_PDISP_HDMI_SPARE 0x4f */ +#define SPARE_HW_CTS BIT(0) +#define SPARE_FORCE_SW_CTS BIT(1) +#define SPARE_CTS_RESET_VAL(x) (((x) & 0x7) << 16) + +/* HDMI_NV_PDISP_SOR_PWR 0x55 */ +#define SOR_PWR_NORMAL_STATE_PD (0 << 0) +#define SOR_PWR_NORMAL_STATE_PU (1 << 0) +#define SOR_PWR_NORMAL_START_NORMAL (0 << 1) +#define SOR_PWR_NORMAL_START_ALT (1 << 1) +#define SOR_PWR_SAFE_STATE_PD (0 << 16) +#define SOR_PWR_SAFE_STATE_PU (1 << 16) +#define SOR_PWR_SETTING_NEW_DONE (0 << 31) +#define SOR_PWR_SETTING_NEW_PENDING (1 << 31) +#define SOR_PWR_SETTING_NEW_TRIGGER (1 << 31) + +/* HDMI_NV_PDISP_SOR_PLL0 0x57 */ +#define SOR_PLL_PWR BIT(0) +#define SOR_PLL_PDBG BIT(1) +#define SOR_PLL_VCAPD BIT(2) +#define SOR_PLL_PDPORT BIT(3) +#define SOR_PLL_RESISTORSEL BIT(4) +#define SOR_PLL_PULLDOWN BIT(5) +#define SOR_PLL_VCOCAP(x) (((x) & 0xf) << 8) +#define SOR_PLL_BG_V17_S(x) (((x) & 0xf) << 12) +#define SOR_PLL_FILTER(x) (((x) & 0xf) << 16) +#define SOR_PLL_ICHPMP(x) (((x) & 0xf) << 24) +#define SOR_PLL_TX_REG_LOAD(x) (((x) & 0xf) << 28) + +/* HDMI_NV_PDISP_SOR_PLL1 0x58 */ +#define SOR_PLL_TMDS_TERM_ENABLE BIT(8) +#define SOR_PLL_TMDS_TERMADJ(x) (((x) & 0xf) << 9) +#define SOR_PLL_LOADADJ(x) (((x) & 0xf) << 20) +#define SOR_PLL_PE_EN BIT(28) +#define SOR_PLL_HALF_FULL_PE BIT(29) +#define SOR_PLL_S_D_PIN_PE BIT(30) + +/* HDMI_NV_PDISP_SOR_CSTM 0x5a */ +#define SOR_CSTM_ROTCLK(x) (((x) & 0xf) << 24) +#define SOR_CSTM_PLLDIV BIT(21) +#define SOR_CSTM_LVDS_ENABLE BIT(16) +#define SOR_CSTM_MODE_LVDS (0 << 12) +#define SOR_CSTM_MODE_TMDS (1 << 12) +#define SOR_CSTM_MODE_MASK (3 << 12) + +/* HDMI_NV_PDISP_SOR_SEQ_CTL 0x5f */ +#define SOR_SEQ_PU_PC(x) (((x) & 0xf) << 0) +#define SOR_SEQ_PU_PC_ALT(x) (((x) & 0xf) << 4) +#define SOR_SEQ_PD_PC(x) (((x) & 0xf) << 8) +#define SOR_SEQ_PD_PC_ALT(x) (((x) & 0xf) << 12) +#define SOR_SEQ_PC(x) (((x) & 0xf) << 16) +#define SOR_SEQ_STATUS BIT(28) +#define SOR_SEQ_SWITCH BIT(30) + +/* HDMI_NV_PDISP_SOR_SEQ_INST(x) (0x60 + (x)) */ +#define SOR_SEQ_INST_WAIT_TIME(x) (((x) & 0x3ff) << 0) +#define SOR_SEQ_INST_WAIT_UNITS_VSYNC (2 << 12) +#define SOR_SEQ_INST_HALT (1 << 15) +#define SOR_SEQ_INST_PIN_A_LOW (0 << 21) +#define SOR_SEQ_INST_PIN_A_HIGH (1 << 21) +#define SOR_SEQ_INST_PIN_B_LOW (0 << 22) +#define SOR_SEQ_INST_PIN_B_HIGH (1 << 22) +#define SOR_SEQ_INST_DRIVE_PWM_OUT_LO (1 << 23) + +/* HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT 0x7e */ +#define DRIVE_CURRENT_LANE0(x) (((x) & 0x3f) << 0) +#define DRIVE_CURRENT_LANE1(x) (((x) & 0x3f) << 8) +#define DRIVE_CURRENT_LANE2(x) (((x) & 0x3f) << 16) +#define DRIVE_CURRENT_LANE3(x) (((x) & 0x3f) << 24) +#define DRIVE_CURRENT_LANE0_T114(x) (((x) & 0x7f) << 0) +#define DRIVE_CURRENT_LANE1_T114(x) (((x) & 0x7f) << 8) +#define DRIVE_CURRENT_LANE2_T114(x) (((x) & 0x7f) << 16) +#define DRIVE_CURRENT_LANE3_T114(x) (((x) & 0x7f) << 24) + +/* Drive current list */ +enum { + DRIVE_CURRENT_1_500_mA, + DRIVE_CURRENT_1_875_mA, + DRIVE_CURRENT_2_250_mA, + DRIVE_CURRENT_2_625_mA, + DRIVE_CURRENT_3_000_mA, + DRIVE_CURRENT_3_375_mA, + DRIVE_CURRENT_3_750_mA, + DRIVE_CURRENT_4_125_mA, + DRIVE_CURRENT_4_500_mA, + DRIVE_CURRENT_4_875_mA, + DRIVE_CURRENT_5_250_mA, + DRIVE_CURRENT_5_625_mA, + DRIVE_CURRENT_6_000_mA, + DRIVE_CURRENT_6_375_mA, + DRIVE_CURRENT_6_750_mA, + DRIVE_CURRENT_7_125_mA, + DRIVE_CURRENT_7_500_mA, + DRIVE_CURRENT_7_875_mA, + DRIVE_CURRENT_8_250_mA, + DRIVE_CURRENT_8_625_mA, + DRIVE_CURRENT_9_000_mA, + DRIVE_CURRENT_9_375_mA, + DRIVE_CURRENT_9_750_mA, + DRIVE_CURRENT_10_125_mA, + DRIVE_CURRENT_10_500_mA, + DRIVE_CURRENT_10_875_mA, + DRIVE_CURRENT_11_250_mA, + DRIVE_CURRENT_11_625_mA, + DRIVE_CURRENT_12_000_mA, + DRIVE_CURRENT_12_375_mA, + DRIVE_CURRENT_12_750_mA, + DRIVE_CURRENT_13_125_mA, + DRIVE_CURRENT_13_500_mA, + DRIVE_CURRENT_13_875_mA, + DRIVE_CURRENT_14_250_mA, + DRIVE_CURRENT_14_625_mA, + DRIVE_CURRENT_15_000_mA, + DRIVE_CURRENT_15_375_mA, + DRIVE_CURRENT_15_750_mA, + DRIVE_CURRENT_16_125_mA, + DRIVE_CURRENT_16_500_mA, + DRIVE_CURRENT_16_875_mA, + DRIVE_CURRENT_17_250_mA, + DRIVE_CURRENT_17_625_mA, + DRIVE_CURRENT_18_000_mA, + DRIVE_CURRENT_18_375_mA, + DRIVE_CURRENT_18_750_mA, + DRIVE_CURRENT_19_125_mA, + DRIVE_CURRENT_19_500_mA, + DRIVE_CURRENT_19_875_mA, + DRIVE_CURRENT_20_250_mA, + DRIVE_CURRENT_20_625_mA, + DRIVE_CURRENT_21_000_mA, + DRIVE_CURRENT_21_375_mA, + DRIVE_CURRENT_21_750_mA, + DRIVE_CURRENT_22_125_mA, + DRIVE_CURRENT_22_500_mA, + DRIVE_CURRENT_22_875_mA, + DRIVE_CURRENT_23_250_mA, + DRIVE_CURRENT_23_625_mA, + DRIVE_CURRENT_24_000_mA, + DRIVE_CURRENT_24_375_mA, + DRIVE_CURRENT_24_750_mA, +}; + +/* Drive current list for T114 */ +enum { + DRIVE_CURRENT_0_000_mA_T114, + DRIVE_CURRENT_0_400_mA_T114, + DRIVE_CURRENT_0_800_mA_T114, + DRIVE_CURRENT_1_200_mA_T114, + DRIVE_CURRENT_1_600_mA_T114, + DRIVE_CURRENT_2_000_mA_T114, + DRIVE_CURRENT_2_400_mA_T114, + DRIVE_CURRENT_2_800_mA_T114, + DRIVE_CURRENT_3_200_mA_T114, + DRIVE_CURRENT_3_600_mA_T114, + DRIVE_CURRENT_4_000_mA_T114, + DRIVE_CURRENT_4_400_mA_T114, + DRIVE_CURRENT_4_800_mA_T114, + DRIVE_CURRENT_5_200_mA_T114, + DRIVE_CURRENT_5_600_mA_T114, + DRIVE_CURRENT_6_000_mA_T114, + DRIVE_CURRENT_6_400_mA_T114, + DRIVE_CURRENT_6_800_mA_T114, + DRIVE_CURRENT_7_200_mA_T114, + DRIVE_CURRENT_7_600_mA_T114, + DRIVE_CURRENT_8_000_mA_T114, + DRIVE_CURRENT_8_400_mA_T114, + DRIVE_CURRENT_8_800_mA_T114, + DRIVE_CURRENT_9_200_mA_T114, + DRIVE_CURRENT_9_600_mA_T114, + DRIVE_CURRENT_10_000_mA_T114, + DRIVE_CURRENT_10_400_mA_T114, + DRIVE_CURRENT_10_800_mA_T114, + DRIVE_CURRENT_11_200_mA_T114, + DRIVE_CURRENT_11_600_mA_T114, + DRIVE_CURRENT_12_000_mA_T114, + DRIVE_CURRENT_12_400_mA_T114, + DRIVE_CURRENT_12_800_mA_T114, + DRIVE_CURRENT_13_200_mA_T114, + DRIVE_CURRENT_13_600_mA_T114, + DRIVE_CURRENT_14_000_mA_T114, + DRIVE_CURRENT_14_400_mA_T114, + DRIVE_CURRENT_14_800_mA_T114, + DRIVE_CURRENT_15_200_mA_T114, + DRIVE_CURRENT_15_600_mA_T114, + DRIVE_CURRENT_16_000_mA_T114, + DRIVE_CURRENT_16_400_mA_T114, + DRIVE_CURRENT_16_800_mA_T114, + DRIVE_CURRENT_17_200_mA_T114, + DRIVE_CURRENT_17_600_mA_T114, + DRIVE_CURRENT_18_000_mA_T114, + DRIVE_CURRENT_18_400_mA_T114, + DRIVE_CURRENT_18_800_mA_T114, + DRIVE_CURRENT_19_200_mA_T114, + DRIVE_CURRENT_19_600_mA_T114, + DRIVE_CURRENT_20_000_mA_T114, + DRIVE_CURRENT_20_400_mA_T114, + DRIVE_CURRENT_20_800_mA_T114, + DRIVE_CURRENT_21_200_mA_T114, + DRIVE_CURRENT_21_600_mA_T114, + DRIVE_CURRENT_22_000_mA_T114, + DRIVE_CURRENT_22_400_mA_T114, + DRIVE_CURRENT_22_800_mA_T114, + DRIVE_CURRENT_23_200_mA_T114, + DRIVE_CURRENT_23_600_mA_T114, + DRIVE_CURRENT_24_000_mA_T114, + DRIVE_CURRENT_24_400_mA_T114, + DRIVE_CURRENT_24_800_mA_T114, + DRIVE_CURRENT_25_200_mA_T114, + DRIVE_CURRENT_25_400_mA_T114, + DRIVE_CURRENT_25_800_mA_T114, + DRIVE_CURRENT_26_200_mA_T114, + DRIVE_CURRENT_26_600_mA_T114, + DRIVE_CURRENT_27_000_mA_T114, + DRIVE_CURRENT_27_400_mA_T114, + DRIVE_CURRENT_27_800_mA_T114, + DRIVE_CURRENT_28_200_mA_T114, +}; + +/* HDMI_NV_PDISP_AUDIO_FS */ +#define AUDIO_FS_LOW(x) (((x) & 0xfff) << 0) +#define AUDIO_FS_HIGH(x) (((x) & 0xfff) << 16) + +/* HDMI_NV_PDISP_AUDIO_CNTRL0 0x8b */ +#define AUDIO_CNTRL0_ERROR_TOLERANCE(x) (((x) & 0xff) << 0) +#define AUDIO_CNTRL0_SOURCE_SELECT_AUTO (0 << 20) +#define AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20) +#define AUDIO_CNTRL0_SOURCE_SELECT_HDAL (2 << 20) +#define AUDIO_CNTRL0_FRAMES_PER_BLOCK(x) (((x) & 0xff) << 24) + +/* HDMI_NV_PDISP_AUDIO_N 0x8c */ +#define AUDIO_N_VALUE(x) (((x) & 0xfffff) << 0) +#define AUDIO_N_RESETF (1 << 20) +#define AUDIO_N_GENERATE_NORMAL (0 << 24) +#define AUDIO_N_GENERATE_ALTERNATE (1 << 24) + +/* HDMI_NV_PDISP_SOR_REFCLK 0x95 */ +#define SOR_REFCLK_DIV_INT(x) (((x) & 0xff) << 8) +#define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x03) << 6) + +/* HDMI_NV_PDISP_INPUT_CONTROL 0x97 */ +#define HDMI_SRC_DISPLAYA (0 << 0) +#define HDMI_SRC_DISPLAYB (1 << 0) +#define ARM_VIDEO_RANGE_FULL (0 << 1) +#define ARM_VIDEO_RANGE_LIMITED (1 << 1) + +/* HDMI_NV_PDISP_PE_CURRENT 0x99 */ +#define PE_CURRENT0(x) (((x) & 0xf) << 0) +#define PE_CURRENT1(x) (((x) & 0xf) << 8) +#define PE_CURRENT2(x) (((x) & 0xf) << 16) +#define PE_CURRENT3(x) (((x) & 0xf) << 24) + +enum { + PE_CURRENT_0_0_mA, + PE_CURRENT_0_5_mA, + PE_CURRENT_1_0_mA, + PE_CURRENT_1_5_mA, + PE_CURRENT_2_0_mA, + PE_CURRENT_2_5_mA, + PE_CURRENT_3_0_mA, + PE_CURRENT_3_5_mA, + PE_CURRENT_4_0_mA, + PE_CURRENT_4_5_mA, + PE_CURRENT_5_0_mA, + PE_CURRENT_5_5_mA, + PE_CURRENT_6_0_mA, + PE_CURRENT_6_5_mA, + PE_CURRENT_7_0_mA, + PE_CURRENT_7_5_mA, +}; + +enum { + PE_CURRENT_0_mA_T114, + PE_CURRENT_1_mA_T114, + PE_CURRENT_2_mA_T114, + PE_CURRENT_3_mA_T114, + PE_CURRENT_4_mA_T114, + PE_CURRENT_5_mA_T114, + PE_CURRENT_6_mA_T114, + PE_CURRENT_7_mA_T114, + PE_CURRENT_8_mA_T114, + PE_CURRENT_9_mA_T114, + PE_CURRENT_10_mA_T114, + PE_CURRENT_11_mA_T114, + PE_CURRENT_12_mA_T114, + PE_CURRENT_13_mA_T114, + PE_CURRENT_14_mA_T114, + PE_CURRENT_15_mA_T114, +}; + +/* HDMI_NV_PDISP_SOR_AUDIO_CNTRL0 0xac */ +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_AUTO (0 << 20) +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20) +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_HDAL (2 << 20) +#define SOR_AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29) + +/* HDMI_NV_PDISP_SOR_AUDIO_SPARE0 0xae */ +#define SOR_AUDIO_SPARE0_HBR_ENABLE BIT(27) + +/* HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0 0xba */ +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID BIT(30) +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff + +/* HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE 0xbd */ +#define SOR_AUDIO_HDA_PRESENSE_VALID BIT(1) +#define SOR_AUDIO_HDA_PRESENSE_PRESENT BIT(0) + +/* HDMI_NV_PDISP_INT_STATUS 0xcc */ +#define INT_SCRATCH BIT(3) +#define INT_CP_REQUEST BIT(2) +#define INT_CODEC_SCRATCH1 BIT(1) +#define INT_CODEC_SCRATCH0 BIT(0) + +/* HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT 0xd1 */ +#define PEAK_CURRENT_LANE0(x) (((x) & 0x7f) << 0) +#define PEAK_CURRENT_LANE1(x) (((x) & 0x7f) << 8) +#define PEAK_CURRENT_LANE2(x) (((x) & 0x7f) << 16) +#define PEAK_CURRENT_LANE3(x) (((x) & 0x7f) << 24) + +enum { + PEAK_CURRENT_0_000_mA, + PEAK_CURRENT_0_200_mA, + PEAK_CURRENT_0_400_mA, + PEAK_CURRENT_0_600_mA, + PEAK_CURRENT_0_800_mA, + PEAK_CURRENT_1_000_mA, + PEAK_CURRENT_1_200_mA, + PEAK_CURRENT_1_400_mA, + PEAK_CURRENT_1_600_mA, + PEAK_CURRENT_1_800_mA, + PEAK_CURRENT_2_000_mA, + PEAK_CURRENT_2_200_mA, + PEAK_CURRENT_2_400_mA, + PEAK_CURRENT_2_600_mA, + PEAK_CURRENT_2_800_mA, + PEAK_CURRENT_3_000_mA, + PEAK_CURRENT_3_200_mA, + PEAK_CURRENT_3_400_mA, + PEAK_CURRENT_3_600_mA, + PEAK_CURRENT_3_800_mA, + PEAK_CURRENT_4_000_mA, + PEAK_CURRENT_4_200_mA, + PEAK_CURRENT_4_400_mA, + PEAK_CURRENT_4_600_mA, + PEAK_CURRENT_4_800_mA, + PEAK_CURRENT_5_000_mA, + PEAK_CURRENT_5_200_mA, + PEAK_CURRENT_5_400_mA, + PEAK_CURRENT_5_600_mA, + PEAK_CURRENT_5_800_mA, + PEAK_CURRENT_6_000_mA, + PEAK_CURRENT_6_200_mA, + PEAK_CURRENT_6_400_mA, + PEAK_CURRENT_6_600_mA, + PEAK_CURRENT_6_800_mA, + PEAK_CURRENT_7_000_mA, + PEAK_CURRENT_7_200_mA, + PEAK_CURRENT_7_400_mA, + PEAK_CURRENT_7_600_mA, + PEAK_CURRENT_7_800_mA, + PEAK_CURRENT_8_000_mA, + PEAK_CURRENT_8_200_mA, + PEAK_CURRENT_8_400_mA, + PEAK_CURRENT_8_600_mA, + PEAK_CURRENT_8_800_mA, + PEAK_CURRENT_9_000_mA, + PEAK_CURRENT_9_200_mA, + PEAK_CURRENT_9_400_mA, +}; + +#endif /* _TEGRA_HDMI_H */ diff --git a/drivers/video/tegra/tegra-host1x.c b/drivers/video/tegra/tegra-host1x.c new file mode 100644 index 00000000000..58ab871a3b4 --- /dev/null +++ b/drivers/video/tegra/tegra-host1x.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 Svyatoslav Ryhel <[email protected]> + */ + +#include <dm.h> +#include <clk.h> +#include <log.h> +#include <reset.h> +#include <linux/delay.h> + +#include <asm/arch/clock.h> +#include <asm/arch-tegra/clk_rst.h> + +struct tegra_host1x_info { + u32 clk_parent; + u32 rate; +}; + +static int tegra_host1x_probe(struct udevice *dev) +{ + struct clk *clk; + struct reset_ctl reset_ctl; + const struct tegra_host1x_info *info; + int ret; + + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) { + log_debug("%s: cannot get HOST1X clock: %ld\n", + __func__, PTR_ERR(clk)); + return PTR_ERR(clk); + } + + ret = reset_get_by_name(dev, "host1x", &reset_ctl); + if (ret) { + log_debug("%s: cannot get HOST1X reset: %d\n", + __func__, ret); + return ret; + } + + info = (struct tegra_host1x_info *)dev_get_driver_data(dev); + + reset_assert(&reset_ctl); + clock_start_periph_pll(clk->id, info->clk_parent, info->rate); + + mdelay(2); + reset_deassert(&reset_ctl); + + return 0; +} + +static const struct tegra_host1x_info tegra20_host1x_info = { + .clk_parent = CLOCK_ID_CGENERAL, + .rate = 150000000, /* 150 MHz */ +}; + +static const struct tegra_host1x_info tegra114_host1x_info = { + .clk_parent = CLOCK_ID_PERIPH, + .rate = 136000000, /* 136 MHz */ +}; + +static const struct udevice_id tegra_host1x_ids[] = { + { + .compatible = "nvidia,tegra20-host1x", + .data = (ulong)&tegra20_host1x_info + }, { + .compatible = "nvidia,tegra30-host1x", + .data = (ulong)&tegra20_host1x_info + }, { + .compatible = "nvidia,tegra114-host1x", + .data = (ulong)&tegra114_host1x_info + }, { + .compatible = "nvidia,tegra124-host1x", + .data = (ulong)&tegra114_host1x_info + }, { + /* sentinel */ + } +}; + +U_BOOT_DRIVER(tegra_host1x) = { + .name = "tegra_host1x", + .id = UCLASS_SIMPLE_BUS, + .of_match = tegra_host1x_ids, + .probe = tegra_host1x_probe, + .flags = DM_FLAG_PRE_RELOC, +}; diff --git a/drivers/video/tegra/tegra-mipi.c b/drivers/video/tegra/tegra-mipi.c new file mode 100644 index 00000000000..a4f4343d008 --- /dev/null +++ b/drivers/video/tegra/tegra-mipi.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 NVIDIA Corporation + * Copyright (c) 2023 Svyatoslav Ryhel <[email protected]> + */ + +#include <dm.h> +#include <clk.h> +#include <misc.h> +#include <linux/delay.h> +#include <linux/iopoll.h> + +#include <asm/arch/clock.h> +#include <asm/io.h> + +/* MIPI control registers 0x00 ~ 0x74 */ +struct mipi_ctlr { + uint mipi_cal_ctrl; + uint mipi_cal_autocal_ctrl; + uint mipi_cal_status; + + uint unused1[2]; + + uint mipi_cal_config_csia; + uint mipi_cal_config_csib; + uint mipi_cal_config_csic; + uint mipi_cal_config_csid; + uint mipi_cal_config_csie; + + uint unused2[4]; + + uint mipi_cal_config_dsia; + uint mipi_cal_config_dsib; + uint mipi_cal_config_dsic; + uint mipi_cal_config_dsid; + + uint unused3[4]; + + uint mipi_cal_bias_pad_cfg0; + uint mipi_cal_bias_pad_cfg1; + uint mipi_cal_bias_pad_cfg2; + + uint mipi_cal_dsia_config_2; + uint mipi_cal_dsib_config_2; + uint mipi_cal_cilc_config_2; + uint mipi_cal_cild_config_2; + uint mipi_cal_csie_config_2; +}; + +#define MIPI_DSIA_PADS 0x60 +#define MIPI_DSIB_PADS 0x180 + +#define MIPI_CAL_CTRL_NOISE_FILTER(x) (((x) & 0xf) << 26) +#define MIPI_CAL_CTRL_PRESCALE(x) (((x) & 0x3) << 24) +#define MIPI_CAL_CTRL_CLKEN_OVR BIT(4) +#define MIPI_CAL_CTRL_START BIT(0) + +#define MIPI_CAL_STATUS_DONE BIT(16) +#define MIPI_CAL_STATUS_ACTIVE BIT(0) + +#define MIPI_CAL_OVERIDE(x) (((x) & 0x1) << 30) +#define MIPI_CAL_SEL(x) (((x) & 0x1) << 21) +#define MIPI_CAL_HSPDOS(x) (((x) & 0x1f) << 16) +#define MIPI_CAL_HSPUOS(x) (((x) & 0x1f) << 8) +#define MIPI_CAL_TERMOS(x) (((x) & 0x1f) << 0) + +#define MIPI_CAL_BIAS_PAD_PDVCLAMP BIT(1) +#define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF BIT(0) + +#define MIPI_CAL_BIAS_PAD_DRV_DN_REF(x) (((x) & 0x7) << 16) +#define MIPI_CAL_BIAS_PAD_DRV_UP_REF(x) (((x) & 0x7) << 8) + +#define MIPI_CAL_BIAS_PAD_VCLAMP(x) (((x) & 0x7) << 16) +#define MIPI_CAL_BIAS_PAD_VAUXP(x) (((x) & 0x7) << 4) +#define MIPI_CAL_BIAS_PAD_PDVREG BIT(1) + +#define MIPI_CAL_HSCLKPDOSDSI(x) (((x) & 0x1f) << 8) +#define MIPI_CAL_HSCLKPUOSDSI(x) (((x) & 0x1f) << 0) + +struct tegra_mipi_priv { + struct mipi_ctlr *mipi; + struct clk *mipi_cal; + u32 version; +}; + +enum { + T114, + T124, +}; + +static void tegra114_mipi_pads_cal(struct tegra_mipi_priv *priv, + int calibration_pads) +{ + u32 value; + + value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x4) | + MIPI_CAL_TERMOS(0x5); + writel(value, &priv->mipi->mipi_cal_config_dsia); + writel(value, &priv->mipi->mipi_cal_config_dsib); + + /* Deselect PAD C */ + value = readl(&priv->mipi->mipi_cal_config_dsic); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_config_dsic); + + /* Deselect PAD D */ + value = readl(&priv->mipi->mipi_cal_config_dsid); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_config_dsid); +} + +static void tegra124_mipi_pads_cal(struct tegra_mipi_priv *priv, + int calibration_pads) +{ + u32 value; + + /* Calibrate DSI-A */ + if (calibration_pads == MIPI_DSIA_PADS) { + printf("Calibrating DSI-A pads\n"); + + value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x0) | + MIPI_CAL_TERMOS(0x0); + writel(value, &priv->mipi->mipi_cal_config_dsia); + writel(value, &priv->mipi->mipi_cal_config_dsib); + + value = MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSCLKPDOSDSI(0x1) | + MIPI_CAL_HSCLKPUOSDSI(0x2); + writel(value, &priv->mipi->mipi_cal_dsia_config_2); + writel(value, &priv->mipi->mipi_cal_dsib_config_2); + + /* Deselect PAD C */ + value = readl(&priv->mipi->mipi_cal_cilc_config_2); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_cilc_config_2); + + /* Deselect PAD D */ + value = readl(&priv->mipi->mipi_cal_cild_config_2); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_cild_config_2); + } + + /* Calibrate DSI-B */ + if (calibration_pads == MIPI_DSIB_PADS) { + printf("Calibrating DSI-B pads\n"); + + value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x0) | + MIPI_CAL_TERMOS(0x0); + writel(value, &priv->mipi->mipi_cal_config_csic); + writel(value, &priv->mipi->mipi_cal_config_csid); + + value = MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSCLKPDOSDSI(0x1) | + MIPI_CAL_HSCLKPUOSDSI(0x2); + writel(value, &priv->mipi->mipi_cal_cilc_config_2); + writel(value, &priv->mipi->mipi_cal_cild_config_2); + + /* Deselect PAD A */ + value = readl(&priv->mipi->mipi_cal_dsia_config_2); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_dsia_config_2); + + /* Deselect PAD B */ + value = readl(&priv->mipi->mipi_cal_dsib_config_2); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_dsib_config_2); + } +} + +static int tegra_mipi_calibrate(struct udevice *dev, int offset, const void *buf, + int size) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + u32 value; + + value = MIPI_CAL_BIAS_PAD_DRV_DN_REF(0x2) | + MIPI_CAL_BIAS_PAD_DRV_UP_REF(0x0); + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg1); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2); + value &= ~MIPI_CAL_BIAS_PAD_VCLAMP(0x7); + value &= ~MIPI_CAL_BIAS_PAD_VAUXP(0x7); + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2); + + switch (priv->version) { + case T114: + tegra114_mipi_pads_cal(priv, offset); + break; + + case T124: + tegra124_mipi_pads_cal(priv, offset); + break; + + default: + return -EINVAL; + } + + value = readl(&priv->mipi->mipi_cal_ctrl); + value &= ~MIPI_CAL_CTRL_NOISE_FILTER(0xf); + value &= ~MIPI_CAL_CTRL_PRESCALE(0x3); + value |= MIPI_CAL_CTRL_NOISE_FILTER(0xa) | + MIPI_CAL_CTRL_PRESCALE(0x2) | + MIPI_CAL_CTRL_CLKEN_OVR; + writel(value, &priv->mipi->mipi_cal_ctrl); + + /* clear any pending status bits */ + value = readl(&priv->mipi->mipi_cal_status); + writel(value, &priv->mipi->mipi_cal_status); + + value = readl(&priv->mipi->mipi_cal_ctrl); + value |= MIPI_CAL_CTRL_START; + writel(value, &priv->mipi->mipi_cal_ctrl); + + /* + * Wait for min 72uS to let calibration logic finish calibration + * sequence codes before waiting for pads idle state to apply the + * results. + */ + udelay(80); + + return readl_poll_sleep_timeout(&priv->mipi->mipi_cal_status, value, + !(value & MIPI_CAL_STATUS_ACTIVE) && + (value & MIPI_CAL_STATUS_DONE), 100, + 250000); +} + +static int tegra_mipi_enable(struct udevice *dev, bool val) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + u32 value; + + reset_set_enable(priv->mipi_cal->id, 1); + mdelay(100); + reset_set_enable(priv->mipi_cal->id, 0); + mdelay(1); + + clk_enable(priv->mipi_cal); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg0); + value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP; + value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF; + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg0); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2); + value &= ~MIPI_CAL_BIAS_PAD_PDVREG; + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2); + + return 0; +} + +static const struct misc_ops tegra_mipi_ops = { + .write = tegra_mipi_calibrate, + .set_enabled = tegra_mipi_enable, +}; + +static int tegra_mipi_probe(struct udevice *dev) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + + priv->version = dev_get_driver_data(dev); + + priv->mipi = (struct mipi_ctlr *)dev_read_addr_ptr(dev); + if (!priv->mipi) { + log_debug("%s: no MIPI controller address\n", __func__); + return -EINVAL; + } + + priv->mipi_cal = devm_clk_get(dev, NULL); + if (IS_ERR(priv->mipi_cal)) { + log_debug("%s: Could not get MIPI clock: %ld\n", + __func__, PTR_ERR(priv->mipi_cal)); + return PTR_ERR(priv->mipi_cal); + } + + return 0; +} + +static const struct udevice_id tegra_mipi_ids[] = { + { .compatible = "nvidia,tegra114-mipi", .data = T114 }, + { .compatible = "nvidia,tegra124-mipi", .data = T124 }, + { } +}; + +U_BOOT_DRIVER(tegra_mipi) = { + .name = "tegra_mipi", + .id = UCLASS_MISC, + .ops = &tegra_mipi_ops, + .of_match = tegra_mipi_ids, + .probe = tegra_mipi_probe, + .priv_auto = sizeof(struct tegra_mipi_priv), +}; diff --git a/drivers/video/tegra/tegra-pwm-backlight.c b/drivers/video/tegra/tegra-pwm-backlight.c new file mode 100644 index 00000000000..998f0df1991 --- /dev/null +++ b/drivers/video/tegra/tegra-pwm-backlight.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2022 Svyatoslav Ryhel <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_PANEL_BACKLIGHT + +#include <backlight.h> +#include <dm.h> +#include <i2c.h> +#include <log.h> +#include <linux/delay.h> +#include <linux/err.h> + +#include <asm/io.h> +#include <asm/gpio.h> + +#include "tegra-dc.h" + +#define TEGRA_PWM_BL_MIN_BRIGHTNESS 0x10 +#define TEGRA_PWM_BL_MAX_BRIGHTNESS 0xFF + +#define TEGRA_PWM_BL_PERIOD 0xFF +#define TEGRA_PWM_BL_CLK_DIV 0x14 +#define TEGRA_PWM_BL_CLK_SELECT 0x00 + +#define PM_PERIOD_SHIFT 18 +#define PM_CLK_DIVIDER_SHIFT 4 + +#define TEGRA_PWM_PM0 0 +#define TEGRA_PWM_PM1 1 + +struct tegra_pwm_backlight_priv { + struct dc_ctlr *dc; /* Display controller regmap */ + + u32 pwm_source; + u32 period; + u32 clk_div; + u32 clk_select; + u32 dft_brightness; +}; + +static int tegra_pwm_backlight_set_brightness(struct udevice *dev, int percent) +{ + struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev); + struct dc_cmd_reg *cmd = &priv->dc->cmd; + struct dc_com_reg *com = &priv->dc->com; + unsigned int ctrl; + unsigned long out_sel; + unsigned long cmd_state; + + if (percent == BACKLIGHT_DEFAULT) + percent = priv->dft_brightness; + + if (percent < TEGRA_PWM_BL_MIN_BRIGHTNESS) + percent = TEGRA_PWM_BL_MIN_BRIGHTNESS; + + if (percent > TEGRA_PWM_BL_MAX_BRIGHTNESS) + percent = TEGRA_PWM_BL_MAX_BRIGHTNESS; + + ctrl = ((priv->period << PM_PERIOD_SHIFT) | + (priv->clk_div << PM_CLK_DIVIDER_SHIFT) | + priv->clk_select); + + /* The new value should be effected immediately */ + cmd_state = readl(&cmd->state_access); + writel((cmd_state | (1 << 2)), &cmd->state_access); + + switch (priv->pwm_source) { + case TEGRA_PWM_PM0: + /* Select the LM0 on PM0 */ + out_sel = readl(&com->pin_output_sel[5]); + out_sel &= ~(7 << 0); + out_sel |= (3 << 0); + writel(out_sel, &com->pin_output_sel[5]); + writel(ctrl, &com->pm0_ctrl); + writel(percent, &com->pm0_duty_cycle); + break; + case TEGRA_PWM_PM1: + /* Select the LM1 on PM1 */ + out_sel = readl(&com->pin_output_sel[5]); + out_sel &= ~(7 << 4); + out_sel |= (3 << 4); + writel(out_sel, &com->pin_output_sel[5]); + writel(ctrl, &com->pm1_ctrl); + writel(percent, &com->pm1_duty_cycle); + break; + default: + break; + } + + writel(cmd_state, &cmd->state_access); + return 0; +} + +static int tegra_pwm_backlight_enable(struct udevice *dev) +{ + struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev); + + return tegra_pwm_backlight_set_brightness(dev, priv->dft_brightness); +} + +static int tegra_pwm_backlight_probe(struct udevice *dev) +{ + struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev); + ofnode dc = ofnode_get_parent(dev_ofnode(dev)); + + priv->dc = (struct dc_ctlr *)ofnode_get_addr(dc); + if (!priv->dc) { + log_err("%s: failed to get DC controller\n", __func__); + return -EINVAL; + } + + priv->pwm_source = + dev_read_u32_default(dev, "nvidia,pwm-source", + TEGRA_PWM_PM0); + priv->period = + dev_read_u32_default(dev, "nvidia,period", + TEGRA_PWM_BL_PERIOD); + priv->clk_div = + dev_read_u32_default(dev, "nvidia,clock-div", + TEGRA_PWM_BL_CLK_DIV); + priv->clk_select = + dev_read_u32_default(dev, "nvidia,clock-select", + TEGRA_PWM_BL_CLK_SELECT); + priv->dft_brightness = + dev_read_u32_default(dev, "nvidia,default-brightness", + TEGRA_PWM_BL_MAX_BRIGHTNESS); + + return 0; +} + +static const struct backlight_ops tegra_pwm_backlight_ops = { + .enable = tegra_pwm_backlight_enable, + .set_brightness = tegra_pwm_backlight_set_brightness, +}; + +static const struct udevice_id tegra_pwm_backlight_ids[] = { + { .compatible = "nvidia,tegra-pwm-backlight" }, + { } +}; + +U_BOOT_DRIVER(tegra_pwm_backlight) = { + .name = "tegra_pwm_backlight", + .id = UCLASS_PANEL_BACKLIGHT, + .of_match = tegra_pwm_backlight_ids, + .probe = tegra_pwm_backlight_probe, + .ops = &tegra_pwm_backlight_ops, + .priv_auto = sizeof(struct tegra_pwm_backlight_priv), +}; |
