- fix building sandbox without SDL

- improve tegra DC driver to work with panel ops and implement
    native 180 degree panel rotation support
  - add T30 support to tegra DC driver
  - add DSI driver (based on mainline Linux one with minor
    adjustments, only T30 tested)
  - add get_display_timing ops to simple panel driver
  - extend simple panel driver to use it for MIPI DSI panels
    which do not require additional DSI commands for setup
 -----BEGIN PGP SIGNATURE-----
 
 iGwEABECACwWIQSC4hxrSoIUVfFO0kRM6ATMmsalXAUCZDBwUw4cYWd1c3RAZGVu
 eC5kZQAKCRBM6ATMmsalXL3+AJ9EkQKAWXZtlYdAOVl4/nzJnuTCAQCfY58zkDsw
 TnAlwmMGWFl3RF6yGXY=
 =uYUp
 -----END PGP SIGNATURE-----

Merge tag 'video-20230407' of https://source.denx.de/u-boot/custodians/u-boot-video

 - fix building sandbox without SDL
 - improve tegra DC driver to work with panel ops and implement
   native 180 degree panel rotation support
 - add T30 support to tegra DC driver
 - add DSI driver (based on mainline Linux one with minor
   adjustments, only T30 tested)
 - add get_display_timing ops to simple panel driver
 - extend simple panel driver to use it for MIPI DSI panels
   which do not require additional DSI commands for setup
This commit is contained in:
Tom Rini 2023-04-08 11:20:47 -04:00
commit 187c7aba22
15 changed files with 1484 additions and 49 deletions

View file

@ -1,3 +1,12 @@
#include <config.h> #include <config.h>
#include "tegra-u-boot.dtsi" #include "tegra-u-boot.dtsi"
/ {
host1x@50000000 {
bootph-all;
dc@54200000 {
bootph-all;
};
};
};

View file

@ -569,4 +569,12 @@ enum {
#define DC_N_WINDOWS 5 #define DC_N_WINDOWS 5
#define DC_REG_SAVE_SPACE (DC_N_WINDOWS + 5) #define DC_REG_SAVE_SPACE (DC_N_WINDOWS + 5)
#define TEGRA_DSI_A "dsi@54300000"
#define TEGRA_DSI_B "dsi@54400000"
struct tegra_dc_plat {
struct udevice *dev; /* Display controller device */
struct dc_ctlr *dc; /* Display controller regmap */
};
#endif /* __ASM_ARCH_TEGRA_DC_H */ #endif /* __ASM_ARCH_TEGRA_DC_H */

View file

@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* (C) Copyright 2010
* NVIDIA Corporation <www.nvidia.com>
*/
#ifndef __ASM_ARCH_TEGRA_DISPLAY_H
#define __ASM_ARCH_TEGRA_DISPLAY_H
#include <asm/arch-tegra/dc.h>
/* 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 /*__ASM_ARCH_TEGRA_DISPLAY_H*/

View file

@ -0,0 +1,217 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* (C) Copyright 2010
* NVIDIA Corporation <www.nvidia.com>
*/
#ifndef __ASM_ARCH_TEGRA_DSI_H
#define __ASM_ARCH_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 ~ 0x04e */
struct dsi_pad_ctrl_reg {
/* Address 0x04b ~ 0x04e */
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 */
};
/* 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 */
};
#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)
/*
* 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 /* __ASM_ARCH_TEGRA_DSI_H */

View file

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Tegra pulse width frequency modulator definitions
*
* Copyright (c) 2011 The Chromium OS Authors.
*/
#ifndef __ASM_ARCH_TEGRA30_PWM_H
#define __ASM_ARCH_TEGRA30_PWM_H
#include <asm/arch-tegra/pwm.h>
#endif /* __ASM_ARCH_TEGRA30_PWM_H */

View file

