diff options
author | Tom Rini <trini@konsulko.com> | 2023-04-08 11:20:47 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2023-04-08 11:20:47 -0400 |
commit | 187c7aba221f633dffa2f33bd582147059ed24f3 (patch) | |
tree | 0d6f97eae7f84c3a4beeb5ddac2ebe18c353907b | |
parent | 965f74b5b3602fe681421bda5676c7300ee3975e (diff) | |
parent | cc54a924cd28a2d1b0f0035bd7d78532b6bc4ad8 (diff) | |
download | u-boot-187c7aba221f633dffa2f33bd582147059ed24f3.zip u-boot-187c7aba221f633dffa2f33bd582147059ed24f3.tar.gz u-boot-187c7aba221f633dffa2f33bd582147059ed24f3.tar.bz2 |
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
-rw-r--r-- | arch/arm/dts/tegra30-u-boot.dtsi | 9 | ||||
-rw-r--r-- | arch/arm/include/asm/arch-tegra/dc.h | 8 | ||||
-rw-r--r-- | arch/arm/include/asm/arch-tegra30/display.h | 28 | ||||
-rw-r--r-- | arch/arm/include/asm/arch-tegra30/dsi.h | 217 | ||||
-rw-r--r-- | arch/arm/include/asm/arch-tegra30/pwm.h | 13 | ||||
-rw-r--r-- | arch/sandbox/include/asm/test.h | 8 | ||||
-rw-r--r-- | drivers/video/Kconfig | 11 | ||||
-rw-r--r-- | drivers/video/Makefile | 2 | ||||
-rw-r--r-- | drivers/video/simple_panel.c | 47 | ||||
-rw-r--r-- | drivers/video/tegra20/Kconfig | 17 | ||||
-rw-r--r-- | drivers/video/tegra20/Makefile | 4 | ||||
-rw-r--r-- | drivers/video/tegra20/mipi-phy.c | 134 | ||||
-rw-r--r-- | drivers/video/tegra20/mipi-phy.h | 48 | ||||
-rw-r--r-- | drivers/video/tegra20/tegra-dc.c (renamed from drivers/video/tegra.c) | 123 | ||||
-rw-r--r-- | drivers/video/tegra20/tegra-dsi.c | 864 |
15 files changed, 1484 insertions, 49 deletions
diff --git a/arch/arm/dts/tegra30-u-boot.dtsi b/arch/arm/dts/tegra30-u-boot.dtsi index 7c11972..3038227 100644 --- a/arch/arm/dts/tegra30-u-boot.dtsi +++ b/arch/arm/dts/tegra30-u-boot.dtsi @@ -1,3 +1,12 @@ #include <config.h> #include "tegra-u-boot.dtsi" + +/ { + host1x@50000000 { + bootph-all; + dc@54200000 { + bootph-all; + }; + }; +}; diff --git a/arch/arm/include/asm/arch-tegra/dc.h b/arch/arm/include/asm/arch-tegra/dc.h index 6444af2..7613d84 100644 --- a/arch/arm/include/asm/arch-tegra/dc.h +++ b/arch/arm/include/asm/arch-tegra/dc.h @@ -569,4 +569,12 @@ enum { #define 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 */ diff --git a/arch/arm/include/asm/arch-tegra30/display.h b/arch/arm/include/asm/arch-tegra30/display.h new file mode 100644 index 0000000..9411525 --- /dev/null +++ b/arch/arm/include/asm/arch-tegra30/display.h @@ -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*/ diff --git a/arch/arm/include/asm/arch-tegra30/dsi.h b/arch/arm/include/asm/arch-tegra30/dsi.h new file mode 100644 index 0000000..7ade132 --- /dev/null +++ b/arch/arm/include/asm/arch-tegra30/dsi.h @@ -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 */ diff --git a/arch/arm/include/asm/arch-tegra30/pwm.h b/arch/arm/include/asm/arch-tegra30/pwm.h new file mode 100644 index 0000000..c314e2b --- /dev/null +++ b/arch/arm/include/asm/arch-tegra30/pwm.h @@ -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 */ diff --git a/arch/sandbox/include/asm/test.h b/arch/sandbox/include/asm/test.h index 4853dc94..e482271 100644 --- a/arch/sandbox/include/asm/test.h +++ b/arch/sandbox/include/asm/test.h @@ -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); +#if IS_ENABLED(CONFIG_SANDBOX_SDL) /** * 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 */ 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 diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 60f4a4b..334d64c 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -668,15 +668,6 @@ source "drivers/video/stm32/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 bool "Enable video support on Tegra124" help @@ -687,6 +678,8 @@ config VIDEO_TEGRA124 source "drivers/video/bridge/Kconfig" +source "drivers/video/tegra20/Kconfig" + source "drivers/video/imx/Kconfig" config VIDEO_MXS diff --git a/drivers/video/Makefile b/drivers/video/Makefile index cb3f373..4d75771 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -67,10 +67,10 @@ obj-$(CONFIG_VIDEO_OMAP3) += omap3_dss.o obj-$(CONFIG_VIDEO_DSI_HOST_SANDBOX) += sandbox_dsi_host.o obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o obj-$(CONFIG_VIDEO_SIMPLE) += simplefb.o -obj-$(CONFIG_VIDEO_TEGRA20) += tegra.o obj-$(CONFIG_VIDEO_VESA) += vesa.o obj-$(CONFIG_VIDEO_SEPS525) += seps525.o obj-$(CONFIG_VIDEO_ZYNQMP_DPSUB) += zynqmp_dpsub.o obj-y += bridge/ obj-y += sunxi/ +obj-y += tegra20/ diff --git a/drivers/video/simple_panel.c b/drivers/video/simple_panel.c index 91c91ee..81fcafb 100644 --- a/drivers/video/simple_panel.c +++ b/drivers/video/simple_panel.c @@ -8,6 +8,7 @@ #include <backlight.h> #include <dm.h> #include <log.h> +#include <mipi_dsi.h> #include <panel.h> #include <asm/gpio.h> #include <power/regulator.h> @@ -18,6 +19,19 @@ struct simple_panel_priv { 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) { 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; } +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) { 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) { 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; if (IS_ENABLED(CONFIG_DM_REGULATOR) && priv->reg) { @@ -96,12 +121,23 @@ static int simple_panel_probe(struct udevice *dev) return ret; } + switch (dsi_data) { + case PANASONIC_VVX10F004B00: + memcpy(plat, &panasonic_vvx10f004b00, + sizeof(panasonic_vvx10f004b00)); + break; + case PANEL_NON_DSI: + default: + break; + } + return 0; } static const struct panel_ops simple_panel_ops = { .enable_backlight = simple_panel_enable_backlight, .set_backlight = simple_panel_set_backlight, + .get_display_timing = simple_panel_get_display_timing, }; static const struct udevice_id simple_panel_ids[] = { @@ -113,15 +149,18 @@ static const struct udevice_id simple_panel_ids[] = { { .compatible = "lg,lb070wv8" }, { .compatible = "sharp,lq123p1jx31" }, { .compatible = "boe,nv101wxmn51" }, + { .compatible = "panasonic,vvx10f004b00", + .data = PANASONIC_VVX10F004B00 }, { } }; U_BOOT_DRIVER(simple_panel) = { - .name = "simple_panel", - .id = UCLASS_PANEL, - .of_match = simple_panel_ids, - .ops = &simple_panel_ops, + .name = "simple_panel", + .id = UCLASS_PANEL, + .of_match = simple_panel_ids, + .ops = &simple_panel_ops, .of_to_plat = simple_panel_of_to_plat, .probe = simple_panel_probe, .priv_auto = sizeof(struct simple_panel_priv), + .plat_auto = sizeof(struct mipi_dsi_panel_plat), }; diff --git a/drivers/video/tegra20/Kconfig b/drivers/video/tegra20/Kconfig new file mode 100644 index 0000000..5b1dfbf --- /dev/null +++ b/drivers/video/tegra20/Kconfig @@ -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. diff --git a/drivers/video/tegra20/Makefile b/drivers/video/tegra20/Makefile new file mode 100644 index 0000000..e82ee96 --- /dev/null +++ b/drivers/video/tegra20/Makefile @@ -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 diff --git a/drivers/video/tegra20/mipi-phy.c b/drivers/video/tegra20/mipi-phy.c new file mode 100644 index 0000000..c3ebc40 --- /dev/null +++ b/drivers/video/tegra20/mipi-phy.c @@ -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; +} diff --git a/drivers/video/tegra20/mipi-phy.h b/drivers/video/tegra20/mipi-phy.h new file mode 100644 index 0000000..41889a7 --- /dev/null +++ b/drivers/video/tegra20/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.c b/drivers/video/tegra20/tegra-dc.c index 3f9fcd0..f53ad46 100644 --- a/drivers/video/tegra.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -4,6 +4,7 @@ */ #include <common.h> +#include <backlight.h> #include <dm.h> #include <fdtdec.h> #include <log.h> @@ -33,20 +34,24 @@ struct tegra_lcd_priv { enum video_log2_bpp log2_bpp; /* colour depth */ struct display_timing timing; 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 */ unsigned pixel_clock; /* Pixel clock in Hz */ + int dc_clk[2]; /* Contains clk and its parent */ + bool rotation; /* 180 degree panel turn */ }; enum { /* Maximum LCD size we support */ - LCD_MAX_WIDTH = 1366, - LCD_MAX_HEIGHT = 768, + LCD_MAX_WIDTH = 1920, + LCD_MAX_HEIGHT = 1200, 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 long val; @@ -87,6 +92,10 @@ static void update_window(struct dc_ctlr *dc, struct disp_ctl_win *win) 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); @@ -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 * 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; 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, struct tegra_lcd_priv *priv) { - win->x = 0; - win->y = 0; + if (priv->rotation) { + win->x = priv->width * 2; + win->y = priv->height; + } else { + win->x = 0; + win->y = 0; + } + win->w = priv->width; win->h = priv->height; 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) { 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; - 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. - * TODO: Put this into the FDT and fdt_lcd struct when we have clock - * support there + * HOST1X is init by default at 150MHz with PLLC as parent */ - clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_PERIPH, - 144 * 1000000); - clock_start_periph_pll(PERIPH_ID_DISP1, CLOCK_ID_CGENERAL, - 600 * 1000000); - basic_init(&dc->cmd); - basic_init_timer(&dc->disp); - rgb_enable(&dc->com); + clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_CGENERAL, + 150 * 1000000); + clock_start_periph_pll(priv->dc_clk[0], priv->dc_clk[1], + rate); + + basic_init(&priv->dc->cmd); + basic_init_timer(&priv->dc->disp); + rgb_enable(&priv->dc->com); if (priv->pixel_clock) - update_display_mode(&dc->disp, priv); + update_display_mode(&priv->dc->disp, priv); if (setup_window(&window, priv)) return -1; - update_window(dc, &window); + update_window(priv, &window); return 0; } @@ -307,14 +326,19 @@ static int tegra_lcd_probe(struct udevice *dev) int ret; /* Initialize the Tegra display controller */ +#ifdef CONFIG_TEGRA20 funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT); +#endif + if (tegra_display_probe(blob, priv, (void *)plat->base)) { printf("%s: Failed to probe display driver\n", __func__); return -1; } +#ifdef CONFIG_TEGRA20 pinmux_set_func(PMUX_PINGRP_GPU, PMUX_FUNC_PWM); pinmux_tristate_disable(PMUX_PINGRP_GPU); +#endif ret = panel_enable_backlight(priv->panel); if (ret) { @@ -322,6 +346,12 @@ static int tegra_lcd_probe(struct udevice *dev) 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, DCACHE_WRITETHROUGH); @@ -347,12 +377,21 @@ static int tegra_lcd_of_to_plat(struct udevice *dev) int rgb; int ret; - priv->disp = dev_read_addr_ptr(dev); - if (!priv->disp) { + priv->dc = (struct dc_ctlr *)dev_read_addr_ptr(dev); + if (!priv->dc) { debug("%s: No display controller address\n", __func__); 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"); if (rgb < 0) { 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; } - 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 * 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__); return -EINVAL; } + ret = uclass_get_device_by_of_offset(UCLASS_PANEL, panel_node, &priv->panel); if (ret) { @@ -389,6 +417,30 @@ static int tegra_lcd_of_to_plat(struct udevice *dev) 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; } @@ -414,6 +466,7 @@ static const struct video_ops tegra_lcd_ops = { static const struct udevice_id tegra_lcd_ids[] = { { .compatible = "nvidia,tegra20-dc" }, + { .compatible = "nvidia,tegra30-dc" }, { } }; diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c new file mode 100644 index 0000000..8c3404e --- /dev/null +++ b/drivers/video/tegra20/tegra-dsi.c @@ -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), +}; |