Add support for 8-bit CPU driven (primary and secondary) display signal interface found in Tegra 2 and Tegra 3 SoC.
Tested-by: Ion Agorria <i...@agorria.com> Signed-off-by: Svyatoslav Ryhel <clamo...@gmail.com> --- arch/arm/include/asm/arch-tegra/dc.h | 48 ++++ drivers/video/tegra/Kconfig | 10 + drivers/video/tegra/Makefile | 1 + drivers/video/tegra/cpu-bridge.c | 325 +++++++++++++++++++++++++++ 4 files changed, 384 insertions(+) create mode 100644 drivers/video/tegra/cpu-bridge.c diff --git a/arch/arm/include/asm/arch-tegra/dc.h b/arch/arm/include/asm/arch-tegra/dc.h index 2fd07403bdf..ab12cc9c7d0 100644 --- a/arch/arm/include/asm/arch-tegra/dc.h +++ b/arch/arm/include/asm/arch-tegra/dc.h @@ -448,10 +448,19 @@ enum win_color_depth_id { #define LVS_OUTPUT_POLARITY_LOW BIT(28) #define LSC0_OUTPUT_POLARITY_LOW BIT(24) +/* DC_COM_PIN_OUTPUT_SELECT6 0x31a */ +#define LDC_OUTPUT_SELECT_V_PULSE1 BIT(14) /* 100b */ + /* DC_DISP_DISP_SIGNAL_OPTIONS0 0x400 */ #define H_PULSE0_ENABLE BIT(8) #define H_PULSE1_ENABLE BIT(10) #define H_PULSE2_ENABLE BIT(12) +#define V_PULSE0_ENABLE BIT(16) +#define V_PULSE1_ENABLE BIT(18) +#define V_PULSE2_ENABLE BIT(19) +#define V_PULSE3_ENABLE BIT(20) +#define M0_ENABLE BIT(24) +#define M1_ENABLE BIT(26) /* DC_DISP_DISP_WIN_OPTIONS 0x402 */ #define CURSOR_ENABLE BIT(16) @@ -525,6 +534,28 @@ enum { BASE_COLOR_SIZE_888, }; +/* DC_DISP_SHIFT_CLOCK_OPTIONS 0x431 */ +#define SC0_H_QUALIFIER_SHIFT 0 +#define SC1_H_QUALIFIER_SHIFT 16 +enum { + SC_H_QUALIFIER_DISABLE, + SC_H_QUALIFIER_NONE, + SC_H_QUALIFIER_HACTIVE, + SC_H_QUALIFIER_EXT_HACTIVE, + SC_H_QUALIFIER_HPULSE, + SC_H_QUALIFIER_EXT_HPULSE, +}; +#define SC0_V_QUALIFIER_SHIFT 3 +#define SC1_V_QUALIFIER_SHIFT 19 +enum { + SC_V_QUALIFIER_NONE, + SC_V_QUALIFIER_RSVD, + SC_V_QUALIFIER_VACTIVE, + SC_V_QUALIFIER_EXT_VACTIVE, + SC_V_QUALIFIER_VPULSE, + SC_V_QUALIFIER_EXT_VPULSE, +}; + /* DC_DISP_DATA_ENABLE_OPTIONS 0x432 */ #define DE_SELECT_SHIFT 0 #define DE_SELECT_MASK (0x3 << DE_SELECT_SHIFT) @@ -541,6 +572,23 @@ enum { DE_CONTROL_ACTIVE_BLANK, }; +/* DC_DISP_INIT_SEQ_CONTROL 0x442 */ +#define SEND_INIT_SEQUENCE BIT(0) +#define INIT_SEQUENCE_MODE_SPI BIT(1) +#define INIT_SEQUENCE_MODE_PLCD 0x0 +#define INIT_SEQ_DC_SIGNAL_SHIFT 4 +#define INIT_SEQ_DC_SIGNAL_MASK (0x7 << INIT_SEQ_DC_SIGNAL_SHIFT) +enum { + NO_DC_SIGNAL, + DC_SIGNAL_VSYNC, + DC_SIGNAL_VPULSE0, + DC_SIGNAL_VPULSE1, + DC_SIGNAL_VPULSE2, + DC_SIGNAL_VPULSE3, +}; +#define INIT_SEQ_DC_CONTROL_SHIFT 7 +#define FRAME_INIT_SEQ_CYCLES_SHIFT 8 + /* DC_WIN_WIN_OPTIONS 0x700 */ #define H_DIRECTION BIT(0) enum { diff --git a/drivers/video/tegra/Kconfig b/drivers/video/tegra/Kconfig index d3b8dbb2826..88607e0498b 100644 --- a/drivers/video/tegra/Kconfig +++ b/drivers/video/tegra/Kconfig @@ -42,6 +42,16 @@ config TEGRA_BACKLIGHT_PWM Enable support for the Display Controller dependent PWM backlight found in the Tegra SoC and usually used with DSI panels. +config TEGRA_8BIT_CPU_BRIDGE + bool "Enable 8 bit panel communication protocol for Tegra 20/30" + depends on VIDEO_BRIDGE && DM_GPIO + select VIDEO_TEGRA + select VIDEO_MIPI_DSI + help + Tegra 20 and Tegra 30 feature 8 bit CPU driver panel control + protocol. This option allows use it as a MIPI DSI bridge to + set up and control compatible panel. + config VIDEO_TEGRA124 bool "Enable video support on Tegra124" help diff --git a/drivers/video/tegra/Makefile b/drivers/video/tegra/Makefile index 3c50a0ba3c3..4995c93a8af 100644 --- a/drivers/video/tegra/Makefile +++ b/drivers/video/tegra/Makefile @@ -5,5 +5,6 @@ obj-$(CONFIG_VIDEO_TEGRA) += dc.o obj-$(CONFIG_VIDEO_DSI_TEGRA) += dsi.o mipi.o mipi-phy.o obj-$(CONFIG_VIDEO_HDMI_TEGRA) += hdmi.o obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += dc-pwm-backlight.o +obj-$(CONFIG_TEGRA_8BIT_CPU_BRIDGE) += cpu-bridge.o obj-${CONFIG_VIDEO_TEGRA124} += tegra124/ diff --git a/drivers/video/tegra/cpu-bridge.c b/drivers/video/tegra/cpu-bridge.c new file mode 100644 index 00000000000..e5fefe028f0 --- /dev/null +++ b/drivers/video/tegra/cpu-bridge.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2024 Svyatoslav Ryhel <clamo...@gmail.com> + * + * This driver uses 8-bit CPU interface found in Tegra 2 + * and Tegra 3 to drive MIPI DSI panel. + */ + +#include <dm.h> +#include <dm/ofnode_graph.h> +#include <log.h> +#include <mipi_display.h> +#include <mipi_dsi.h> +#include <backlight.h> +#include <panel.h> +#include <video_bridge.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <asm/gpio.h> +#include <asm/io.h> + +#include "dc.h" + +struct tegra_cpu_bridge_priv { + struct dc_ctlr *dc; + + struct mipi_dsi_host host; + struct mipi_dsi_device device; + + struct udevice *panel; + struct display_timing timing; + + struct gpio_desc dc_gpio; + struct gpio_desc rw_gpio; + struct gpio_desc cs_gpio; + + struct gpio_desc data_gpios[8]; + + u32 pixel_format; + u32 spi_init_seq[4]; +}; + +#define TEGRA_CPU_BRIDGE_COMM 0 +#define TEGRA_CPU_BRIDGE_DATA 1 + +static void tegra_cpu_bridge_write(struct tegra_cpu_bridge_priv *priv, + u8 type, u8 value) +{ + int i; + + dm_gpio_set_value(&priv->dc_gpio, type); + + dm_gpio_set_value(&priv->cs_gpio, 0); + dm_gpio_set_value(&priv->rw_gpio, 0); + + for (i = 0; i < 8; i++) + dm_gpio_set_value(&priv->data_gpios[i], + (value >> i) & 0x1); + + dm_gpio_set_value(&priv->cs_gpio, 1); + dm_gpio_set_value(&priv->rw_gpio, 1); + + udelay(10); + + log_debug("%s: type 0x%x, val 0x%x\n", + __func__, type, value); +} + +static ssize_t tegra_cpu_bridge_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct udevice *dev = (struct udevice *)host->dev; + struct tegra_cpu_bridge_priv *priv = dev_get_priv(dev); + u8 command = *(u8 *)msg->tx_buf; + const u8 *data = msg->tx_buf; + int i; + + tegra_cpu_bridge_write(priv, TEGRA_CPU_BRIDGE_COMM, command); + + for (i = 1; i < msg->tx_len; i++) + tegra_cpu_bridge_write(priv, TEGRA_CPU_BRIDGE_DATA, data[i]); + + return 0; +} + +static const struct mipi_dsi_host_ops tegra_cpu_bridge_host_ops = { + .transfer = tegra_cpu_bridge_transfer, +}; + +static int tegra_cpu_bridge_get_format(enum mipi_dsi_pixel_format format, u32 *fmt) +{ + switch (format) { + case MIPI_DSI_FMT_RGB888: + case MIPI_DSI_FMT_RGB666_PACKED: + *fmt = BASE_COLOR_SIZE_888; + break; + + case MIPI_DSI_FMT_RGB666: + *fmt = BASE_COLOR_SIZE_666; + break; + + case MIPI_DSI_FMT_RGB565: + *fmt = BASE_COLOR_SIZE_565; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int tegra_cpu_bridge_attach(struct udevice *dev) +{ + struct tegra_cpu_bridge_priv *priv = dev_get_priv(dev); + struct dc_disp_reg *disp = &priv->dc->disp; + struct dc_cmd_reg *cmd = &priv->dc->cmd; + struct dc_com_reg *com = &priv->dc->com; + u32 value; + int ret; + + writel(CTRL_MODE_STOP << CTRL_MODE_SHIFT, &cmd->disp_cmd); + writel(0, &disp->disp_win_opt); + writel(GENERAL_UPDATE, &cmd->state_ctrl); + writel(GENERAL_ACT_REQ, &cmd->state_ctrl); + + /* TODO: parametrize if needed */ + writel(V_PULSE1_ENABLE, &disp->disp_signal_opt0); + writel(PULSE_POLARITY_LOW, &disp->v_pulse1.v_pulse_ctrl); + + writel(PULSE_END(1), &disp->v_pulse1.v_pulse_pos[V_PULSE0_POSITION_A]); + writel(0, &disp->v_pulse1.v_pulse_pos[V_PULSE0_POSITION_B]); + writel(0, &disp->v_pulse1.v_pulse_pos[V_PULSE0_POSITION_C]); + + ret = dev_read_u32_array(dev, "nvidia,init-sequence", priv->spi_init_seq, 4); + if (!ret) { + value = 1 << FRAME_INIT_SEQ_CYCLES_SHIFT | + DC_SIGNAL_VPULSE1 << INIT_SEQ_DC_SIGNAL_SHIFT | + INIT_SEQUENCE_MODE_PLCD | SEND_INIT_SEQUENCE; + writel(value, &disp->seq_ctrl); + + writel(priv->spi_init_seq[0], &disp->spi_init_seq_data_a); + writel(priv->spi_init_seq[1], &disp->spi_init_seq_data_b); + writel(priv->spi_init_seq[2], &disp->spi_init_seq_data_c); + writel(priv->spi_init_seq[3], &disp->spi_init_seq_data_d); + } + + value = readl(&cmd->disp_cmd); + value &= ~CTRL_MODE_MASK; + value |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT; + writel(value, &cmd->disp_cmd); + + /* set LDC pin to V Pulse 1 */ + value = readl(&com->pin_output_sel[6]) | LDC_OUTPUT_SELECT_V_PULSE1; + writel(value, &com->pin_output_sel[6]); + + value = readl(&disp->disp_interface_ctrl); + value |= DATA_ALIGNMENT_LSB << DATA_ALIGNMENT_SHIFT; + writel(value, &disp->disp_interface_ctrl); + + value = SC_H_QUALIFIER_NONE << SC1_H_QUALIFIER_SHIFT | + SC_V_QUALIFIER_VACTIVE << SC0_V_QUALIFIER_SHIFT | + SC_H_QUALIFIER_HACTIVE << SC0_H_QUALIFIER_SHIFT; + writel(value, &disp->shift_clk_opt); + + value = readl(&disp->disp_color_ctrl); + value |= priv->pixel_format; + writel(value, &disp->disp_color_ctrl); + + /* Perform panel setup */ + panel_enable_backlight(priv->panel); + + dm_gpio_set_value(&priv->cs_gpio, 0); + + dm_gpio_free(dev, &priv->dc_gpio); + dm_gpio_free(dev, &priv->rw_gpio); + dm_gpio_free(dev, &priv->cs_gpio); + + gpio_free_list(dev, priv->data_gpios, 8); + + return 0; +} + +static int tegra_cpu_bridge_set_panel(struct udevice *dev, int percent) +{ + struct tegra_cpu_bridge_priv *priv = dev_get_priv(dev); + + return panel_set_backlight(priv->panel, percent); +} + +static int tegra_cpu_bridge_panel_timings(struct udevice *dev, + struct display_timing *timing) +{ + struct tegra_cpu_bridge_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + + return 0; +} + +static int tegra_cpu_bridge_hw_init(struct udevice *dev) +{ + struct tegra_cpu_bridge_priv *priv = dev_get_priv(dev); + + dm_gpio_set_value(&priv->cs_gpio, 1); + + dm_gpio_set_value(&priv->rw_gpio, 1); + dm_gpio_set_value(&priv->dc_gpio, 0); + + return 0; +} + +static int tegra_cpu_bridge_get_links(struct udevice *dev) +{ + struct tegra_cpu_bridge_priv *priv = dev_get_priv(dev); + int i, ret; + + u32 num = ofnode_graph_get_port_count(dev_ofnode(dev)); + + for (i = 0; i < num; i++) { + ofnode remote = ofnode_graph_get_remote_node(dev_ofnode(dev), i, -1); + + /* Look for DC source */ + if (ofnode_name_eq(remote, "rgb")) { + ofnode dc = ofnode_get_parent(remote); + + priv->dc = (struct dc_ctlr *)ofnode_get_addr(dc); + if (!priv->dc) { + log_err("%s: failed to get DC controller\n", __func__); + return -EINVAL; + } + } + + /* Look for driven panel */ + ret = uclass_get_device_by_ofnode(UCLASS_PANEL, remote, &priv->panel); + if (!ret) + return 0; + } + + /* If this point is reached, no panels were found */ + return -ENODEV; +} + +static int tegra_cpu_bridge_probe(struct udevice *dev) +{ + struct tegra_cpu_bridge_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct mipi_dsi_panel_plat *mipi_plat; + int ret; + + ret = tegra_cpu_bridge_get_links(dev); + if (ret) { + log_debug("%s: links not found, ret %d\n", __func__, ret); + return ret; + } + + panel_get_display_timing(priv->panel, &priv->timing); + + mipi_plat = dev_get_plat(priv->panel); + mipi_plat->device = device; + + priv->host.dev = (struct device *)dev; + priv->host.ops = &tegra_cpu_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_cpu_bridge_get_format(device->format, &priv->pixel_format); + + /* get control gpios */ + ret = gpio_request_by_name(dev, "dc-gpios", 0, + &priv->dc_gpio, GPIOD_IS_OUT); + if (ret) { + log_debug("%s: could not decode dc-gpios (%d)\n", __func__, ret); + return ret; + } + + ret = gpio_request_by_name(dev, "rw-gpios", 0, + &priv->rw_gpio, GPIOD_IS_OUT); + if (ret) { + log_debug("%s: could not decode rw-gpios (%d)\n", __func__, ret); + return ret; + } + + ret = gpio_request_by_name(dev, "cs-gpios", 0, + &priv->cs_gpio, GPIOD_IS_OUT); + if (ret) { + log_debug("%s: could not decode cs-gpios (%d)\n", __func__, ret); + return ret; + } + + /* get data gpios */ + ret = gpio_request_list_by_name(dev, "data-gpios", + priv->data_gpios, 8, + GPIOD_IS_OUT); + if (ret < 0) { + log_debug("%s: could not decode data-gpios (%d)\n", __func__, ret); + return ret; + } + + return tegra_cpu_bridge_hw_init(dev); +} + +static const struct video_bridge_ops tegra_cpu_bridge_ops = { + .attach = tegra_cpu_bridge_attach, + .set_backlight = tegra_cpu_bridge_set_panel, + .get_display_timing = tegra_cpu_bridge_panel_timings, +}; + +static const struct udevice_id tegra_cpu_bridge_ids[] = { + { .compatible = "nvidia,tegra-8bit-cpu" }, + { } +}; + +U_BOOT_DRIVER(tegra_8bit_cpu) = { + .name = "tegra_8bit_cpu", + .id = UCLASS_VIDEO_BRIDGE, + .of_match = tegra_cpu_bridge_ids, + .ops = &tegra_cpu_bridge_ops, + .bind = dm_scan_fdt_dev, + .probe = tegra_cpu_bridge_probe, + .priv_auto = sizeof(struct tegra_cpu_bridge_priv), +}; -- 2.43.0