@ -300,6 +300,7 @@ void sandbox_cros_ec_set_test_flags(struct udevice *dev, uint flags);
*/ */
int sandbox_cros_ec_get_pwm_duty(struct udevice *dev, uint index, uint *duty); int sandbox_cros_ec_get_pwm_duty(struct udevice *dev, uint index, uint *duty);
#if IS_ENABLED(CONFIG_SANDBOX_SDL)
/** /**
* sandbox_sdl_set_bpp() - Set the depth of the sandbox display * sandbox_sdl_set_bpp() - Set the depth of the sandbox display
* *
@ -315,6 +316,13 @@ int sandbox_cros_ec_get_pwm_duty(struct udevice *dev, uint index, uint *duty);
* after the change * after the change
*/ */
int sandbox_sdl_set_bpp(struct udevice *dev, enum video_log2_bpp l2bpp); int sandbox_sdl_set_bpp(struct udevice *dev, enum video_log2_bpp l2bpp);
#else
static inline int sandbox_sdl_set_bpp(struct udevice *dev,
enum video_log2_bpp l2bpp)
{
return -ENOSYS;
}
#endif
/** /**
* sandbox_set_fake_efi_mgr_dev() - Control EFI bootmgr producing valid bootflow * sandbox_set_fake_efi_mgr_dev() - Control EFI bootmgr producing valid bootflow

View file

@ -668,15 +668,6 @@ source "drivers/video/stm32/Kconfig"
source "drivers/video/tidss/Kconfig" source "drivers/video/tidss/Kconfig"
config VIDEO_TEGRA20
bool "Enable LCD support on Tegra20"
depends on OF_CONTROL
help
Tegra20 supports 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_TEGRA124 config VIDEO_TEGRA124
bool "Enable video support on Tegra124" bool "Enable video support on Tegra124"
help help
@ -687,6 +678,8 @@ config VIDEO_TEGRA124
source "drivers/video/bridge/Kconfig" source "drivers/video/bridge/Kconfig"
source "drivers/video/tegra20/Kconfig"
source "drivers/video/imx/Kconfig" source "drivers/video/imx/Kconfig"
config VIDEO_MXS config VIDEO_MXS

View file

@ -67,10 +67,10 @@ obj-$(CONFIG_VIDEO_OMAP3) += omap3_dss.o
obj-$(CONFIG_VIDEO_DSI_HOST_SANDBOX) += sandbox_dsi_host.o obj-$(CONFIG_VIDEO_DSI_HOST_SANDBOX) += sandbox_dsi_host.o
obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o
obj-$(CONFIG_VIDEO_SIMPLE) += simplefb.o obj-$(CONFIG_VIDEO_SIMPLE) += simplefb.o
obj-$(CONFIG_VIDEO_TEGRA20) += tegra.o
obj-$(CONFIG_VIDEO_VESA) += vesa.o obj-$(CONFIG_VIDEO_VESA) += vesa.o
obj-$(CONFIG_VIDEO_SEPS525) += seps525.o obj-$(CONFIG_VIDEO_SEPS525) += seps525.o
obj-$(CONFIG_VIDEO_ZYNQMP_DPSUB) += zynqmp_dpsub.o obj-$(CONFIG_VIDEO_ZYNQMP_DPSUB) += zynqmp_dpsub.o
obj-y += bridge/ obj-y += bridge/
obj-y += sunxi/ obj-y += sunxi/
obj-y += tegra20/

View file

@ -8,6 +8,7 @@
#include <backlight.h> #include <backlight.h>
#include <dm.h> #include <dm.h>
#include <log.h> #include <log.h>
#include <mipi_dsi.h>
#include <panel.h> #include <panel.h>
#include <asm/gpio.h> #include <asm/gpio.h>
#include <power/regulator.h> #include <power/regulator.h>
@ -18,6 +19,19 @@ struct simple_panel_priv {
struct gpio_desc enable; struct gpio_desc enable;
}; };
/* List of supported DSI panels */
enum {
PANEL_NON_DSI,
PANASONIC_VVX10F004B00,
};
static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = {
.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_CLOCK_NON_CONTINUOUS,
.format = MIPI_DSI_FMT_RGB888,
.lanes = 4,
};
static int simple_panel_enable_backlight(struct udevice *dev) static int simple_panel_enable_backlight(struct udevice *dev)
{ {
struct simple_panel_priv *priv = dev_get_priv(dev); struct simple_panel_priv *priv = dev_get_priv(dev);
@ -48,6 +62,15 @@ static int simple_panel_set_backlight(struct udevice *dev, int percent)
return 0; return 0;
} }
static int simple_panel_get_display_timing(struct udevice *dev,
struct display_timing *timings)
{
const void *blob = gd->fdt_blob;
return fdtdec_decode_display_timing(blob, dev_of_offset(dev),
0, timings);
}
static int simple_panel_of_to_plat(struct udevice *dev) static int simple_panel_of_to_plat(struct udevice *dev)
{ {
struct simple_panel_priv *priv = dev_get_priv(dev); struct simple_panel_priv *priv = dev_get_priv(dev);
@ -87,6 +110,8 @@ static int simple_panel_of_to_plat(struct udevice *dev)
static int simple_panel_probe(struct udevice *dev) static int simple_panel_probe(struct udevice *dev)
{ {
struct simple_panel_priv *priv = dev_get_priv(dev); struct simple_panel_priv *priv = dev_get_priv(dev);
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
const u32 dsi_data = dev_get_driver_data(dev);
int ret; int ret;
if (IS_ENABLED(CONFIG_DM_REGULATOR) && priv->reg) { if (IS_ENABLED(CONFIG_DM_REGULATOR) && priv->reg) {
@ -96,12 +121,23 @@ static int simple_panel_probe(struct udevice *dev)
return ret; return ret;
} }
switch (dsi_data) {
case PANASONIC_VVX10F004B00:
memcpy(plat, &panasonic_vvx10f004b00,
sizeof(panasonic_vvx10f004b00));
break;
case PANEL_NON_DSI:
default:
break;
}
return 0; return 0;
} }
static const struct panel_ops simple_panel_ops = { static const struct panel_ops simple_panel_ops = {
.enable_backlight = simple_panel_enable_backlight, .enable_backlight = simple_panel_enable_backlight,
.set_backlight = simple_panel_set_backlight, .set_backlight = simple_panel_set_backlight,
.get_display_timing = simple_panel_get_display_timing,
}; };
static const struct udevice_id simple_panel_ids[] = { static const struct udevice_id simple_panel_ids[] = {
@ -113,6 +149,8 @@ static const struct udevice_id simple_panel_ids[] = {
{ .compatible = "lg,lb070wv8" }, { .compatible = "lg,lb070wv8" },
{ .compatible = "sharp,lq123p1jx31" }, { .compatible = "sharp,lq123p1jx31" },
{ .compatible = "boe,nv101wxmn51" }, { .compatible = "boe,nv101wxmn51" },
{ .compatible = "panasonic,vvx10f004b00",
.data = PANASONIC_VVX10F004B00 },
{ } { }
}; };
@ -124,4 +162,5 @@ U_BOOT_DRIVER(simple_panel) = {
.of_to_plat = simple_panel_of_to_plat, .of_to_plat = simple_panel_of_to_plat,
.probe = simple_panel_probe, .probe = simple_panel_probe,
.priv_auto = sizeof(struct simple_panel_priv), .priv_auto = sizeof(struct simple_panel_priv),
.plat_auto = sizeof(struct mipi_dsi_panel_plat),
}; };

View file

@ -0,0 +1,17 @@
config VIDEO_TEGRA20
bool "Enable Display Controller support on Tegra20 and Tegra 30"
depends on OF_CONTROL
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 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.

View file

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0+
obj-$(CONFIG_VIDEO_TEGRA20) += tegra-dc.o
obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o mipi-phy.o

View file

@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#include <common.h>
#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;
}

View file

@ -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

View file

@ -4,6 +4,7 @@
*/ */
#include <common.h> #include <common.h>
#include <backlight.h>
#include <dm.h> #include <dm.h>
#include <fdtdec.h> #include <fdtdec.h>
#include <log.h> #include <log.h>
@ -33,20 +34,24 @@ struct tegra_lcd_priv {
enum video_log2_bpp log2_bpp; /* colour depth */ enum video_log2_bpp log2_bpp; /* colour depth */
struct display_timing timing; struct display_timing timing;
struct udevice *panel; struct udevice *panel;
struct disp_ctlr *disp; /* Display controller to use */ struct dc_ctlr *dc; /* Display controller regmap */
fdt_addr_t frame_buffer; /* Address of frame buffer */ fdt_addr_t frame_buffer; /* Address of frame buffer */
unsigned pixel_clock; /* Pixel clock in Hz */ unsigned pixel_clock; /* Pixel clock in Hz */
int dc_clk[2]; /* Contains clk and its parent */
bool rotation; /* 180 degree panel turn */
}; };
enum { enum {
/* Maximum LCD size we support */ /* Maximum LCD size we support */
LCD_MAX_WIDTH = 1366, LCD_MAX_WIDTH = 1920,
LCD_MAX_HEIGHT = 768, LCD_MAX_HEIGHT = 1200,
LCD_MAX_LOG2_BPP = VIDEO_BPP16, LCD_MAX_LOG2_BPP = VIDEO_BPP16,
}; };
static void update_window(struct dc_ctlr *dc, struct disp_ctl_win *win) 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 h_dda, v_dda;
unsigned long val; unsigned long val;
@ -87,6 +92,10 @@ static void update_window(struct dc_ctlr *dc, struct disp_ctl_win *win)
val = WIN_ENABLE; val = WIN_ENABLE;
if (win->bpp < 24) if (win->bpp < 24)
val |= COLOR_EXPAND; val |= COLOR_EXPAND;
if (priv->rotation)
val |= H_DIRECTION | V_DIRECTION;
writel(val, &dc->win.win_opt); writel(val, &dc->win.win_opt);
writel((unsigned long)win->phys_addr, &dc->winbuf.start_addr); writel((unsigned long)win->phys_addr, &dc->winbuf.start_addr);
@ -134,7 +143,7 @@ static int update_display_mode(struct dc_disp_reg *disp,
* the display clock (typically 600MHz) to the pixel clock. We round * the display clock (typically 600MHz) to the pixel clock. We round
* up or down as requried. * up or down as requried.
*/ */
rate = clock_get_periph_rate(PERIPH_ID_DISP1, CLOCK_ID_CGENERAL); rate = clock_get_periph_rate(priv->dc_clk[0], priv->dc_clk[1]);
div = ((rate * 2 + priv->pixel_clock / 2) / priv->pixel_clock) - 2; div = ((rate * 2 + priv->pixel_clock / 2) / priv->pixel_clock) - 2;
debug("Display clock %lu, divider %lu\n", rate, div); debug("Display clock %lu, divider %lu\n", rate, div);
@ -223,8 +232,14 @@ static void rgb_enable(struct dc_com_reg *com)
static int setup_window(struct disp_ctl_win *win, static int setup_window(struct disp_ctl_win *win,
struct tegra_lcd_priv *priv) struct tegra_lcd_priv *priv)
{ {
if (priv->rotation) {
win->x = priv->width * 2;
win->y = priv->height;
} else {
win->x = 0; win->x = 0;
win->y = 0; win->y = 0;
}
win->w = priv->width; win->w = priv->width;
win->h = priv->height; win->h = priv->height;
win->out_x = 0; win->out_x = 0;
@ -268,32 +283,36 @@ static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv,
void *default_lcd_base) void *default_lcd_base)
{ {
struct disp_ctl_win window; struct disp_ctl_win window;
struct dc_ctlr *dc; unsigned long rate = clock_get_rate(priv->dc_clk[1]);
priv->frame_buffer = (u32)default_lcd_base; priv->frame_buffer = (u32)default_lcd_base;
dc = (struct dc_ctlr *)priv->disp; /*
* We halve the rate if DISP1 paret is PLLD, since actual parent
* is plld_out0 which is PLLD divided by 2.
*/
if (priv->dc_clk[1] == CLOCK_ID_DISPLAY)
rate /= 2;
/* /*
* A header file for clock constants was NAKed upstream. * HOST1X is init by default at 150MHz with PLLC as parent
* TODO: Put this into the FDT and fdt_lcd struct when we have clock
* support there
*/ */
clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_PERIPH, clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_CGENERAL,
144 * 1000000); 150 * 1000000);
clock_start_periph_pll(PERIPH_ID_DISP1, CLOCK_ID_CGENERAL, clock_start_periph_pll(priv->dc_clk[0], priv->dc_clk[1],
600 * 1000000); rate);
basic_init(&dc->cmd);
basic_init_timer(&dc->disp); basic_init(&priv->dc->cmd);
rgb_enable(&dc->com); basic_init_timer(&priv->dc->disp);
rgb_enable(&priv->dc->com);
if (priv->pixel_clock) if (priv->pixel_clock)
update_display_mode(&dc->disp, priv); update_display_mode(&priv->dc->disp, priv);
if (setup_window(&window, priv)) if (setup_window(&window, priv))
return -1; return -1;
update_window(dc, &window); update_window(priv, &window);
return 0; return 0;
} }
@ -307,14 +326,19 @@ static int tegra_lcd_probe(struct udevice *dev)
int ret; int ret;
/* Initialize the Tegra display controller */ /* Initialize the Tegra display controller */
#ifdef CONFIG_TEGRA20
funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT); funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT);
#endif
if (tegra_display_probe(blob, priv, (void *)plat->base)) { if (tegra_display_probe(blob, priv, (void *)plat->base)) {
printf("%s: Failed to probe display driver\n", __func__); printf("%s: Failed to probe display driver\n", __func__);
return -1; return -1;
} }
#ifdef CONFIG_TEGRA20
pinmux_set_func(PMUX_PINGRP_GPU, PMUX_FUNC_PWM); pinmux_set_func(PMUX_PINGRP_GPU, PMUX_FUNC_PWM);
pinmux_tristate_disable(PMUX_PINGRP_GPU); pinmux_tristate_disable(PMUX_PINGRP_GPU);
#endif
ret = panel_enable_backlight(priv->panel); ret = panel_enable_backlight(priv->panel);
if (ret) { if (ret) {
@ -322,6 +346,12 @@ static int tegra_lcd_probe(struct udevice *dev)
return ret; return ret;
} }
ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
if (ret) {
debug("%s: Cannot set backlight to default, ret=%d\n", __func__, ret);
return ret;
}
mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size, mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size,
DCACHE_WRITETHROUGH); DCACHE_WRITETHROUGH);
@ -347,12 +377,21 @@ static int tegra_lcd_of_to_plat(struct udevice *dev)
int rgb; int rgb;
int ret; int ret;
priv->disp = dev_read_addr_ptr(dev); priv->dc = (struct dc_ctlr *)dev_read_addr_ptr(dev);
if (!priv->disp) { if (!priv->dc) {
debug("%s: No display controller address\n", __func__); debug("%s: No display controller address\n", __func__);
return -EINVAL; return -EINVAL;
} }
ret = clock_decode_pair(dev, priv->dc_clk);
if (ret < 0) {
debug("%s: Cannot decode clocks for '%s' (ret = %d)\n",
__func__, dev->name, ret);
return -EINVAL;
}
priv->rotation = dev_read_bool(dev, "nvidia,180-rotation");
rgb = fdt_subnode_offset(blob, node, "rgb"); rgb = fdt_subnode_offset(blob, node, "rgb");
if (rgb < 0) { if (rgb < 0) {
debug("%s: Cannot find rgb subnode for '%s' (ret=%d)\n", debug("%s: Cannot find rgb subnode for '%s' (ret=%d)\n",
@ -360,18 +399,6 @@ static int tegra_lcd_of_to_plat(struct udevice *dev)
return -EINVAL; return -EINVAL;
} }
ret = fdtdec_decode_display_timing(blob, rgb, 0, &priv->timing);
if (ret) {
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;
/* /*
* Sadly the panel phandle is in an rgb subnode so we cannot use * Sadly the panel phandle is in an rgb subnode so we cannot use
* uclass_get_device_by_phandle(). * uclass_get_device_by_phandle().
@ -381,6 +408,7 @@ static int tegra_lcd_of_to_plat(struct udevice *dev)
debug("%s: Cannot find panel information\n", __func__); debug("%s: Cannot find panel information\n", __func__);
return -EINVAL; return -EINVAL;
} }
ret = uclass_get_device_by_of_offset(UCLASS_PANEL, panel_node, ret = uclass_get_device_by_of_offset(UCLASS_PANEL, panel_node,
&priv->panel); &priv->panel);
if (ret) { if (ret) {
@ -389,6 +417,30 @@ static int tegra_lcd_of_to_plat(struct udevice *dev)
return ret; return ret;
} }
if (!strcmp(priv->panel->name, TEGRA_DSI_A) ||
!strcmp(priv->panel->name, TEGRA_DSI_B)) {
struct tegra_dc_plat *dc_plat = dev_get_plat(priv->panel);
dc_plat->dev = dev;
dc_plat->dc = priv->dc;
}
ret = panel_get_display_timing(priv->panel, &priv->timing);
if (ret) {
ret = fdtdec_decode_display_timing(blob, rgb, 0, &priv->timing);
if (ret) {
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; return 0;
} }
@ -414,6 +466,7 @@ static const struct video_ops tegra_lcd_ops = {
static const struct udevice_id tegra_lcd_ids[] = { static const struct udevice_id tegra_lcd_ids[] = {
{ .compatible = "nvidia,tegra20-dc" }, { .compatible = "nvidia,tegra20-dc" },
{ .compatible = "nvidia,tegra30-dc" },
{ } { }
}; };

View file

@ -0,0 +1,864 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2013 NVIDIA Corporation
* Copyright (c) 2022 Svyatoslav Ryhel <clamor95@gmail.com>
*/
#include <common.h>
#include <dm.h>
#include <log.h>
#include <misc.h>
#include <mipi_display.h>
#include <mipi_dsi.h>
#include <backlight.h>
#include <panel.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <power/regulator.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/arch/display.h>
#include <asm/arch-tegra30/dsi.h>
#include "mipi-phy.h"
#define USEC_PER_SEC 1000000L
#define NSEC_PER_SEC 1000000000L
struct tegra_dsi_priv {
struct mipi_dsi_host host;
struct mipi_dsi_device device;
struct mipi_dphy_timing dphy_timing;
struct udevice *panel;
struct display_timing timing;
struct dsi_ctlr *dsi;
struct udevice *avdd;
enum tegra_dsi_format format;
int dsi_clk;
int video_fifo_depth;
int host_fifo_depth;
};
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;
/*
* 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_set_timeout(struct dsi_timeout_reg *rtimeout,
unsigned long bclk,
unsigned int vrefresh)
{
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);
}
static void tegra_dsi_set_phy_timing(struct dsi_timing_reg *ptiming,
unsigned long period,
const struct mipi_dphy_timing *dphy_timing)
{
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);
}
static void tegra_dsi_configure(struct udevice *dev,
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(0);
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;
if ((mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) == 0)
hbp += hsw;
/* horizontal front porch */
hfp = timing->hfront_porch.typ * mul / div;
/* 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 {
/* 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 (for non-burst mode only) */
writel(8 * mul / div, &misc->dsi_sol_delay);
}
static int tegra_dsi_encoder_enable(struct udevice *dev)
{
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;
unsigned int mul, div;
unsigned long bclk, plld, period;
u32 value;
int ret;
/* Disable interrupt */
writel(0, &misc->int_enable);
tegra_dsi_pad_calibrate(&priv->dsi->pad);
tegra_dsi_get_muldiv(device->format, &mul, &div);
/* compute byte clock */
bclk = (timing->pixelclock.typ * mul) / (div * device->lanes);
tegra_dsi_set_timeout(&priv->dsi->timeout, 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(&priv->dsi->ptiming,
period * 8, &priv->dphy_timing);
/* Perform panel HW setup */
ret = panel_enable_backlight(priv->panel);
if (ret)
return ret;
tegra_dsi_configure(dev, 0);
ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
if (ret)
return ret;
tegra_dsi_configure(dev, device->mode_flags);
tegra_dc_enable_controller(dev);
/* enable DSI controller */
value = readl(&misc->dsi_pwr_ctrl);
value |= DSI_POWER_CONTROL_ENABLE;
writel(value, &misc->dsi_pwr_ctrl);
return 0;
}
static int tegra_dsi_bridge_set_panel(struct udevice *dev, int percent)
{
/* Is not used in tegra dc */
return 0;
}
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 mipi_dsi_device *device = &priv->device;
unsigned int mul, div;
unsigned long bclk, plld;
tegra_dsi_get_muldiv(device->format, &mul, &div);
bclk = (priv->timing.pixelclock.typ * mul) /
(div * device->lanes);
plld = DIV_ROUND_UP(bclk * 8, 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(CLOCK_ID_DISPLAY, plld, 12, 0, 8);
break;
case CLOCK_OSC_FREQ_26_0: /* OSC is 26Mhz */
clock_set_rate(CLOCK_ID_DISPLAY, 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(CLOCK_ID_DISPLAY, plld, 13, 0, 8);
break;
case CLOCK_OSC_FREQ_19_2:
case CLOCK_OSC_FREQ_38_4:
default:
/*
* These are not supported.
*/
break;
}
priv->dsi_clk = clock_decode_periph_id(dev);
clock_enable(priv->dsi_clk);
udelay(2);
reset_set_enable(priv->dsi_clk, 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;
int ret;
priv->dsi = (struct dsi_ctlr *)dev_read_addr_ptr(dev);
if (!priv->dsi) {
printf("%s: No display controller address\n", __func__);
return -EINVAL;
}
priv->video_fifo_depth = 480;
priv->host_fifo_depth = 64;
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);
ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev,
"panel", &priv->panel);
if (ret) {
printf("%s: Cannot get panel: error %d\n", __func__, ret);
return log_ret(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);
if (priv->avdd) {
ret = regulator_set_enable(priv->avdd, true);
if (ret)
return ret;
}
tegra_dsi_init_clocks(dev);
return 0;
}
static const struct panel_ops tegra_dsi_bridge_ops = {
.enable_backlight = 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,tegra30-dsi" },
{ }
};
U_BOOT_DRIVER(tegra_dsi) = {
.name = "tegra_dsi",
.id = UCLASS_PANEL,
.of_match = tegra_dsi_bridge_ids,
.ops = &tegra_dsi_bridge_ops,
.probe = tegra_dsi_bridge_probe,
.plat_auto = sizeof(struct tegra_dc_plat),
.priv_auto = sizeof(struct tegra_dsi_priv),
};