mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-24 13:43:28 +00:00
- 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:
commit
187c7aba22
15 changed files with 1484 additions and 49 deletions
|
@ -1,3 +1,12 @@
|
|||
#include <config.h>
|
||||
|
||||
#include "tegra-u-boot.dtsi"
|
||||
|
||||
/ {
|
||||
host1x@50000000 {
|
||||
bootph-all;
|
||||
dc@54200000 {
|
||||
bootph-all;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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 */
|
||||
|
|
28
arch/arm/include/asm/arch-tegra30/display.h
Normal file
28
arch/arm/include/asm/arch-tegra30/display.h
Normal 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*/
|
217
arch/arm/include/asm/arch-tegra30/dsi.h
Normal file
217
arch/arm/include/asm/arch-tegra30/dsi.h
Normal 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 */
|
13
arch/arm/include/asm/arch-tegra30/pwm.h
Normal file
13
arch/arm/include/asm/arch-tegra30/pwm.h
Normal 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 */
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
|
17
drivers/video/tegra20/Kconfig
Normal file
17
drivers/video/tegra20/Kconfig
Normal 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.
|
4
drivers/video/tegra20/Makefile
Normal file
4
drivers/video/tegra20/Makefile
Normal 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
|
134
drivers/video/tegra20/mipi-phy.c
Normal file
134
drivers/video/tegra20/mipi-phy.c
Normal 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;
|
||||
}
|
48
drivers/video/tegra20/mipi-phy.h
Normal file
48
drivers/video/tegra20/mipi-phy.h
Normal 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
|
|
@ -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" },
|
||||
{ }
|
||||
};
|
||||
|
864
drivers/video/tegra20/tegra-dsi.c
Normal file
864
drivers/video/tegra20/tegra-dsi.c
Normal 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),
|
||||
};
|
Loading…
Reference in a new issue