video: tegra: add 8-bit CPU driven protocol

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 <ion@agorria.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
This commit is contained in:
Svyatoslav Ryhel
2024-10-04 11:54:46 +03:00
parent 5f3588a94d
commit 505dd92275
4 changed files with 384 additions and 0 deletions

View File

@@ -448,10 +448,19 @@ enum win_color_depth_id {
#define LVS_OUTPUT_POLARITY_LOW BIT(28) #define LVS_OUTPUT_POLARITY_LOW BIT(28)
#define LSC0_OUTPUT_POLARITY_LOW BIT(24) #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 */ /* DC_DISP_DISP_SIGNAL_OPTIONS0 0x400 */
#define H_PULSE0_ENABLE BIT(8) #define H_PULSE0_ENABLE BIT(8)
#define H_PULSE1_ENABLE BIT(10) #define H_PULSE1_ENABLE BIT(10)
#define H_PULSE2_ENABLE BIT(12) #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 */ /* DC_DISP_DISP_WIN_OPTIONS 0x402 */
#define CURSOR_ENABLE BIT(16) #define CURSOR_ENABLE BIT(16)
@@ -525,6 +534,28 @@ enum {
BASE_COLOR_SIZE_888, 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 */ /* DC_DISP_DATA_ENABLE_OPTIONS 0x432 */
#define DE_SELECT_SHIFT 0 #define DE_SELECT_SHIFT 0
#define DE_SELECT_MASK (0x3 << DE_SELECT_SHIFT) #define DE_SELECT_MASK (0x3 << DE_SELECT_SHIFT)
@@ -541,6 +572,23 @@ enum {
DE_CONTROL_ACTIVE_BLANK, 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 */ /* DC_WIN_WIN_OPTIONS 0x700 */
#define H_DIRECTION BIT(0) #define H_DIRECTION BIT(0)
enum { enum {

View File

@@ -42,6 +42,16 @@ config TEGRA_BACKLIGHT_PWM
Enable support for the Display Controller dependent PWM backlight Enable support for the Display Controller dependent PWM backlight
found in the Tegra SoC and usually used with DSI panels. 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 config VIDEO_TEGRA124
bool "Enable video support on Tegra124" bool "Enable video support on Tegra124"
imply VIDEO_DAMAGE imply VIDEO_DAMAGE

View File

@@ -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_DSI_TEGRA) += dsi.o mipi.o mipi-phy.o
obj-$(CONFIG_VIDEO_HDMI_TEGRA) += hdmi.o obj-$(CONFIG_VIDEO_HDMI_TEGRA) += hdmi.o
obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += dc-pwm-backlight.o obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += dc-pwm-backlight.o
obj-$(CONFIG_TEGRA_8BIT_CPU_BRIDGE) += cpu-bridge.o
obj-${CONFIG_VIDEO_TEGRA124} += tegra124/ obj-${CONFIG_VIDEO_TEGRA124} += tegra124/

View File

@@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2024 Svyatoslav Ryhel <clamor95@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),
};