* [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface
@ 2026-10-22 13:22 Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 1/7] dt-bindings: display: panel: Document Hitachi TX10D07VM0BAA and LG LH400WV3 panels Svyatoslav Ryhel
` (6 more replies)
0 siblings, 7 replies; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-22 13:22 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Mikko Perttunen, Svyatoslav Ryhel
Cc: dri-devel, devicetree, linux-kernel, linux-tegra
The display controller in Tegra20/30 SoCs features an 8-bit SPI interface
that closely resembles the MIPI DBI Type B protocol and is referred to as
'8-bit CPU'.
Add DC parametrization options needed for fine tuning by bridges.
Add 8-bit CPU interface in form of a bridge linking to RGB port.
Add 2 DBI Type B panels used in LG Optimus 2X which work with
the 8-bit CPU interface.
---
Changes in v2:
- convert enums in the dc header into definitions list
- add check in RGB if bridge_state is not NULL, if yes, use default flags
- inverted FLAG_PIXDATA_SAMPLE flag setup logic since default value is set to
be on NEGEDGE while v1 set default to be on POSEDGE
- 8bit-cpu schema renamed to dbi, removed compaible and set as DC child
- expanded list of required properties
- 8bit-cpu bridge remodelled into dbi interface as DC child
- improve gpio handling
- rework attach to get drvdata from parent
- add panel supplies description
- split panel driver into per-panel drivers
- get DBI interface from the patent directly
I have deliberately left GPIO parsing from device tree since it seems to
be the most convenient and mainstream method of working with GPIOs.
Changes in v3:
- rgb: fix default bus_flags to match original behavior
- dbi-schema: remove compatible from required props
remove dc@54200000 wrapper from example
restrict dbi nodes for tegra20/tegra30 only
- dbi: remove unused idx from tegra_dbi_priv
wrap dbi registration in dc with devm_add_action_or_reset()
separate dbi into separate object
export tegra_dc_commit() and trace_dc_readl()/dc_readl()
Changes in v4:
- reorder patches to avoid errors on checking dbi schema
- dbi schema renamed to nvidia,tegra20-dbi for consistency
- fix DC_DISP_V_PULSE0_POSITION_* > DC_DISP_V_PULSE1_POSITION_*
- add MODULE_ALIAS
- swap width and height in panels
- dropped use of init-sequence and gpios
- dbi driver reworked to use only DC to operate pins
---
Svyatoslav Ryhel (7):
dt-bindings: display: panel: Document Hitachi TX10D07VM0BAA and LG
LH400WV3 panels
drm/panel: Add Hitachi TX10D07VM0BAA MIPI DBI Type B panel driver
drm/panel: Add LG LH400WV3-SD04 MIPI DBI Type B panel driver
drm/tegra: dc: Expand available registers layouts
drm/tegra: rgb: Parameterize configuration based on bus flags
dt-bindings: display: tegra: Document 8-bit CPU parallel interface
drm/tegra: Add support for 8-bit CPU interface
.../display/panel/hit,tx10d07vm0baa.yaml | 58 ++
.../display/tegra/nvidia,tegra20-dbi.yaml | 108 ++++
.../display/tegra/nvidia,tegra20-dc.yaml | 15 +
drivers/gpu/drm/panel/Kconfig | 27 +
drivers/gpu/drm/panel/Makefile | 2 +
.../drm/panel/panel-hitachi-tx10d07vm0baa.c | 297 ++++++++++
drivers/gpu/drm/panel/panel-lg-lh400wv3.c | 236 ++++++++
drivers/gpu/drm/tegra/Kconfig | 7 +
drivers/gpu/drm/tegra/Makefile | 3 +
drivers/gpu/drm/tegra/dbi.c | 536 ++++++++++++++++++
drivers/gpu/drm/tegra/dc.c | 32 +-
drivers/gpu/drm/tegra/dc.h | 50 +-
drivers/gpu/drm/tegra/rgb.c | 41 +-
drivers/gpu/drm/tegra/trace.c | 3 +
14 files changed, 1403 insertions(+), 12 deletions(-)
create mode 100644 Documentation/devicetree/bindings/display/panel/hit,tx10d07vm0baa.yaml
create mode 100644 Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dbi.yaml
create mode 100644 drivers/gpu/drm/panel/panel-hitachi-tx10d07vm0baa.c
create mode 100644 drivers/gpu/drm/panel/panel-lg-lh400wv3.c
create mode 100644 drivers/gpu/drm/tegra/dbi.c
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 1/7] dt-bindings: display: panel: Document Hitachi TX10D07VM0BAA and LG LH400WV3 panels
2026-10-22 13:22 [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
@ 2026-10-22 13:22 ` Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 2/7] drm/panel: Add Hitachi TX10D07VM0BAA MIPI DBI Type B panel driver Svyatoslav Ryhel
` (5 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-22 13:22 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Mikko Perttunen, Svyatoslav Ryhel
Cc: dri-devel, devicetree, linux-kernel, linux-tegra
Document Hitachi TX10D07VM0BAA and LG LH400WV3 MIPI DBI 4" WVGA panels
found in LG Optimus 2X P990.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
.../display/panel/hit,tx10d07vm0baa.yaml | 58 +++++++++++++++++++
1 file changed, 58 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/panel/hit,tx10d07vm0baa.yaml
diff --git a/Documentation/devicetree/bindings/display/panel/hit,tx10d07vm0baa.yaml b/Documentation/devicetree/bindings/display/panel/hit,tx10d07vm0baa.yaml
new file mode 100644
index 0000000000000..4642dc14c42eb
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/hit,tx10d07vm0baa.yaml
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/hit,tx10d07vm0baa.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Hitachi/LG 4" WVGA TFT LCD panel
+
+maintainers:
+ - Svyatoslav Ryhel <clamor95@gmail.com>
+
+allOf:
+ - $ref: panel-common.yaml#
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - hit,tx10d07vm0baa
+ - lg,lh400wv3-sd04
+
+ avci-supply:
+ description: Panel power supply
+
+ iovcc-supply:
+ description: I/O voltage supply
+
+ reset-gpios: true
+
+ backlight: true
+ port: true
+
+required:
+ - compatible
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ panel {
+ compatible = "lg,lh400wv3-sd04";
+
+ reset-gpios = <&gpio 175 GPIO_ACTIVE_LOW>;
+
+ avci-supply = <&vcc_2v8_lcd>;
+ iovcc-supply = <&iovcc_1v8_lcd>;
+
+ backlight = <&backlight>;
+
+ port {
+ endpoint {
+ remote-endpoint = <&dbi_out>;
+ };
+ };
+ };
+...
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 2/7] drm/panel: Add Hitachi TX10D07VM0BAA MIPI DBI Type B panel driver
2026-10-22 13:22 [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 1/7] dt-bindings: display: panel: Document Hitachi TX10D07VM0BAA and LG LH400WV3 panels Svyatoslav Ryhel
@ 2026-10-22 13:22 ` Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 3/7] drm/panel: Add LG LH400WV3-SD04 " Svyatoslav Ryhel
` (4 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-22 13:22 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Mikko Perttunen, Svyatoslav Ryhel
Cc: dri-devel, devicetree, linux-kernel, linux-tegra
Add a driver for panel used in LG Optimus 2X P990. Panel is 4" WVGA MIPI
DBI Type B linked to DRM encoder via RGB to DBI bridge.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/gpu/drm/panel/Kconfig | 14 +
drivers/gpu/drm/panel/Makefile | 1 +
.../drm/panel/panel-hitachi-tx10d07vm0baa.c | 297 ++++++++++++++++++
3 files changed, 312 insertions(+)
create mode 100644 drivers/gpu/drm/panel/panel-hitachi-tx10d07vm0baa.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 53a1fc2913fee..5f68b78526c3e 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -274,6 +274,20 @@ config DRM_PANEL_HIMAX_HX8394
If M is selected the module will be called panel-himax-hx8394.
+config DRM_PANEL_HITACHI_TX10D07VM0BAA
+ tristate "Hitachi TX10D07VM0BAA MIPI DBI Type B panel"
+ depends on OF
+ depends on BACKLIGHT_CLASS_DEVICE
+ select DRM_MIPI_DBI
+ select VIDEOMODE_HELPERS
+ help
+ Say Y here if you want to enable support for the HITACHI
+ TX10D07VM0BAA MIPI DBI Type B panel found in the LG Optimus 2X
+ P990 smartphone.
+
+ To compile this driver as a module, choose M here: the module will
+ be called panel-hitachi-tx10d07vm0baa.
+
config DRM_PANEL_HYDIS_HV101HD1
tristate "Hydis HV101HD1 panel"
depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index f2c9c80a218f0..414267a49d922 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_PANEL_HIMAX_HX83112A) += panel-himax-hx83112a.o
obj-$(CONFIG_DRM_PANEL_HIMAX_HX83112B) += panel-himax-hx83112b.o
obj-$(CONFIG_DRM_PANEL_HIMAX_HX83121A) += panel-himax-hx83121a.o
obj-$(CONFIG_DRM_PANEL_HIMAX_HX8394) += panel-himax-hx8394.o
+obj-$(CONFIG_DRM_PANEL_HITACHI_TX10D07VM0BAA) += panel-hitachi-tx10d07vm0baa.o
obj-$(CONFIG_DRM_PANEL_HYDIS_HV101HD1) += panel-hydis-hv101hd1.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI7807S) += panel-ilitek-ili7807s.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI7836A) += panel-ilitek-ili7836a.o
diff --git a/drivers/gpu/drm/panel/panel-hitachi-tx10d07vm0baa.c b/drivers/gpu/drm/panel/panel-hitachi-tx10d07vm0baa.c
new file mode 100644
index 0000000000000..83c2af1be9755
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-hitachi-tx10d07vm0baa.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/array_size.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_mipi_dbi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+static const struct regulator_bulk_data hitachi_tx10d07vm0baa_supplies[] = {
+ { .supply = "avci" }, { .supply = "iovcc" },
+};
+
+struct hitachi_tx10d07vm0baa {
+ struct drm_panel panel;
+ struct mipi_dbi *dbi;
+
+ struct regulator_bulk_data *supplies;
+ struct gpio_desc *reset_gpio;
+};
+
+static inline struct hitachi_tx10d07vm0baa *to_hitachi_tx10d07vm0baa(struct drm_panel *panel)
+{
+ return container_of(panel, struct hitachi_tx10d07vm0baa, panel);
+}
+
+#define panel_dbi_command(priv, cmd, seq...) \
+({ \
+ const u8 d[] = { seq }; \
+ struct drm_panel *panel = &(priv)->panel; \
+ struct device *dev = panel->dev; \
+ int ret; \
+ ret = mipi_dbi_command_stackbuf((priv)->dbi, cmd, d, ARRAY_SIZE(d)); \
+ if (ret) \
+ dev_err_ratelimited(dev, "error %d when sending command %#02x\n", ret, cmd); \
+ ret; \
+})
+
+static int hitachi_tx10d07vm0baa_prepare(struct drm_panel *panel)
+{
+ struct hitachi_tx10d07vm0baa *priv = to_hitachi_tx10d07vm0baa(panel);
+ struct device *dev = panel->dev;
+ int ret;
+
+ priv->dbi = dev_get_drvdata(dev->parent);
+ if (!priv->dbi)
+ return -ENODEV;
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(hitachi_tx10d07vm0baa_supplies), priv->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable power supplies: %d\n", ret);
+ return ret;
+ }
+
+ usleep_range(1000, 2000);
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+
+ usleep_range(10000, 11000);
+
+ return 0;
+};
+
+static int hitachi_tx10d07vm0baa_enable(struct drm_panel *panel)
+{
+ struct hitachi_tx10d07vm0baa *priv = to_hitachi_tx10d07vm0baa(panel);
+
+ panel_dbi_command(priv, MIPI_DCS_SET_PARTIAL_ROWS, 0x00, 0x00, 0x03, 0x1f);
+ panel_dbi_command(priv, MIPI_DCS_SET_SCROLL_AREA, 0x00, 0x00, 0x03, 0x20,
+ 0x00, 0x00);
+
+ panel_dbi_command(priv, MIPI_DCS_SET_ADDRESS_MODE, 0x0a);
+ panel_dbi_command(priv, MIPI_DCS_SET_SCROLL_START, 0x00, 0x00);
+
+ panel_dbi_command(priv, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_24BIT);
+ panel_dbi_command(priv, MIPI_DCS_SET_TEAR_SCANLINE, 0x00, 0x00);
+
+ panel_dbi_command(priv, 0x71, 0x00); /* Ex_Vsync_en */
+
+ panel_dbi_command(priv, 0xb2, 0x00); /* VCSEL */
+ panel_dbi_command(priv, 0xb4, 0xaa); /* setvgmpm */
+ panel_dbi_command(priv, 0xb5, 0x33); /* rbias1 */
+ panel_dbi_command(priv, 0xb6, 0x03); /* rbias2 */
+
+ panel_dbi_command(priv, 0xb7, 0x1a, 0x33, 0x03, 0x03,
+ 0x03, 0x00, 0x00, 0x01, 0x02, 0x00,
+ 0x00, 0x04, 0x00, 0x01, 0x01, 0x01); /* set_ddvdhp */
+ panel_dbi_command(priv, 0xb8, 0x1c, 0x53, 0x03, 0x03,
+ 0x00, 0x01, 0x02, 0x00, 0x00, 0x04,
+ 0x00, 0x01, 0x01); /* set_ddvdhm */
+
+ panel_dbi_command(priv, 0xb9, 0x0a, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x02, 0x01); /* set_vgh */
+ panel_dbi_command(priv, 0xba, 0x0f, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x02, 0x01); /* set_vgl */
+ panel_dbi_command(priv, 0xbb, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x01); /* set_vcl */
+
+ panel_dbi_command(priv, 0xc1, 0x01); /* number of lines */
+ panel_dbi_command(priv, 0xc2, 0x08); /* number of fp lines */
+ panel_dbi_command(priv, 0xc3, 0x04); /* gateset(1) */
+ panel_dbi_command(priv, 0xc4, 0x4c); /* 1h period */
+ panel_dbi_command(priv, 0xc5, 0x03); /* source precharge */
+ panel_dbi_command(priv, 0xc6, 0xc4, 0x04); /* source precharge timing */
+ panel_dbi_command(priv, 0xc7, 0x00); /* source level */
+ panel_dbi_command(priv, 0xc8, 0x02); /* number of bp lines */
+ panel_dbi_command(priv, 0xc9, 0x10); /* gateset(2) */
+ panel_dbi_command(priv, 0xca, 0x04, 0x04); /* gateset(3) */
+ panel_dbi_command(priv, 0xcb, 0x03); /* gateset(4) */
+ panel_dbi_command(priv, 0xcc, 0x12); /* gateset(5) */
+ panel_dbi_command(priv, 0xcd, 0x12); /* gateset(6) */
+ panel_dbi_command(priv, 0xce, 0x30); /* gateset(7) */
+ panel_dbi_command(priv, 0xcf, 0x30); /* gateset(8) */
+ panel_dbi_command(priv, 0xd0, 0x40); /* gateset(9) */
+ panel_dbi_command(priv, 0xd1, 0x22); /* flhw */
+ panel_dbi_command(priv, 0xd2, 0x22); /* vckhw */
+ panel_dbi_command(priv, 0xd3, 0x04); /* flt */
+ panel_dbi_command(priv, 0xd4, 0x14); /* tctrl */
+ panel_dbi_command(priv, 0xd6, 0x02); /* dotinv */
+ panel_dbi_command(priv, 0xd7, 0x00); /* on/off sequence period */
+
+ panel_dbi_command(priv, 0xd8, 0x01, 0x05, 0x06, 0x0d,
+ 0x18, 0x09, 0x22, 0x23, 0x00); /* ponseqa */
+ panel_dbi_command(priv, 0xd9, 0x24, 0x01); /* ponseqb */
+ panel_dbi_command(priv, 0xde, 0x09, 0x0f, 0x21, 0x12,
+ 0x04); /* ponseqc */
+
+ panel_dbi_command(priv, 0xdf, 0x02, 0x06, 0x06, 0x06,
+ 0x06, 0x00); /* pofseqa */
+ panel_dbi_command(priv, 0xe0, 0x01); /* pofseqb */
+
+ panel_dbi_command(priv, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0xff);
+ panel_dbi_command(priv, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x40);
+
+ panel_dbi_command(priv, 0xe2, 0x00, 0x00); /* cabc pwm */
+ panel_dbi_command(priv, 0xe3, 0x03); /* cabc */
+ panel_dbi_command(priv, 0xe4, 0x66, 0x7b, 0x90, 0xa5,
+ 0xbb, 0xc7, 0xe1, 0xe5); /* cabc brightness */
+ panel_dbi_command(priv, 0xe5, 0xc5, 0xc5, 0xc9, 0xc9,
+ 0xd1, 0xe1, 0xf1, 0xfe); /* cabc brightness */
+ panel_dbi_command(priv, 0xe7, 0x2a); /* cabc */
+ panel_dbi_command(priv, 0xe8, 0x00); /* brt_rev */
+ panel_dbi_command(priv, 0xe9, 0x00); /* tefreq */
+
+ panel_dbi_command(priv, 0xea, 0x01); /* high speed ram */
+
+ panel_dbi_command(priv, 0xeb, 0x00, 0x33, 0x0e, 0x15,
+ 0xb7, 0x78, 0x88, 0x0f); /* gamma setting r pos */
+ panel_dbi_command(priv, 0xec, 0x00, 0x33, 0x0e, 0x15,
+ 0xb7, 0x78, 0x88, 0x0f); /* gamma setting r neg */
+ panel_dbi_command(priv, 0xed, 0x00, 0x33, 0x0e, 0x15,
+ 0xb7, 0x78, 0x88, 0x0f); /* gamma setting g pos */
+ panel_dbi_command(priv, 0xee, 0x00, 0x33, 0x0e, 0x15,
+ 0xb7, 0x78, 0x88, 0x0f); /* gamma setting g neg */
+ panel_dbi_command(priv, 0xef, 0x00, 0x33, 0x0e, 0x15,
+ 0xb7, 0x78, 0x88, 0x0f); /* gamma setting b pos */
+ panel_dbi_command(priv, 0xf0, 0x00, 0x33, 0x0e, 0x15,
+ 0xb7, 0x78, 0x88, 0x0f); /* gamma setting b neg */
+
+ panel_dbi_command(priv, MIPI_DCS_EXIT_SLEEP_MODE);
+ msleep(110);
+
+ panel_dbi_command(priv, MIPI_DCS_SET_DISPLAY_ON);
+
+ return 0;
+}
+
+static int hitachi_tx10d07vm0baa_disable(struct drm_panel *panel)
+{
+ struct hitachi_tx10d07vm0baa *priv = to_hitachi_tx10d07vm0baa(panel);
+
+ panel_dbi_command(priv, MIPI_DCS_SET_DISPLAY_OFF);
+ msleep(35);
+
+ panel_dbi_command(priv, MIPI_DCS_ENTER_SLEEP_MODE);
+ msleep(50);
+
+ return 0;
+}
+
+static int hitachi_tx10d07vm0baa_unprepare(struct drm_panel *panel)
+{
+ struct hitachi_tx10d07vm0baa *priv = to_hitachi_tx10d07vm0baa(panel);
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+ regulator_bulk_disable(ARRAY_SIZE(hitachi_tx10d07vm0baa_supplies), priv->supplies);
+
+ msleep(50);
+
+ return 0;
+}
+
+static const struct drm_display_mode hitachi_tx10d07vm0baa_mode = {
+ .clock = (480 + 10 + 10 + 10) * (800 + 4 + 4 + 4) * 60 / 1000,
+ .hdisplay = 480,
+ .hsync_start = 480 + 10,
+ .hsync_end = 480 + 10 + 10,
+ .htotal = 480 + 10 + 10 + 10,
+ .vdisplay = 800,
+ .vsync_start = 800 + 4,
+ .vsync_end = 800 + 4 + 4,
+ .vtotal = 800 + 4 + 4 + 4,
+ .width_mm = 52,
+ .height_mm = 86,
+ .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+static int hitachi_tx10d07vm0baa_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+
+ connector->display_info.bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
+ DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE |
+ DRM_BUS_FLAG_DE_HIGH |
+ DRM_BUS_FLAG_DATA_LSB_TO_MSB;
+
+ return drm_connector_helper_get_modes_fixed(connector, &hitachi_tx10d07vm0baa_mode);
+}
+
+static const struct drm_panel_funcs hitachi_tx10d07vm0baa_panel_funcs = {
+ .prepare = hitachi_tx10d07vm0baa_prepare,
+ .enable = hitachi_tx10d07vm0baa_enable,
+ .disable = hitachi_tx10d07vm0baa_disable,
+ .unprepare = hitachi_tx10d07vm0baa_unprepare,
+ .get_modes = hitachi_tx10d07vm0baa_get_modes,
+};
+
+static int hitachi_tx10d07vm0baa_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct hitachi_tx10d07vm0baa *priv;
+ int ret;
+
+ priv = devm_drm_panel_alloc(dev, struct hitachi_tx10d07vm0baa, panel,
+ &hitachi_tx10d07vm0baa_panel_funcs, DRM_MODE_CONNECTOR_DPI);
+ if (IS_ERR(priv))
+ return PTR_ERR(priv);
+
+ ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(hitachi_tx10d07vm0baa_supplies),
+ hitachi_tx10d07vm0baa_supplies, &priv->supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get supplies\n");
+
+ priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->reset_gpio),
+ "Failed to get reset gpio\n");
+
+ ret = drm_panel_of_backlight(&priv->panel);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get backlight\n");
+
+ ret = devm_drm_panel_add(dev, &priv->panel);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add panel\n");
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static const struct of_device_id hitachi_tx10d07vm0baa_of_match[] = {
+ { .compatible = "hit,tx10d07vm0baa" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, hitachi_tx10d07vm0baa_of_match);
+
+static struct platform_driver hitachi_tx10d07vm0baa_driver = {
+ .driver = {
+ .name = "panel-hitachi-tx10d07vm0baa",
+ .of_match_table = hitachi_tx10d07vm0baa_of_match,
+ },
+ .probe = hitachi_tx10d07vm0baa_probe,
+};
+module_platform_driver(hitachi_tx10d07vm0baa_driver);
+
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("HITACHI TX10D07VM0BAA MIPI DBI Type B panel driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 3/7] drm/panel: Add LG LH400WV3-SD04 MIPI DBI Type B panel driver
2026-10-22 13:22 [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 1/7] dt-bindings: display: panel: Document Hitachi TX10D07VM0BAA and LG LH400WV3 panels Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 2/7] drm/panel: Add Hitachi TX10D07VM0BAA MIPI DBI Type B panel driver Svyatoslav Ryhel
@ 2026-10-22 13:22 ` Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 4/7] drm/tegra: dc: Expand available registers layouts Svyatoslav Ryhel
` (3 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-22 13:22 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Mikko Perttunen, Svyatoslav Ryhel
Cc: dri-devel, devicetree, linux-kernel, linux-tegra
Add a driver for panel used in LG Optimus 2X P990. Panel is 4" WVGA MIPI
DBI Type B linked to DRM encoder via RGB to DBI bridge.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/gpu/drm/panel/Kconfig | 13 ++
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-lg-lh400wv3.c | 236 ++++++++++++++++++++++
3 files changed, 250 insertions(+)
create mode 100644 drivers/gpu/drm/panel/panel-lg-lh400wv3.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 5f68b78526c3e..f29b63ad8ba0f 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -554,6 +554,19 @@ config DRM_PANEL_LG_LG4573
Say Y here if you want to enable support for LG4573 RGB panel.
To compile this driver as a module, choose M here.
+config DRM_PANEL_LG_LH400WV3
+ tristate "LG LH400WV3 MIPI DBI Type B panel"
+ depends on OF
+ depends on BACKLIGHT_CLASS_DEVICE
+ select DRM_MIPI_DBI
+ select VIDEOMODE_HELPERS
+ help
+ Say Y here if you want to enable support for the LG LH400WV3 MIPI
+ DBI Type B panel found in the LG Optimus 2X P990 smartphone.
+
+ To compile this driver as a module, choose M here: the module will
+ be called panel-lg-lh400wv3.
+
config DRM_PANEL_LG_SW43408
tristate "LG SW43408 panel"
depends on GPIOLIB
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 414267a49d922..842a2a973777f 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_DRM_PANEL_LINCOLNTECH_LCD197) += panel-lincolntech-lcd197.o
obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
obj-$(CONFIG_DRM_PANEL_LG_LD070WX3) += panel-lg-ld070wx3.o
obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
+obj-$(CONFIG_DRM_PANEL_LG_LH400WV3) += panel-lg-lh400wv3.o
obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
obj-$(CONFIG_DRM_PANEL_LXD_M9189A) += panel-lxd-m9189a.o
obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
diff --git a/drivers/gpu/drm/panel/panel-lg-lh400wv3.c b/drivers/gpu/drm/panel/panel-lg-lh400wv3.c
new file mode 100644
index 0000000000000..557bcb6f2ca70
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-lg-lh400wv3.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/array_size.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_mipi_dbi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+static const struct regulator_bulk_data lg_lh400wv3_supplies[] = {
+ { .supply = "avci" }, { .supply = "iovcc" },
+};
+
+struct lg_lh400wv3 {
+ struct drm_panel panel;
+ struct mipi_dbi *dbi;
+
+ struct regulator_bulk_data *supplies;
+ struct gpio_desc *reset_gpio;
+};
+
+static inline struct lg_lh400wv3 *to_lg_lh400wv3(struct drm_panel *panel)
+{
+ return container_of(panel, struct lg_lh400wv3, panel);
+}
+
+#define panel_dbi_command(priv, cmd, seq...) \
+({ \
+ const u8 d[] = { seq }; \
+ struct drm_panel *panel = &(priv)->panel; \
+ struct device *dev = panel->dev; \
+ int ret; \
+ ret = mipi_dbi_command_stackbuf((priv)->dbi, cmd, d, ARRAY_SIZE(d)); \
+ if (ret) \
+ dev_err_ratelimited(dev, "error %d when sending command %#02x\n", ret, cmd); \
+ ret; \
+})
+
+static int lg_lh400wv3_prepare(struct drm_panel *panel)
+{
+ struct lg_lh400wv3 *priv = to_lg_lh400wv3(panel);
+ struct device *dev = panel->dev;
+ int ret;
+
+ priv->dbi = dev_get_drvdata(dev->parent);
+ if (!priv->dbi)
+ return -ENODEV;
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(lg_lh400wv3_supplies), priv->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable power supplies: %d\n", ret);
+ return ret;
+ }
+
+ usleep_range(1000, 2000);
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+
+ usleep_range(10000, 11000);
+
+ return 0;
+};
+
+static int lg_lh400wv3_enable(struct drm_panel *panel)
+{
+ struct lg_lh400wv3 *priv = to_lg_lh400wv3(panel);
+
+ panel_dbi_command(priv, MIPI_DCS_EXIT_INVERT_MODE);
+ panel_dbi_command(priv, MIPI_DCS_SET_TEAR_ON);
+
+ panel_dbi_command(priv, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_24BIT |
+ MIPI_DCS_PIXEL_FMT_24BIT << 4);
+
+ panel_dbi_command(priv, 0xb2, 0x00, 0xc8);
+ panel_dbi_command(priv, 0xb3, 0x00);
+ panel_dbi_command(priv, 0xb4, 0x04);
+ panel_dbi_command(priv, 0xb5, 0x42, 0x10, 0x10, 0x00, 0x20);
+ panel_dbi_command(priv, 0xb6, 0x0b, 0x0f, 0x3c, 0x13, 0x13, 0xe8);
+ panel_dbi_command(priv, 0xb7, 0x4c, 0x06, 0x0c, 0x00, 0x00);
+
+ panel_dbi_command(priv, 0xc0, 0x01, 0x11);
+ panel_dbi_command(priv, 0xc3, 0x07, 0x03, 0x04, 0x04, 0x04);
+ panel_dbi_command(priv, 0xc4, 0x12, 0x24, 0x18, 0x18, 0x02, 0x49);
+ panel_dbi_command(priv, 0xc5, 0x65);
+ panel_dbi_command(priv, 0xc6, 0x41, 0x63);
+
+ panel_dbi_command(priv, 0xd0, 0x00, 0x46, 0x74, 0x32, 0x1d, 0x03, 0x51, 0x15, 0x04);
+ panel_dbi_command(priv, 0xd1, 0x00, 0x46, 0x74, 0x32, 0x1d, 0x03, 0x51, 0x15, 0x04);
+ panel_dbi_command(priv, 0xd2, 0x00, 0x46, 0x74, 0x32, 0x1f, 0x03, 0x51, 0x15, 0x04);
+ panel_dbi_command(priv, 0xd3, 0x00, 0x46, 0x74, 0x32, 0x1f, 0x03, 0x51, 0x15, 0x04);
+ panel_dbi_command(priv, 0xd4, 0x01, 0x46, 0x74, 0x25, 0x00, 0x03, 0x51, 0x15, 0x04);
+ panel_dbi_command(priv, 0xd5, 0x01, 0x46, 0x74, 0x25, 0x00, 0x03, 0x51, 0x15, 0x04);
+
+ panel_dbi_command(priv, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x01, 0xdf);
+ panel_dbi_command(priv, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x03, 0x1f);
+
+ panel_dbi_command(priv, MIPI_DCS_EXIT_SLEEP_MODE);
+ msleep(120);
+
+ panel_dbi_command(priv, MIPI_DCS_WRITE_MEMORY_START);
+ panel_dbi_command(priv, MIPI_DCS_SET_DISPLAY_ON);
+
+ return 0;
+}
+
+static int lg_lh400wv3_disable(struct drm_panel *panel)
+{
+ struct lg_lh400wv3 *priv = to_lg_lh400wv3(panel);
+
+ panel_dbi_command(priv, MIPI_DCS_SET_DISPLAY_OFF);
+ panel_dbi_command(priv, MIPI_DCS_ENTER_SLEEP_MODE);
+ msleep(150);
+
+ panel_dbi_command(priv, 0xc1, 0x01);
+ usleep_range(10000, 11000);
+
+ return 0;
+};
+
+static int lg_lh400wv3_unprepare(struct drm_panel *panel)
+{
+ struct lg_lh400wv3 *priv = to_lg_lh400wv3(panel);
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+ regulator_bulk_disable(ARRAY_SIZE(lg_lh400wv3_supplies), priv->supplies);
+
+ msleep(50);
+
+ return 0;
+}
+
+static const struct drm_display_mode lg_lh400wv3_mode = {
+ .clock = (480 + 10 + 10 + 10) * (800 + 4 + 4 + 4) * 60 / 1000,
+ .hdisplay = 480,
+ .hsync_start = 480 + 10,
+ .hsync_end = 480 + 10 + 10,
+ .htotal = 480 + 10 + 10 + 10,
+ .vdisplay = 800,
+ .vsync_start = 800 + 4,
+ .vsync_end = 800 + 4 + 4,
+ .vtotal = 800 + 4 + 4 + 4,
+ .width_mm = 52,
+ .height_mm = 86,
+ .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+static int lg_lh400wv3_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+
+ connector->display_info.bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
+ DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE |
+ DRM_BUS_FLAG_DE_HIGH |
+ DRM_BUS_FLAG_DATA_LSB_TO_MSB;
+
+ return drm_connector_helper_get_modes_fixed(connector, &lg_lh400wv3_mode);
+}
+
+static const struct drm_panel_funcs lg_lh400wv3_panel_funcs = {
+ .prepare = lg_lh400wv3_prepare,
+ .enable = lg_lh400wv3_enable,
+ .disable = lg_lh400wv3_disable,
+ .unprepare = lg_lh400wv3_unprepare,
+ .get_modes = lg_lh400wv3_get_modes,
+};
+
+static int lg_lh400wv3_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lg_lh400wv3 *priv;
+ int ret;
+
+ priv = devm_drm_panel_alloc(dev, struct lg_lh400wv3, panel,
+ &lg_lh400wv3_panel_funcs, DRM_MODE_CONNECTOR_DPI);
+ if (IS_ERR(priv))
+ return PTR_ERR(priv);
+
+ ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(lg_lh400wv3_supplies),
+ lg_lh400wv3_supplies, &priv->supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get supplies\n");
+
+ priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->reset_gpio),
+ "Failed to get reset gpio\n");
+
+ ret = drm_panel_of_backlight(&priv->panel);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get backlight\n");
+
+ ret = devm_drm_panel_add(dev, &priv->panel);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add panel\n");
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static const struct of_device_id lg_lh400wv3_of_match[] = {
+ { .compatible = "lg,lh400wv3-sd04" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, lg_lh400wv3_of_match);
+
+static struct platform_driver lg_lh400wv3_driver = {
+ .driver = {
+ .name = "panel-lg-lh400wv3",
+ .of_match_table = lg_lh400wv3_of_match,
+ },
+ .probe = lg_lh400wv3_probe,
+};
+module_platform_driver(lg_lh400wv3_driver);
+
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("LG LH400WV3-SD04 MIPI DBI Type B panel driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 4/7] drm/tegra: dc: Expand available registers layouts
2026-10-22 13:22 [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
` (2 preceding siblings ...)
2026-10-22 13:22 ` [PATCH v4 3/7] drm/panel: Add LG LH400WV3-SD04 " Svyatoslav Ryhel
@ 2026-10-22 13:22 ` Svyatoslav Ryhel
2026-10-23 7:14 ` Mikko Perttunen
2026-10-22 13:22 ` [PATCH v4 5/7] drm/tegra: rgb: Parameterize configuration based on bus flags Svyatoslav Ryhel
` (2 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-22 13:22 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Mikko Perttunen, Svyatoslav Ryhel
Cc: dri-devel, devicetree, linux-kernel, linux-tegra
Expand existing DC register definitions with additional fields in
preparation for adding the 8-bit CPU interface.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/gpu/drm/tegra/dc.c | 3 ++-
drivers/gpu/drm/tegra/dc.h | 49 +++++++++++++++++++++++++++++++++-----
2 files changed, 45 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
index b0bfa946e6979..5c67928bcabfa 100644
--- a/drivers/gpu/drm/tegra/dc.c
+++ b/drivers/gpu/drm/tegra/dc.c
@@ -2384,7 +2384,8 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
if (dc->rgb) {
/* XXX: parameterize? */
- value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE;
+ value = SC0_H_QUALIFIER(SC_H_QUALIFIER_NONE) |
+ SC1_H_QUALIFIER(SC_H_QUALIFIER_NONE);
tegra_dc_writel(dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
}
diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
index 0cb0515968b35..7005df32f485f 100644
--- a/drivers/gpu/drm/tegra/dc.h
+++ b/drivers/gpu/drm/tegra/dc.h
@@ -274,8 +274,11 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
#define DC_COM_CRC_CHECKSUM 0x301
#define DC_COM_PIN_OUTPUT_ENABLE(x) (0x302 + (x))
#define DC_COM_PIN_OUTPUT_POLARITY(x) (0x306 + (x))
+#define LSC0_OUTPUT_POLARITY_LOW BIT(24)
#define LVS_OUTPUT_POLARITY_LOW (1 << 28)
#define LHS_OUTPUT_POLARITY_LOW (1 << 30)
+#define LSPI_OUTPUT_POLARITY_LOW BIT(8)
+#define LDC_OUTPUT_SELECT_V_PULSE1 BIT(14)
#define DC_COM_PIN_OUTPUT_DATA(x) (0x30a + (x))
#define DC_COM_PIN_INPUT_ENABLE(x) (0x30e + (x))
#define DC_COM_PIN_INPUT_DATA(x) (0x312 + (x))
@@ -303,9 +306,15 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
#define UNDERFLOW_REPORT_ENABLE (1 << 0)
#define DC_DISP_DISP_SIGNAL_OPTIONS0 0x400
-#define H_PULSE0_ENABLE (1 << 8)
-#define H_PULSE1_ENABLE (1 << 10)
-#define H_PULSE2_ENABLE (1 << 12)
+#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)
#define DC_DISP_DISP_SIGNAL_OPTIONS1 0x401
@@ -451,12 +460,26 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
#define BASE_COLOR_SIZE_888 ( 8 << 0)
#define BASE_COLOR_SIZE_101010 ( 10 << 0)
#define BASE_COLOR_SIZE_121212 ( 12 << 0)
+#define DISP_COLOR_SWAP_BGR BIT(16)
#define CMU_ENABLE_ENABLE (1 << 20)
#define DC_DISP_SHIFT_CLOCK_OPTIONS 0x431
-#define SC1_H_QUALIFIER_NONE (1 << 16)
-#define SC0_H_QUALIFIER_NONE (1 << 0)
-
+#define SC0_H_QUALIFIER(x) (((x) & 0x7) << 0)
+#define SC1_H_QUALIFIER(x) (((x) & 0x7) << 16)
+#define SC_H_QUALIFIER_DISABLE 0
+#define SC_H_QUALIFIER_NONE 1
+#define SC_H_QUALIFIER_HACTIVE 2
+#define SC_H_QUALIFIER_EXT_HACTIVE 3
+#define SC_H_QUALIFIER_HPULSE 4
+#define SC_H_QUALIFIER_EXT_HPULSE 5
+#define SC0_V_QUALIFIER(x) (((x) & 0x7) << 3)
+#define SC1_V_QUALIFIER(x) (((x) & 0x7) << 19)
+#define SC_V_QUALIFIER_NONE 0
+#define SC_V_QUALIFIER_RSVD 1
+#define SC_V_QUALIFIER_VACTIVE 2
+#define SC_V_QUALIFIER_EXT_VACTIVE 3
+#define SC_V_QUALIFIER_VPULSE 4
+#define SC_V_QUALIFIER_EXT_VPULSE 5
#define DC_DISP_DATA_ENABLE_OPTIONS 0x432
#define DE_SELECT_ACTIVE_BLANK (0 << 0)
#define DE_SELECT_ACTIVE (1 << 0)
@@ -493,6 +516,20 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
#define DC_DISP_CURSOR_POSITION_NS 0x441
#define 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)
+#define NO_DC_SIGNAL 0
+#define DC_SIGNAL_VSYNC 1
+#define DC_SIGNAL_VPULSE0 2
+#define DC_SIGNAL_VPULSE1 3
+#define DC_SIGNAL_VPULSE2 4
+#define DC_SIGNAL_VPULSE3 5
+#define INIT_SEQ_DC_CONTROL_SHIFT 7
+#define FRAME_INIT_SEQ_CYCLES_SHIFT 8
+
#define DC_DISP_SPI_INIT_SEQ_DATA_A 0x443
#define DC_DISP_SPI_INIT_SEQ_DATA_B 0x444
#define DC_DISP_SPI_INIT_SEQ_DATA_C 0x445
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 5/7] drm/tegra: rgb: Parameterize configuration based on bus flags
2026-10-22 13:22 [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
` (3 preceding siblings ...)
2026-10-22 13:22 ` [PATCH v4 4/7] drm/tegra: dc: Expand available registers layouts Svyatoslav Ryhel
@ 2026-10-22 13:22 ` Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 6/7] dt-bindings: display: tegra: Document 8-bit CPU parallel interface Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 7/7] drm/tegra: Add support for 8-bit CPU interface Svyatoslav Ryhel
6 siblings, 0 replies; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-22 13:22 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Mikko Perttunen, Svyatoslav Ryhel
Cc: dri-devel, devicetree, linux-kernel, linux-tegra
Parameterize configuration based on bus flags passed from the bridge or
panel. The list of supported flags includes now pixel clock polarity,
display enable (DE) polarity, and data alignment.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/gpu/drm/tegra/rgb.c | 41 ++++++++++++++++++++++++++++++++-----
1 file changed, 36 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c
index b6d09a8d8e5ea..da9e37fe36689 100644
--- a/drivers/gpu/drm/tegra/rgb.c
+++ b/drivers/gpu/drm/tegra/rgb.c
@@ -5,6 +5,7 @@
*/
#include <linux/clk.h>
+#include <linux/media-bus-format.h>
#include <linux/of.h>
#include <drm/drm_atomic_helper.h>
@@ -104,14 +105,24 @@ static void tegra_rgb_encoder_enable(struct drm_encoder *encoder)
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_rgb *rgb = to_rgb(output);
- u32 value;
+ struct drm_bridge_state *bridge_state;
+ u32 bus_flags, value;
+
+ /* Get but flags from the bridge state. */
+ bridge_state = drm_bridge_get_current_state(output->bridge);
+ if (bridge_state)
+ bus_flags = bridge_state->input_bus_cfg.flags;
+ else
+ bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
+ DRM_BUS_FLAG_DE_HIGH |
+ DRM_BUS_FLAG_DATA_MSB_TO_LSB;
tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable));
value = DE_SELECT_ACTIVE | DE_CONTROL_NORMAL;
tegra_dc_writel(rgb->dc, value, DC_DISP_DATA_ENABLE_OPTIONS);
- /* configure H- and V-sync signal polarities */
+ /* configure H- and V-sync and pixel clock signal polarities */
value = tegra_dc_readl(rgb->dc, DC_COM_PIN_OUTPUT_POLARITY(1));
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
@@ -124,11 +135,31 @@ static void tegra_rgb_encoder_enable(struct drm_encoder *encoder)
else
value &= ~LVS_OUTPUT_POLARITY_LOW;
+ if (bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE)
+ value &= ~LSC0_OUTPUT_POLARITY_LOW;
+ else
+ value |= LSC0_OUTPUT_POLARITY_LOW;
+
tegra_dc_writel(rgb->dc, value, DC_COM_PIN_OUTPUT_POLARITY(1));
- /* XXX: parameterize? */
- value = DISP_DATA_FORMAT_DF1P1C | DISP_ALIGNMENT_MSB |
- DISP_ORDER_RED_BLUE;
+ /* configure DE signal polarities */
+ value = tegra_dc_readl(rgb->dc, DC_COM_PIN_OUTPUT_POLARITY(3));
+
+ if (bus_flags & DRM_BUS_FLAG_DE_LOW)
+ value |= LSPI_OUTPUT_POLARITY_LOW;
+ else
+ value &= ~LSPI_OUTPUT_POLARITY_LOW;
+
+ tegra_dc_writel(rgb->dc, value, DC_COM_PIN_OUTPUT_POLARITY(3));
+
+ /* configure DATA order and alignment */
+ value = DISP_DATA_FORMAT_DF1P1C | DISP_ORDER_RED_BLUE;
+
+ if (bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
+ value |= DISP_ALIGNMENT_LSB;
+ else
+ value &= ~DISP_ALIGNMENT_LSB;
+
tegra_dc_writel(rgb->dc, value, DC_DISP_DISP_INTERFACE_CONTROL);
tegra_dc_commit(rgb->dc);
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 6/7] dt-bindings: display: tegra: Document 8-bit CPU parallel interface
2026-10-22 13:22 [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
` (4 preceding siblings ...)
2026-10-22 13:22 ` [PATCH v4 5/7] drm/tegra: rgb: Parameterize configuration based on bus flags Svyatoslav Ryhel
@ 2026-10-22 13:22 ` Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 7/7] drm/tegra: Add support for 8-bit CPU interface Svyatoslav Ryhel
6 siblings, 0 replies; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-22 13:22 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Mikko Perttunen, Svyatoslav Ryhel
Cc: dri-devel, devicetree, linux-kernel, linux-tegra
Document 8-bit CPU parallel MIPI DBI Type B interface provided by
Tegra20/30 SoCs display controller.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
.../display/tegra/nvidia,tegra20-dbi.yaml | 108 ++++++++++++++++++
.../display/tegra/nvidia,tegra20-dc.yaml | 15 +++
2 files changed, 123 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dbi.yaml
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dbi.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dbi.yaml
new file mode 100644
index 0000000000000..6b4fd20c5817c
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dbi.yaml
@@ -0,0 +1,108 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/tegra/nvidia,tegra20-dbi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nvidia Tegra Display Controller based MIPI DBI Type B interface
+
+maintainers:
+ - Svyatoslav Ryhel <clamor95@gmail.com>
+
+description: The display controller in Tegra20/30 SoCs features an
+ 8-bit SPI interface that closely resembles the MIPI DBI Type B
+ protocol and is referred to as '8-bit CPU'. Each display controller
+ provides two such interfaces, which can be used to send MIPI DCS
+ commands to initialize and control the panel while image data is
+ transmitted via 16/18/24-line RGB.
+
+properties:
+ nvidia,index:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Number of DBI interface used 0 - primary, 1 - secondary. If
+ property is not set, primary will be used.
+ enum: [ 0, 1 ]
+
+ panel:
+ type: object
+ description: Node of supported panel driven by the bridge.
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: Video port for RGB input.
+
+ properties:
+ endpoint:
+ $ref: /schemas/graph.yaml#/$defs/endpoint-base
+ unevaluatedProperties: false
+
+ properties:
+ bus-width:
+ enum: [ 16, 18, 24 ]
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Video port for DBI output (panel or connector).
+
+ required:
+ - port@0
+ - port@1
+
+required:
+ - panel
+ - ports
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ dbi {
+ nvidia,index = <0>;
+
+ panel {
+ compatible = "hit,tx10d07vm0baa";
+
+ reset-gpios = <&gpio 175 GPIO_ACTIVE_LOW>;
+
+ avci-supply = <&vcc_2v8_lcd>;
+ iovcc-supply = <&iovcc_1v8_lcd>;
+
+ backlight = <&backlight>;
+
+ port {
+ panel_input: endpoint {
+ remote-endpoint = <&bridge_output>;
+ };
+ };
+ };
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ bridge_input: endpoint {
+ remote-endpoint = <&dpi_output>;
+ bus-width = <24>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ bridge_output: endpoint {
+ remote-endpoint = <&panel_input>;
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml
index 69be95afd5627..a014bf0dd508d 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml
@@ -76,6 +76,9 @@ properties:
$ref: /schemas/types.yaml#/definitions/phandle-array
description: A list of phandles of outputs that this display controller can drive.
+ dbi:
+ $ref: /schemas/display/tegra/nvidia,tegra20-dbi.yaml#
+
rgb:
type: object
@@ -155,6 +158,18 @@ allOf:
- const: wind
- const: wint
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - nvidia,tegra114-dc
+ - nvidia,tegra124-dc
+ - nvidia,tegra210-dc
+ then:
+ properties:
+ dbi: false
+
additionalProperties: false
required:
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 7/7] drm/tegra: Add support for 8-bit CPU interface
2026-10-22 13:22 [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
` (5 preceding siblings ...)
2026-10-22 13:22 ` [PATCH v4 6/7] dt-bindings: display: tegra: Document 8-bit CPU parallel interface Svyatoslav Ryhel
@ 2026-10-22 13:22 ` Svyatoslav Ryhel
2026-10-23 7:53 ` Mikko Perttunen
6 siblings, 1 reply; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-22 13:22 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Mikko Perttunen, Svyatoslav Ryhel
Cc: dri-devel, devicetree, linux-kernel, linux-tegra
The display controller in Tegra20/30 SoCs features an 8-bit SPI interface
that closely resembles the MIPI DBI Type B protocol and is referred to as
'8-bit CPU'. Each display controller provides two such interfaces, which
can be used to send MIPI DCS commands to initialize and control the panel
while image data is transmitted via 16/18/24-line RGB.
The '8-bit CPU', although being the DC interface, is modeled using a
bridge framework for flexibility and convenience. This implementation does
not change the existing codebase besides registering an interface and can
be enabled/disabled without losing other Tegra DRM functionality.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/gpu/drm/tegra/Kconfig | 7 +
drivers/gpu/drm/tegra/Makefile | 3 +
drivers/gpu/drm/tegra/dbi.c | 536 +++++++++++++++++++++++++++++++++
drivers/gpu/drm/tegra/dc.c | 29 ++
drivers/gpu/drm/tegra/dc.h | 1 +
drivers/gpu/drm/tegra/trace.c | 3 +
6 files changed, 579 insertions(+)
create mode 100644 drivers/gpu/drm/tegra/dbi.c
diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig
index 8a3b16aac5d68..ce3a424398713 100644
--- a/drivers/gpu/drm/tegra/Kconfig
+++ b/drivers/gpu/drm/tegra/Kconfig
@@ -30,6 +30,13 @@ config DRM_TEGRA
if DRM_TEGRA
+config DRM_TEGRA_DBI
+ tristate "Enable MIPI DBI type B communication protocol for Tegra 20/30"
+ help
+ Tegra20 and Tegra30 feature 8 bit CPU driver panel control
+ protocol (similar to MIPI DBI Type B). This option allows use
+ it to set up and control compatible panel.
+
config DRM_TEGRA_DEBUG
bool "NVIDIA Tegra DRM debug support"
help
diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile
index e399b40d64a1d..d67e53248a082 100644
--- a/drivers/gpu/drm/tegra/Makefile
+++ b/drivers/gpu/drm/tegra/Makefile
@@ -33,3 +33,6 @@ tegra-drm-y += trace.o
tegra-drm-$(CONFIG_DRM_FBDEV_EMULATION) += fbdev.o
obj-$(CONFIG_DRM_TEGRA) += tegra-drm.o
+
+obj-$(CONFIG_DRM_TEGRA_DBI) += tegra-dbi.o
+tegra-dbi-y := dbi.o
diff --git a/drivers/gpu/drm/tegra/dbi.c b/drivers/gpu/drm/tegra/dbi.c
new file mode 100644
index 0000000000000..7bd90d32053f2
--- /dev/null
+++ b/drivers/gpu/drm/tegra/dbi.c
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_mipi_dbi.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include <video/mipi_display.h>
+
+#include "dc.h"
+
+#define TEGRA_DBI_COMMAND 0
+#define TEGRA_DBI_DATA 1
+#define TEGRA_DBI_DATA_PINS_MAX 8
+#define TEGRA_DBI_INTERFACE_MAX 2
+
+struct tegra_dbi_output {
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+};
+
+struct tegra_dbi_priv {
+ struct mipi_dbi dbi; /* must be first */
+ struct device *dev;
+ struct tegra_dc *dc;
+
+ struct drm_bridge bridge;
+ struct tegra_dbi_output output;
+
+ const struct tegra_dbi_interface *index;
+
+ u32 bus_width;
+ u32 idx;
+ bool active;
+};
+
+struct reg_entry {
+ u32 offset;
+ u32 value;
+};
+
+/*
+ * This is used to backup and restore COM pin state during
+ * tegra_dbi_atomic_enable/disable.
+ */
+static struct reg_entry com_pin_output[] = {
+ { DC_COM_PIN_OUTPUT_ENABLE(0), 0 },
+ { DC_COM_PIN_OUTPUT_ENABLE(1), 0 },
+ { DC_COM_PIN_OUTPUT_ENABLE(2), 0 },
+ { DC_COM_PIN_OUTPUT_ENABLE(3), 0 },
+ { DC_COM_PIN_OUTPUT_POLARITY(0), 0 },
+ { DC_COM_PIN_OUTPUT_POLARITY(1), 0 },
+ { DC_COM_PIN_OUTPUT_POLARITY(2), 0 },
+ { DC_COM_PIN_OUTPUT_POLARITY(3), 0 },
+ { DC_COM_PIN_OUTPUT_DATA(0), 0 },
+ { DC_COM_PIN_OUTPUT_DATA(1), 0 },
+ { DC_COM_PIN_OUTPUT_DATA(2), 0 },
+ { DC_COM_PIN_OUTPUT_DATA(3), 0 },
+ { DC_COM_PIN_OUTPUT_SELECT(0), 0 },
+ { DC_COM_PIN_OUTPUT_SELECT(1), 0 },
+ { DC_COM_PIN_OUTPUT_SELECT(2), 0 },
+ { DC_COM_PIN_OUTPUT_SELECT(3), 0 },
+ { DC_COM_PIN_OUTPUT_SELECT(4), 0 },
+ { DC_COM_PIN_OUTPUT_SELECT(5), 0 },
+ { DC_COM_PIN_OUTPUT_SELECT(6), 0 },
+};
+
+struct pin {
+ u32 reg;
+ u32 pos;
+};
+
+struct tegra_dbi_interface {
+ struct pin dc;
+ struct pin rw;
+ struct pin cs;
+ struct pin data[TEGRA_DBI_DATA_PINS_MAX];
+};
+
+static const struct tegra_dbi_interface tegra_dbi_idx[TEGRA_DBI_INTERFACE_MAX] = {
+ /*
+ * Nvidia Primary 8-bit CPU interface configuration:
+ * DC = LCD_DC0/LDC; RW = LCD_PCLK/LSC0; CS = LCD_CS0_N/LCSN
+ * DATA = LD0 - LD7
+ */
+ {
+ .dc = { DC_COM_PIN_OUTPUT_DATA(3), 6 },
+ .rw = { DC_COM_PIN_OUTPUT_DATA(1), 24 },
+ .cs = { DC_COM_PIN_OUTPUT_DATA(3), 4 },
+ .data = {
+ { DC_COM_PIN_OUTPUT_DATA(0), 0 },
+ { DC_COM_PIN_OUTPUT_DATA(0), 2 },
+ { DC_COM_PIN_OUTPUT_DATA(0), 4 },
+ { DC_COM_PIN_OUTPUT_DATA(0), 6 },
+ { DC_COM_PIN_OUTPUT_DATA(0), 8 },
+ { DC_COM_PIN_OUTPUT_DATA(0), 10 },
+ { DC_COM_PIN_OUTPUT_DATA(0), 12 },
+ { DC_COM_PIN_OUTPUT_DATA(0), 14 },
+ },
+ },
+ /*
+ * Nvidia Secondary 8-bit CPU interface configuration:
+ * DC = LCD_DC1/LVP0; RW = LCD_WR_N/LSC1; CS = LCD_CS1_N/LM0
+ * DATA0 = LCD_D18/LHP1 DATA4 = LCD_PWR0/LPW0
+ * DATA1 = LCD_M1/LM1 DATA5 = LCD_PWR2/LPW2
+ * DATA2 = LCD_D22/LDI DATA6 = LCD_D20/LVP1
+ * DATA3 = LCD_D23/LPP DATA7 = LCD_D21/LHP0
+ */
+ {
+ .dc = { DC_COM_PIN_OUTPUT_DATA(2), 8 },
+ .rw = { DC_COM_PIN_OUTPUT_DATA(1), 26 },
+ .cs = { DC_COM_PIN_OUTPUT_DATA(3), 16 },
+ .data = {
+ { DC_COM_PIN_OUTPUT_DATA(2), 2 },
+ { DC_COM_PIN_OUTPUT_DATA(2), 18 },
+ { DC_COM_PIN_OUTPUT_DATA(2), 20 },
+ { DC_COM_PIN_OUTPUT_DATA(2), 22 },
+ { DC_COM_PIN_OUTPUT_DATA(1), 16 },
+ { DC_COM_PIN_OUTPUT_DATA(1), 20 },
+ { DC_COM_PIN_OUTPUT_DATA(2), 10 },
+ { DC_COM_PIN_OUTPUT_DATA(2), 0 },
+ },
+ },
+};
+
+static inline struct tegra_dbi_priv *mipi_dbi_to_tegra_dbi(struct mipi_dbi *dbi)
+{
+ return container_of(dbi, struct tegra_dbi_priv, dbi);
+}
+
+static inline struct tegra_dbi_priv *bridge_to_tegra_dbi(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct tegra_dbi_priv, bridge);
+}
+
+static void tegra_dbi_pin_set(struct tegra_dc *dc, const struct pin *pin, int value)
+{
+ u32 val;
+
+ val = tegra_dc_readl(dc, pin->reg);
+ val |= BIT(pin->pos + 1); /* unmask is 1 bit above position */
+ if (value)
+ val |= BIT(pin->pos);
+ else
+ val &= ~BIT(pin->pos);
+ tegra_dc_writel(dc, val, pin->reg);
+}
+
+static void tegra_dbi_write(struct tegra_dbi_priv *priv, u8 type, u8 value)
+{
+ struct tegra_dc *dc = priv->dc;
+ const struct tegra_dbi_interface *idx = priv->index;
+
+ tegra_dbi_pin_set(dc, &idx->dc, type);
+
+ tegra_dbi_pin_set(dc, &idx->cs, 0);
+ tegra_dbi_pin_set(dc, &idx->rw, 0);
+
+ for (int i = 0; i < TEGRA_DBI_DATA_PINS_MAX; i++)
+ tegra_dbi_pin_set(dc, &idx->data[i], BIT(i) & value);
+
+ tegra_dbi_pin_set(dc, &idx->cs, 1);
+ tegra_dbi_pin_set(dc, &idx->rw, 1);
+
+ udelay(10);
+}
+
+static int tegra_dbi_dbi_command(struct mipi_dbi *dbi, u8 *cmd, u8 *param, size_t num)
+{
+ struct tegra_dbi_priv *priv = mipi_dbi_to_tegra_dbi(dbi);
+ u8 command = *cmd;
+
+ /* Blocks all commands when interface is not active */
+ if (!priv->active)
+ return -ENODEV;
+
+ tegra_dbi_write(priv, TEGRA_DBI_COMMAND, command);
+
+ for (int i = 0; i < num; i++)
+ tegra_dbi_write(priv, TEGRA_DBI_DATA, param[i]);
+
+ return 0;
+}
+
+static void tegra_dbi_init_primary_interface(struct tegra_dbi_priv *priv)
+{
+ struct tegra_dc *dc = priv->dc;
+ u32 value;
+
+ /* Set LCSN and LDC to output enable */
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_ENABLE(3));
+ value &= ~(BIT(4) | BIT(6));
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_ENABLE(3));
+
+ /* Set LSC0/LCSN/LDC output polarity HIGH */
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY(1));
+ value &= ~BIT(24);
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_POLARITY(1));
+
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY(3));
+ value &= ~(BIT(4) | BIT(6));
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_POLARITY(3));
+
+ /* Select output for pins LD0 - LD7 */
+ tegra_dc_writel(dc, 0x11111111, DC_COM_PIN_OUTPUT_SELECT(0));
+
+ /* Select output for pins LSC0/LCSN/LDC */
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(3));
+ value &= ~0x70000;
+ value |= BIT(16);
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(3));
+
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(6));
+ value &= ~0x7700;
+ value |= BIT(12) | BIT(8);
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(6));
+}
+
+static void tegra_dbi_init_secondary_interface(struct tegra_dbi_priv *priv)
+{
+ struct tegra_dc *dc = priv->dc;
+ u32 value;
+
+ /* Set LPP/LDI to output enable */
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_ENABLE(2));
+ value &= ~(BIT(20) | BIT(22));
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_ENABLE(2));
+
+ /* Select output for pins LSC1/LPW2/LPW0 */
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(3));
+ value &= ~0x700707;
+ value |= BIT(20) | BIT(8) | BIT(0);
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(3));
+
+ /* Select output for pins LVP1/LVP0/LHP1/LHP0 */
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(4));
+ value &= ~0x770077;
+ value |= BIT(20) | BIT(16) | BIT(4) | BIT(0);
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(4));
+
+ /* Select output for pins LPP/LDI/LM1/LM0 */
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(5));
+ value &= ~0x7777;
+ value |= BIT(12) | BIT(8) | BIT(4) | BIT(0);
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(5));
+}
+
+static void tegra_dbi_atomic_enable(struct drm_bridge *bridge,
+ struct drm_atomic_commit *state)
+{
+ struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
+ struct tegra_dc *dc = priv->dc;
+ u32 value;
+
+ value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
+ value &= ~DISP_CTRL_MODE_MASK;
+ tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
+
+ /* Disable all options except cursor if enabled */
+ value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
+ value &= CURSOR_ENABLE;
+ tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
+
+ tegra_dc_commit(dc);
+
+ tegra_dc_writel(dc, V_PULSE1_ENABLE, DC_DISP_DISP_SIGNAL_OPTIONS0);
+ tegra_dc_writel(dc, PULSE_POLARITY_LOW, DC_DISP_V_PULSE1_CONTROL);
+
+ tegra_dc_writel(dc, PULSE_END(1), DC_DISP_V_PULSE1_POSITION_A);
+ tegra_dc_writel(dc, 0, DC_DISP_V_PULSE1_POSITION_B);
+ tegra_dc_writel(dc, 0, DC_DISP_V_PULSE1_POSITION_C);
+
+ value = 1 << FRAME_INIT_SEQ_CYCLES_SHIFT |
+ DC_SIGNAL_VPULSE1 << INIT_SEQ_DC_SIGNAL_SHIFT |
+ INIT_SEQUENCE_MODE_PLCD | SEND_INIT_SEQUENCE;
+ tegra_dc_writel(dc, value, DC_DISP_INIT_SEQ_CONTROL);
+
+ /* Generic sequence for DBI command panels */
+ tegra_dc_writel(dc, 0x2c, DC_DISP_SPI_INIT_SEQ_DATA_A);
+ tegra_dc_writel(dc, 0x0, DC_DISP_SPI_INIT_SEQ_DATA_B);
+ tegra_dc_writel(dc, 0x0, DC_DISP_SPI_INIT_SEQ_DATA_C);
+ tegra_dc_writel(dc, 0x5000, DC_DISP_SPI_INIT_SEQ_DATA_D);
+
+ value = SC1_H_QUALIFIER(SC_H_QUALIFIER_NONE) |
+ SC0_V_QUALIFIER(SC_V_QUALIFIER_VACTIVE) |
+ SC0_H_QUALIFIER(SC_H_QUALIFIER_HACTIVE);
+ tegra_dc_writel(dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
+
+ value = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL);
+ value &= ~BASE_COLOR_SIZE_MASK;
+
+ switch (priv->bus_width) {
+ case 16:
+ value |= BASE_COLOR_SIZE_565;
+ break;
+ case 18:
+ value |= BASE_COLOR_SIZE_666;
+ break;
+ case 24:
+ default:
+ value |= BASE_COLOR_SIZE_888;
+ break;
+ }
+
+ tegra_dc_writel(dc, value, DC_DISP_DISP_COLOR_CONTROL);
+
+ /*
+ * Backup registers before unshadowing since actual register values
+ * may differ and their state backed up now is the correct one.
+ */
+ for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
+ com_pin_output[i].value = tegra_dc_readl(dc, com_pin_output[i].offset);
+
+ /* Unshadow to set up command and data pins */
+ tegra_dc_writel(dc, WRITE_MUX, DC_CMD_STATE_ACCESS);
+
+ if (priv->idx)
+ tegra_dbi_init_secondary_interface(priv);
+ else
+ tegra_dbi_init_primary_interface(priv);
+
+ priv->active = true;
+
+ if (priv->output.panel)
+ drm_panel_enable(priv->output.panel);
+
+ tegra_dbi_pin_set(dc, &priv->index->cs, 0);
+
+ priv->active = false;
+
+ for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
+ tegra_dc_writel(dc, com_pin_output[i].value, com_pin_output[i].offset);
+
+ /* Set LDC pin to V Pulse 1 */
+ value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(6));
+ value |= LDC_OUTPUT_SELECT_V_PULSE1;
+ tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(6));
+
+ value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
+ value &= ~DISP_CTRL_MODE_MASK;
+ value |= DISP_CTRL_MODE_C_DISPLAY;
+ tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
+
+ tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
+}
+
+static void tegra_dbi_atomic_disable(struct drm_bridge *bridge,
+ struct drm_atomic_commit *state)
+{
+ struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
+ struct tegra_dc *dc = priv->dc;
+
+ for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
+ com_pin_output[i].value = tegra_dc_readl(dc, com_pin_output[i].offset);
+
+ tegra_dc_writel(dc, WRITE_MUX, DC_CMD_STATE_ACCESS);
+
+ if (priv->idx)
+ tegra_dbi_init_secondary_interface(priv);
+ else
+ tegra_dbi_init_primary_interface(priv);
+
+ priv->active = true;
+
+ if (priv->output.panel)
+ drm_panel_disable(priv->output.panel);
+
+ priv->active = false;
+
+ for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
+ tegra_dc_writel(dc, com_pin_output[i].value, com_pin_output[i].offset);
+
+ tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
+}
+
+static int tegra_dbi_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+
+ /* Set safe default bus flags if panel does not specify them */
+ if (!bridge_state->input_bus_cfg.flags)
+ bridge_state->input_bus_cfg.flags = bridge->timings->input_bus_flags;
+
+ /* Default to positive sync */
+ if (!(adjusted_mode->flags &
+ (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC)))
+ adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC;
+
+ if (!(adjusted_mode->flags &
+ (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC)))
+ adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC;
+
+ return 0;
+}
+
+static int tegra_dbi_attach(struct drm_bridge *bridge,
+ struct drm_encoder *encoder,
+ enum drm_bridge_attach_flags flags)
+{
+ struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
+ struct device *dev = priv->dev;
+
+ priv->dc = dev_get_drvdata(dev->parent);
+ if (!priv->dc)
+ return -ENODEV;
+
+ return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge,
+ flags);
+}
+
+static enum drm_mode_status tegra_dbi_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ if (mode->hdisplay > 800)
+ return MODE_H_ILLEGAL;
+
+ if (mode->vdisplay > 800)
+ return MODE_V_ILLEGAL;
+
+ return MODE_OK;
+}
+
+static const struct drm_bridge_funcs tegra_dbi_funcs = {
+ .attach = tegra_dbi_attach,
+ .mode_valid = tegra_dbi_mode_valid,
+
+ .atomic_enable = tegra_dbi_atomic_enable,
+ .atomic_disable = tegra_dbi_atomic_disable,
+ .atomic_check = tegra_dbi_atomic_check,
+
+ .atomic_create_state = drm_atomic_helper_bridge_create_state,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+};
+
+static const struct drm_bridge_timings default_tegra_dbi_timings = {
+ .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
+ DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE |
+ DRM_BUS_FLAG_DE_HIGH,
+};
+
+static int tegra_dbi_probe(struct platform_device *pdev)
+{
+ struct tegra_dbi_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct drm_bridge *bridge;
+ struct drm_panel *panel;
+ struct device_node *ep;
+ int ret;
+
+ priv = devm_drm_bridge_alloc(dev, struct tegra_dbi_priv, bridge,
+ &tegra_dbi_funcs);
+ if (IS_ERR(priv))
+ return PTR_ERR(priv);
+
+ priv->dev = dev;
+
+ /* Default to primaty */
+ device_property_read_u32(dev, "nvidia,index", &priv->idx);
+ if (priv->idx)
+ priv->index = &tegra_dbi_idx[1];
+ else
+ priv->index = &tegra_dbi_idx[0];
+
+ /* Initialize MIPI DBI interface */
+ mutex_init(&priv->dbi.cmdlock);
+ priv->dbi.command = tegra_dbi_dbi_command;
+
+ ret = devm_of_platform_populate(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to probe children\n");
+
+ ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, &bridge);
+ if (ret)
+ return ret;
+
+ if (panel) {
+ bridge = devm_drm_panel_bridge_add_typed(dev, panel,
+ DRM_MODE_CONNECTOR_DPI);
+ if (IS_ERR(bridge))
+ return PTR_ERR(bridge);
+ }
+
+ priv->output.bridge = bridge;
+ priv->output.panel = panel;
+
+ /* get input ep (port0/endpoint0) */
+ ret = -EINVAL;
+ ep = of_graph_get_endpoint_by_regs(np, 0, 0);
+ if (ep) {
+ ret = of_property_read_u32(ep, "bus-width", &priv->bus_width);
+ of_node_put(ep);
+ }
+
+ if (ret)
+ priv->bus_width = 24;
+
+ priv->bridge.of_node = np;
+ priv->bridge.timings = &default_tegra_dbi_timings;
+
+ ret = devm_drm_bridge_add(dev, &priv->bridge);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add bridge\n");
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static struct platform_driver tegra_dbi_driver = {
+ .driver.name = "tegra-dbi",
+ .probe = tegra_dbi_probe,
+};
+module_platform_driver(tegra_dbi_driver);
+
+MODULE_ALIAS("platform:tegra-dbi");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("Nvidia Tegra20/30 MIPI-DBI Type B driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
index 5c67928bcabfa..8f12d30cd2d8b 100644
--- a/drivers/gpu/drm/tegra/dc.c
+++ b/drivers/gpu/drm/tegra/dc.c
@@ -223,6 +223,7 @@ void tegra_dc_commit(struct tegra_dc *dc)
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
+EXPORT_SYMBOL_GPL(tegra_dc_commit);
static inline u32 compute_dda_inc(unsigned int in, unsigned int out, bool v,
unsigned int bpp)
@@ -3279,9 +3280,15 @@ static int tegra_dc_init_opp_table(struct tegra_dc *dc)
return 0;
}
+static void tegra_dc_release_dbi_pdev(void *pdev)
+{
+ platform_device_unregister(pdev);
+}
+
static int tegra_dc_probe(struct platform_device *pdev)
{
u64 dma_mask = dma_get_mask(pdev->dev.parent);
+ struct device_node *dbi_np;
struct tegra_dc *dc;
int err;
@@ -3382,6 +3389,28 @@ static int tegra_dc_probe(struct platform_device *pdev)
}
}
+ dbi_np = of_get_available_child_by_name(pdev->dev.of_node, "dbi");
+ if (dbi_np) {
+ struct platform_device_info dbi_info = {
+ .parent = &pdev->dev,
+ .name = "tegra-dbi",
+ .id = PLATFORM_DEVID_AUTO,
+ .fwnode = of_fwnode_handle(dbi_np),
+ .of_node_reused = true,
+ };
+
+ dc->dbi = platform_device_register_full(&dbi_info);
+ of_node_put(dbi_np);
+ if (IS_ERR(dc->dbi))
+ return dev_err_probe(&pdev->dev, PTR_ERR(dc->dbi),
+ "Failed to create DBI device\n");
+
+ err = devm_add_action_or_reset(&pdev->dev, tegra_dc_release_dbi_pdev,
+ dc->dbi);
+ if (err)
+ return err;
+ }
+
err = tegra_dc_rgb_probe(dc);
if (err < 0 && err != -ENODEV)
return dev_err_probe(&pdev->dev, err,
diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
index 7005df32f485f..27dbee57f8f72 100644
--- a/drivers/gpu/drm/tegra/dc.h
+++ b/drivers/gpu/drm/tegra/dc.h
@@ -95,6 +95,7 @@ struct tegra_dc {
struct tegra_output *rgb;
struct tegra_pmc *pmc;
+ struct platform_device *dbi;
struct tegra_dc_stats stats;
struct list_head list;
diff --git a/drivers/gpu/drm/tegra/trace.c b/drivers/gpu/drm/tegra/trace.c
index 006f65c72a344..3aeb3825e2100 100644
--- a/drivers/gpu/drm/tegra/trace.c
+++ b/drivers/gpu/drm/tegra/trace.c
@@ -1,2 +1,5 @@
#define CREATE_TRACE_POINTS
#include "trace.h"
+
+EXPORT_TRACEPOINT_SYMBOL_GPL(dc_readl);
+EXPORT_TRACEPOINT_SYMBOL_GPL(dc_writel);
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 4/7] drm/tegra: dc: Expand available registers layouts
2026-10-22 13:22 ` [PATCH v4 4/7] drm/tegra: dc: Expand available registers layouts Svyatoslav Ryhel
@ 2026-10-23 7:14 ` Mikko Perttunen
2026-10-23 8:06 ` Svyatoslav Ryhel
0 siblings, 1 reply; 12+ messages in thread
From: Mikko Perttunen @ 2026-10-23 7:14 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Svyatoslav Ryhel, devicetree
Cc: dri-devel, devicetree, linux-kernel, linux-tegra, Svyatoslav Ryhel
On Thursday, October 22, 2026 10:22 PM Svyatoslav Ryhel wrote:
> Expand existing DC register definitions with additional fields in
> preparation for adding the 8-bit CPU interface.
>
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> ---
> drivers/gpu/drm/tegra/dc.c | 3 ++-
> drivers/gpu/drm/tegra/dc.h | 49 +++++++++++++++++++++++++++++++++-----
> 2 files changed, 45 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
> index b0bfa946e6979..5c67928bcabfa 100644
> --- a/drivers/gpu/drm/tegra/dc.c
> +++ b/drivers/gpu/drm/tegra/dc.c
> @@ -2384,7 +2384,8 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
>
> if (dc->rgb) {
> /* XXX: parameterize? */
> - value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE;
> + value = SC0_H_QUALIFIER(SC_H_QUALIFIER_NONE) |
> + SC1_H_QUALIFIER(SC_H_QUALIFIER_NONE);
> tegra_dc_writel(dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
> }
>
> diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
> index 0cb0515968b35..7005df32f485f 100644
> --- a/drivers/gpu/drm/tegra/dc.h
> +++ b/drivers/gpu/drm/tegra/dc.h
> @@ -274,8 +274,11 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
> #define DC_COM_CRC_CHECKSUM 0x301
> #define DC_COM_PIN_OUTPUT_ENABLE(x) (0x302 + (x))
> #define DC_COM_PIN_OUTPUT_POLARITY(x) (0x306 + (x))
> +#define LSC0_OUTPUT_POLARITY_LOW BIT(24)
> #define LVS_OUTPUT_POLARITY_LOW (1 << 28)
> #define LHS_OUTPUT_POLARITY_LOW (1 << 30)
> +#define LSPI_OUTPUT_POLARITY_LOW BIT(8)
Please update the existing defines to use BIT(n) as well, and align the
define values (incl. the lines around here that are already there) like
elsewhere in the file. Also, currently it's hard to tell which register
each bit define corresponds to, so I'd like some comments like ..
/* DC_COM_PIN_OUTPUT_POLARITY(0): */
#define ...
#define ...
> +#define LDC_OUTPUT_SELECT_V_PULSE1 BIT(14)
Please move this below the DC_COM_PIN_OUTPUT_SELECT(x) define.
> #define DC_COM_PIN_OUTPUT_DATA(x) (0x30a + (x))
> #define DC_COM_PIN_INPUT_ENABLE(x) (0x30e + (x))
> #define DC_COM_PIN_INPUT_DATA(x) (0x312 + (x))
> @@ -303,9 +306,15 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
> #define UNDERFLOW_REPORT_ENABLE (1 << 0)
>
> #define DC_DISP_DISP_SIGNAL_OPTIONS0 0x400
> -#define H_PULSE0_ENABLE (1 << 8)
> -#define H_PULSE1_ENABLE (1 << 10)
> -#define H_PULSE2_ENABLE (1 << 12)
> +#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)
>
> #define DC_DISP_DISP_SIGNAL_OPTIONS1 0x401
>
> @@ -451,12 +460,26 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
> #define BASE_COLOR_SIZE_888 ( 8 << 0)
> #define BASE_COLOR_SIZE_101010 ( 10 << 0)
> #define BASE_COLOR_SIZE_121212 ( 12 << 0)
> +#define DISP_COLOR_SWAP_BGR BIT(16)
> #define CMU_ENABLE_ENABLE (1 << 20)
>
> #define DC_DISP_SHIFT_CLOCK_OPTIONS 0x431
> -#define SC1_H_QUALIFIER_NONE (1 << 16)
> -#define SC0_H_QUALIFIER_NONE (1 << 0)
> -
> +#define SC0_H_QUALIFIER(x) (((x) & 0x7) << 0)
> +#define SC1_H_QUALIFIER(x) (((x) & 0x7) << 16)
> +#define SC_H_QUALIFIER_DISABLE 0
> +#define SC_H_QUALIFIER_NONE 1
> +#define SC_H_QUALIFIER_HACTIVE 2
> +#define SC_H_QUALIFIER_EXT_HACTIVE 3
> +#define SC_H_QUALIFIER_HPULSE 4
> +#define SC_H_QUALIFIER_EXT_HPULSE 5
> +#define SC0_V_QUALIFIER(x) (((x) & 0x7) << 3)
> +#define SC1_V_QUALIFIER(x) (((x) & 0x7) << 19)
> +#define SC_V_QUALIFIER_NONE 0
> +#define SC_V_QUALIFIER_RSVD 1
> +#define SC_V_QUALIFIER_VACTIVE 2
> +#define SC_V_QUALIFIER_EXT_VACTIVE 3
> +#define SC_V_QUALIFIER_VPULSE 4
> +#define SC_V_QUALIFIER_EXT_VPULSE 5
> #define DC_DISP_DATA_ENABLE_OPTIONS 0x432
> #define DE_SELECT_ACTIVE_BLANK (0 << 0)
> #define DE_SELECT_ACTIVE (1 << 0)
> @@ -493,6 +516,20 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
> #define DC_DISP_CURSOR_POSITION_NS 0x441
>
> #define 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)
> +#define NO_DC_SIGNAL 0
> +#define DC_SIGNAL_VSYNC 1
> +#define DC_SIGNAL_VPULSE0 2
> +#define DC_SIGNAL_VPULSE1 3
> +#define DC_SIGNAL_VPULSE2 4
> +#define DC_SIGNAL_VPULSE3 5
> +#define INIT_SEQ_DC_CONTROL_SHIFT 7
> +#define FRAME_INIT_SEQ_CYCLES_SHIFT 8
> +
> #define DC_DISP_SPI_INIT_SEQ_DATA_A 0x443
> #define DC_DISP_SPI_INIT_SEQ_DATA_B 0x444
> #define DC_DISP_SPI_INIT_SEQ_DATA_C 0x445
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 7/7] drm/tegra: Add support for 8-bit CPU interface
2026-10-22 13:22 ` [PATCH v4 7/7] drm/tegra: Add support for 8-bit CPU interface Svyatoslav Ryhel
@ 2026-10-23 7:53 ` Mikko Perttunen
2026-10-23 8:26 ` Svyatoslav Ryhel
0 siblings, 1 reply; 12+ messages in thread
From: Mikko Perttunen @ 2026-10-23 7:53 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Svyatoslav Ryhel, devicetree
Cc: dri-devel, devicetree, linux-kernel, linux-tegra, Svyatoslav Ryhel
On Thursday, October 22, 2026 10:22 PM Svyatoslav Ryhel wrote:
> The display controller in Tegra20/30 SoCs features an 8-bit SPI interface
> that closely resembles the MIPI DBI Type B protocol and is referred to as
> '8-bit CPU'. Each display controller provides two such interfaces, which
> can be used to send MIPI DCS commands to initialize and control the panel
> while image data is transmitted via 16/18/24-line RGB.
>
> The '8-bit CPU', although being the DC interface, is modeled using a
> bridge framework for flexibility and convenience. This implementation does
> not change the existing codebase besides registering an interface and can
> be enabled/disabled without losing other Tegra DRM functionality.
>
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> ---
> drivers/gpu/drm/tegra/Kconfig | 7 +
> drivers/gpu/drm/tegra/Makefile | 3 +
> drivers/gpu/drm/tegra/dbi.c | 536 +++++++++++++++++++++++++++++++++
> drivers/gpu/drm/tegra/dc.c | 29 ++
> drivers/gpu/drm/tegra/dc.h | 1 +
> drivers/gpu/drm/tegra/trace.c | 3 +
> 6 files changed, 579 insertions(+)
> create mode 100644 drivers/gpu/drm/tegra/dbi.c
>
> diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig
> index 8a3b16aac5d68..ce3a424398713 100644
> --- a/drivers/gpu/drm/tegra/Kconfig
> +++ b/drivers/gpu/drm/tegra/Kconfig
> @@ -30,6 +30,13 @@ config DRM_TEGRA
>
> if DRM_TEGRA
>
> +config DRM_TEGRA_DBI
> + tristate "Enable MIPI DBI type B communication protocol for Tegra 20/30"
> + help
> + Tegra20 and Tegra30 feature 8 bit CPU driver panel control
> + protocol (similar to MIPI DBI Type B). This option allows use
> + it to set up and control compatible panel.
> +
> config DRM_TEGRA_DEBUG
> bool "NVIDIA Tegra DRM debug support"
> help
> diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile
> index e399b40d64a1d..d67e53248a082 100644
> --- a/drivers/gpu/drm/tegra/Makefile
> +++ b/drivers/gpu/drm/tegra/Makefile
> @@ -33,3 +33,6 @@ tegra-drm-y += trace.o
> tegra-drm-$(CONFIG_DRM_FBDEV_EMULATION) += fbdev.o
>
> obj-$(CONFIG_DRM_TEGRA) += tegra-drm.o
> +
> +obj-$(CONFIG_DRM_TEGRA_DBI) += tegra-dbi.o
> +tegra-dbi-y := dbi.o
> diff --git a/drivers/gpu/drm/tegra/dbi.c b/drivers/gpu/drm/tegra/dbi.c
> new file mode 100644
> index 0000000000000..7bd90d32053f2
> --- /dev/null
> +++ b/drivers/gpu/drm/tegra/dbi.c
> @@ -0,0 +1,536 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/kernel.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/property.h>
> +#include <linux/platform_device.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_mipi_dbi.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +
> +#include <video/mipi_display.h>
> +
> +#include "dc.h"
> +
> +#define TEGRA_DBI_COMMAND 0
> +#define TEGRA_DBI_DATA 1
I'd use an enum for COMMAND/DATA -- as is it's not quite obvious these
are alternative values. It would also give a proper typing for the
parameter to dbi_write.
> +#define TEGRA_DBI_DATA_PINS_MAX 8
> +#define TEGRA_DBI_INTERFACE_MAX 2
> +
> +struct tegra_dbi_output {
> + struct drm_panel *panel;
> + struct drm_bridge *bridge;
> +};
> +
> +struct tegra_dbi_priv {
> + struct mipi_dbi dbi; /* must be first */
> + struct device *dev;
> + struct tegra_dc *dc;
> +
> + struct drm_bridge bridge;
> + struct tegra_dbi_output output;
> +
> + const struct tegra_dbi_interface *index;
> +
> + u32 bus_width;
> + u32 idx;
> + bool active;
> +};
> +
> +struct reg_entry {
> + u32 offset;
> + u32 value;
> +};
> +
> +/*
> + * This is used to backup and restore COM pin state during
> + * tegra_dbi_atomic_enable/disable.
> + */
> +static struct reg_entry com_pin_output[] = {
> + { DC_COM_PIN_OUTPUT_ENABLE(0), 0 },
> + { DC_COM_PIN_OUTPUT_ENABLE(1), 0 },
> + { DC_COM_PIN_OUTPUT_ENABLE(2), 0 },
> + { DC_COM_PIN_OUTPUT_ENABLE(3), 0 },
> + { DC_COM_PIN_OUTPUT_POLARITY(0), 0 },
> + { DC_COM_PIN_OUTPUT_POLARITY(1), 0 },
> + { DC_COM_PIN_OUTPUT_POLARITY(2), 0 },
> + { DC_COM_PIN_OUTPUT_POLARITY(3), 0 },
> + { DC_COM_PIN_OUTPUT_DATA(0), 0 },
> + { DC_COM_PIN_OUTPUT_DATA(1), 0 },
> + { DC_COM_PIN_OUTPUT_DATA(2), 0 },
> + { DC_COM_PIN_OUTPUT_DATA(3), 0 },
> + { DC_COM_PIN_OUTPUT_SELECT(0), 0 },
> + { DC_COM_PIN_OUTPUT_SELECT(1), 0 },
> + { DC_COM_PIN_OUTPUT_SELECT(2), 0 },
> + { DC_COM_PIN_OUTPUT_SELECT(3), 0 },
> + { DC_COM_PIN_OUTPUT_SELECT(4), 0 },
> + { DC_COM_PIN_OUTPUT_SELECT(5), 0 },
> + { DC_COM_PIN_OUTPUT_SELECT(6), 0 },
> +};
> +
> +struct pin {
> + u32 reg;
> + u32 pos;
> +};
> +
> +struct tegra_dbi_interface {
> + struct pin dc;
> + struct pin rw;
> + struct pin cs;
> + struct pin data[TEGRA_DBI_DATA_PINS_MAX];
> +};
> +
> +static const struct tegra_dbi_interface tegra_dbi_idx[TEGRA_DBI_INTERFACE_MAX] = {
> + /*
> + * Nvidia Primary 8-bit CPU interface configuration:
> + * DC = LCD_DC0/LDC; RW = LCD_PCLK/LSC0; CS = LCD_CS0_N/LCSN
> + * DATA = LD0 - LD7
> + */
> + {
> + .dc = { DC_COM_PIN_OUTPUT_DATA(3), 6 },
> + .rw = { DC_COM_PIN_OUTPUT_DATA(1), 24 },
> + .cs = { DC_COM_PIN_OUTPUT_DATA(3), 4 },
> + .data = {
> + { DC_COM_PIN_OUTPUT_DATA(0), 0 },
> + { DC_COM_PIN_OUTPUT_DATA(0), 2 },
> + { DC_COM_PIN_OUTPUT_DATA(0), 4 },
> + { DC_COM_PIN_OUTPUT_DATA(0), 6 },
> + { DC_COM_PIN_OUTPUT_DATA(0), 8 },
> + { DC_COM_PIN_OUTPUT_DATA(0), 10 },
> + { DC_COM_PIN_OUTPUT_DATA(0), 12 },
> + { DC_COM_PIN_OUTPUT_DATA(0), 14 },
I'd like to see defines added for the bit offsets (in dc.h).
Alternatively perhaps we could have an enum tegra_dc_pin with
TEGRA_DC_PIN_... variants in the same order as the pins are in the
various registers (I assume they are in the same order in all
registers). Then we could refer to those enumeration values here
and compute the register and bit from it dynamically.
> + },
> + },
> + /*
> + * Nvidia Secondary 8-bit CPU interface configuration:
> + * DC = LCD_DC1/LVP0; RW = LCD_WR_N/LSC1; CS = LCD_CS1_N/LM0
> + * DATA0 = LCD_D18/LHP1 DATA4 = LCD_PWR0/LPW0
> + * DATA1 = LCD_M1/LM1 DATA5 = LCD_PWR2/LPW2
> + * DATA2 = LCD_D22/LDI DATA6 = LCD_D20/LVP1
> + * DATA3 = LCD_D23/LPP DATA7 = LCD_D21/LHP0
> + */
> + {
> + .dc = { DC_COM_PIN_OUTPUT_DATA(2), 8 },
> + .rw = { DC_COM_PIN_OUTPUT_DATA(1), 26 },
> + .cs = { DC_COM_PIN_OUTPUT_DATA(3), 16 },
> + .data = {
> + { DC_COM_PIN_OUTPUT_DATA(2), 2 },
> + { DC_COM_PIN_OUTPUT_DATA(2), 18 },
> + { DC_COM_PIN_OUTPUT_DATA(2), 20 },
> + { DC_COM_PIN_OUTPUT_DATA(2), 22 },
> + { DC_COM_PIN_OUTPUT_DATA(1), 16 },
> + { DC_COM_PIN_OUTPUT_DATA(1), 20 },
> + { DC_COM_PIN_OUTPUT_DATA(2), 10 },
> + { DC_COM_PIN_OUTPUT_DATA(2), 0 },
> + },
> + },
> +};
> +
> +static inline struct tegra_dbi_priv *mipi_dbi_to_tegra_dbi(struct mipi_dbi *dbi)
> +{
> + return container_of(dbi, struct tegra_dbi_priv, dbi);
> +}
> +
> +static inline struct tegra_dbi_priv *bridge_to_tegra_dbi(struct drm_bridge *bridge)
> +{
> + return container_of(bridge, struct tegra_dbi_priv, bridge);
> +}
> +
> +static void tegra_dbi_pin_set(struct tegra_dc *dc, const struct pin *pin, int value)
I would change this to 'bool value', and and have '!!(BIT(i) & value)'
for more descriptive types.
> +{
> + u32 val;
> +
> + val = tegra_dc_readl(dc, pin->reg);
You don't need to read the register value -- the purpose of the unmask
bit is that only the data bits that are unmasked in the write are
modified.
> + val |= BIT(pin->pos + 1); /* unmask is 1 bit above position */
> + if (value)
> + val |= BIT(pin->pos);
> + else
> + val &= ~BIT(pin->pos);
> + tegra_dc_writel(dc, val, pin->reg);
> +}
> +
> +static void tegra_dbi_write(struct tegra_dbi_priv *priv, u8 type, u8 value)
> +{
> + struct tegra_dc *dc = priv->dc;
> + const struct tegra_dbi_interface *idx = priv->index;
> +
> + tegra_dbi_pin_set(dc, &idx->dc, type);
> +
> + tegra_dbi_pin_set(dc, &idx->cs, 0);
> + tegra_dbi_pin_set(dc, &idx->rw, 0);
> +
> + for (int i = 0; i < TEGRA_DBI_DATA_PINS_MAX; i++)
> + tegra_dbi_pin_set(dc, &idx->data[i], BIT(i) & value);
> +
> + tegra_dbi_pin_set(dc, &idx->cs, 1);
> + tegra_dbi_pin_set(dc, &idx->rw, 1);
> +
> + udelay(10);
> +}
> +
> +static int tegra_dbi_dbi_command(struct mipi_dbi *dbi, u8 *cmd, u8 *param, size_t num)
> +{
> + struct tegra_dbi_priv *priv = mipi_dbi_to_tegra_dbi(dbi);
> + u8 command = *cmd;
> +
> + /* Blocks all commands when interface is not active */
> + if (!priv->active)
> + return -ENODEV;
> +
> + tegra_dbi_write(priv, TEGRA_DBI_COMMAND, command);
> +
> + for (int i = 0; i < num; i++)
> + tegra_dbi_write(priv, TEGRA_DBI_DATA, param[i]);
> +
> + return 0;
> +}
> +
> +static void tegra_dbi_init_primary_interface(struct tegra_dbi_priv *priv)
> +{
> + struct tegra_dc *dc = priv->dc;
> + u32 value;
> +
> + /* Set LCSN and LDC to output enable */
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_ENABLE(3));
> + value &= ~(BIT(4) | BIT(6));
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_ENABLE(3));
We should use defines, or something like the enum tegra_dc_pin thing to
calculate the parameter to DC_COM_PIN_OUTPUT/SELECT and the bit offset.
> +
> + /* Set LSC0/LCSN/LDC output polarity HIGH */
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY(1));
> + value &= ~BIT(24);
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_POLARITY(1));
> +
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY(3));
> + value &= ~(BIT(4) | BIT(6));
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_POLARITY(3));
> +
> + /* Select output for pins LD0 - LD7 */
> + tegra_dc_writel(dc, 0x11111111, DC_COM_PIN_OUTPUT_SELECT(0));
> +
> + /* Select output for pins LSC0/LCSN/LDC */
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(3));
> + value &= ~0x70000;
Please write this as (0x7 << 16) for clarity, similarly below.
> + value |= BIT(16);
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(3));
> +
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(6));
> + value &= ~0x7700;
> + value |= BIT(12) | BIT(8);
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(6));
> +}
> +
> +static void tegra_dbi_init_secondary_interface(struct tegra_dbi_priv *priv)
> +{
> + struct tegra_dc *dc = priv->dc;
> + u32 value;
> +
> + /* Set LPP/LDI to output enable */
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_ENABLE(2));
> + value &= ~(BIT(20) | BIT(22));
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_ENABLE(2));
> +
> + /* Select output for pins LSC1/LPW2/LPW0 */
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(3));
> + value &= ~0x700707;
> + value |= BIT(20) | BIT(8) | BIT(0);
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(3));
> +
> + /* Select output for pins LVP1/LVP0/LHP1/LHP0 */
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(4));
> + value &= ~0x770077;
> + value |= BIT(20) | BIT(16) | BIT(4) | BIT(0);
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(4));
> +
> + /* Select output for pins LPP/LDI/LM1/LM0 */
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(5));
> + value &= ~0x7777;
> + value |= BIT(12) | BIT(8) | BIT(4) | BIT(0);
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(5));
> +}
> +
> +static void tegra_dbi_atomic_enable(struct drm_bridge *bridge,
> + struct drm_atomic_commit *state)
> +{
> + struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
> + struct tegra_dc *dc = priv->dc;
> + u32 value;
> +
> + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
> + value &= ~DISP_CTRL_MODE_MASK;
> + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
> +
> + /* Disable all options except cursor if enabled */
> + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
> + value &= CURSOR_ENABLE;
> + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
Just 'tegra_dc_writel(dc, CURSOR_ENABLE, DC_DISP_DISP_WIN_OPTIONS);'
> +
> + tegra_dc_commit(dc);
> +
> + tegra_dc_writel(dc, V_PULSE1_ENABLE, DC_DISP_DISP_SIGNAL_OPTIONS0);
> + tegra_dc_writel(dc, PULSE_POLARITY_LOW, DC_DISP_V_PULSE1_CONTROL);
> +
> + tegra_dc_writel(dc, PULSE_END(1), DC_DISP_V_PULSE1_POSITION_A);
> + tegra_dc_writel(dc, 0, DC_DISP_V_PULSE1_POSITION_B);
> + tegra_dc_writel(dc, 0, DC_DISP_V_PULSE1_POSITION_C);
> +
> + value = 1 << FRAME_INIT_SEQ_CYCLES_SHIFT |
> + DC_SIGNAL_VPULSE1 << INIT_SEQ_DC_SIGNAL_SHIFT |
> + INIT_SEQUENCE_MODE_PLCD | SEND_INIT_SEQUENCE;
> + tegra_dc_writel(dc, value, DC_DISP_INIT_SEQ_CONTROL);
> +
> + /* Generic sequence for DBI command panels */
> + tegra_dc_writel(dc, 0x2c, DC_DISP_SPI_INIT_SEQ_DATA_A);
MIPI_DCS_WRITE_MEMORY_START
> + tegra_dc_writel(dc, 0x0, DC_DISP_SPI_INIT_SEQ_DATA_B);
> + tegra_dc_writel(dc, 0x0, DC_DISP_SPI_INIT_SEQ_DATA_C);
> + tegra_dc_writel(dc, 0x5000, DC_DISP_SPI_INIT_SEQ_DATA_D);
(1 << 12) | (1 << 14), and comment above
/* Init cycle 1: WRITE_MEMORY_START, set LCD_PCLK and COMMAND */
or something in that vein.
> +
> + value = SC1_H_QUALIFIER(SC_H_QUALIFIER_NONE) |
> + SC0_V_QUALIFIER(SC_V_QUALIFIER_VACTIVE) |
> + SC0_H_QUALIFIER(SC_H_QUALIFIER_HACTIVE);
> + tegra_dc_writel(dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
> +
> + value = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL);
> + value &= ~BASE_COLOR_SIZE_MASK;
> +
> + switch (priv->bus_width) {
> + case 16:
> + value |= BASE_COLOR_SIZE_565;
> + break;
> + case 18:
> + value |= BASE_COLOR_SIZE_666;
> + break;
> + case 24:
> + default:
> + value |= BASE_COLOR_SIZE_888;
> + break;
> + }
> +
> + tegra_dc_writel(dc, value, DC_DISP_DISP_COLOR_CONTROL);
> +
> + /*
> + * Backup registers before unshadowing since actual register values
> + * may differ and their state backed up now is the correct one.
> + */
> + for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
> + com_pin_output[i].value = tegra_dc_readl(dc, com_pin_output[i].offset);
> +
> + /* Unshadow to set up command and data pins */
> + tegra_dc_writel(dc, WRITE_MUX, DC_CMD_STATE_ACCESS);
> +
> + if (priv->idx)
> + tegra_dbi_init_secondary_interface(priv);
> + else
> + tegra_dbi_init_primary_interface(priv);
> +
> + priv->active = true;
> +
> + if (priv->output.panel)
> + drm_panel_enable(priv->output.panel);
> +
> + tegra_dbi_pin_set(dc, &priv->index->cs, 0);
> +
> + priv->active = false;
> +
> + for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
> + tegra_dc_writel(dc, com_pin_output[i].value, com_pin_output[i].offset);
> +
> + /* Set LDC pin to V Pulse 1 */
> + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(6));
> + value |= LDC_OUTPUT_SELECT_V_PULSE1;
> + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(6));
> +
> + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
> + value &= ~DISP_CTRL_MODE_MASK;
> + value |= DISP_CTRL_MODE_C_DISPLAY;
> + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
> +
> + tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
> +}
> +
> +static void tegra_dbi_atomic_disable(struct drm_bridge *bridge,
> + struct drm_atomic_commit *state)
> +{
> + struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
> + struct tegra_dc *dc = priv->dc;
> +
> + for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
> + com_pin_output[i].value = tegra_dc_readl(dc, com_pin_output[i].offset);
> +
> + tegra_dc_writel(dc, WRITE_MUX, DC_CMD_STATE_ACCESS);
> +
> + if (priv->idx)
> + tegra_dbi_init_secondary_interface(priv);
> + else
> + tegra_dbi_init_primary_interface(priv);
> +
> + priv->active = true;
> +
> + if (priv->output.panel)
> + drm_panel_disable(priv->output.panel);
> +
> + priv->active = false;
> +
> + for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
> + tegra_dc_writel(dc, com_pin_output[i].value, com_pin_output[i].offset);
> +
> + tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
> +}
> +
> +static int tegra_dbi_atomic_check(struct drm_bridge *bridge,
> + struct drm_bridge_state *bridge_state,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state)
> +{
> + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
> +
> + /* Set safe default bus flags if panel does not specify them */
> + if (!bridge_state->input_bus_cfg.flags)
> + bridge_state->input_bus_cfg.flags = bridge->timings->input_bus_flags;
> +
> + /* Default to positive sync */
> + if (!(adjusted_mode->flags &
> + (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC)))
> + adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC;
> +
> + if (!(adjusted_mode->flags &
> + (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC)))
> + adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC;
> +
> + return 0;
> +}
> +
> +static int tegra_dbi_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
> + struct device *dev = priv->dev;
> +
> + priv->dc = dev_get_drvdata(dev->parent);
> + if (!priv->dc)
> + return -ENODEV;
> +
> + return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge,
> + flags);
> +}
> +
> +static enum drm_mode_status tegra_dbi_mode_valid(struct drm_bridge *bridge,
> + const struct drm_display_info *info,
> + const struct drm_display_mode *mode)
> +{
> + if (mode->hdisplay > 800)
> + return MODE_H_ILLEGAL;
> +
> + if (mode->vdisplay > 800)
> + return MODE_V_ILLEGAL;
> +
> + return MODE_OK;
> +}
> +
> +static const struct drm_bridge_funcs tegra_dbi_funcs = {
> + .attach = tegra_dbi_attach,
> + .mode_valid = tegra_dbi_mode_valid,
> +
> + .atomic_enable = tegra_dbi_atomic_enable,
> + .atomic_disable = tegra_dbi_atomic_disable,
> + .atomic_check = tegra_dbi_atomic_check,
> +
> + .atomic_create_state = drm_atomic_helper_bridge_create_state,
> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> +};
> +
> +static const struct drm_bridge_timings default_tegra_dbi_timings = {
> + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
> + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE |
> + DRM_BUS_FLAG_DE_HIGH,
> +};
> +
> +static int tegra_dbi_probe(struct platform_device *pdev)
> +{
> + struct tegra_dbi_priv *priv;
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + struct drm_bridge *bridge;
> + struct drm_panel *panel;
> + struct device_node *ep;
> + int ret;
> +
> + priv = devm_drm_bridge_alloc(dev, struct tegra_dbi_priv, bridge,
> + &tegra_dbi_funcs);
> + if (IS_ERR(priv))
> + return PTR_ERR(priv);
> +
> + priv->dev = dev;
> +
> + /* Default to primaty */
primary
> + device_property_read_u32(dev, "nvidia,index", &priv->idx);
Could we use the standard 'reg' property instead of this?
> + if (priv->idx)
> + priv->index = &tegra_dbi_idx[1];
> + else
> + priv->index = &tegra_dbi_idx[0];
> +
> + /* Initialize MIPI DBI interface */
> + mutex_init(&priv->dbi.cmdlock);
> + priv->dbi.command = tegra_dbi_dbi_command;
> +
> + ret = devm_of_platform_populate(dev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to probe children\n");
> +
> + ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, &bridge);
> + if (ret)
> + return ret;
> +
> + if (panel) {
> + bridge = devm_drm_panel_bridge_add_typed(dev, panel,
> + DRM_MODE_CONNECTOR_DPI);
> + if (IS_ERR(bridge))
> + return PTR_ERR(bridge);
> + }
> +
> + priv->output.bridge = bridge;
> + priv->output.panel = panel;
> +
> + /* get input ep (port0/endpoint0) */
> + ret = -EINVAL;
> + ep = of_graph_get_endpoint_by_regs(np, 0, 0);
> + if (ep) {
> + ret = of_property_read_u32(ep, "bus-width", &priv->bus_width);
> + of_node_put(ep);
> + }
> +
> + if (ret)
> + priv->bus_width = 24;
> +
> + priv->bridge.of_node = np;
> + priv->bridge.timings = &default_tegra_dbi_timings;
> +
> + ret = devm_drm_bridge_add(dev, &priv->bridge);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to add bridge\n");
> +
> + platform_set_drvdata(pdev, priv);
> +
> + return 0;
> +}
> +
> +static struct platform_driver tegra_dbi_driver = {
> + .driver.name = "tegra-dbi",
> + .probe = tegra_dbi_probe,
> +};
> +module_platform_driver(tegra_dbi_driver);
> +
> +MODULE_ALIAS("platform:tegra-dbi");
> +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
> +MODULE_DESCRIPTION("Nvidia Tegra20/30 MIPI-DBI Type B driver");
> +MODULE_LICENSE("GPL");
No separate platform driver or device. If a separate struct device is
needed for some technical purpose, we need to create a minimal separate
virtual device, but unless there is a strong reason to have that we
should be fine with just the dc device.
Thank you
Mikko
> diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
> index 5c67928bcabfa..8f12d30cd2d8b 100644
> --- a/drivers/gpu/drm/tegra/dc.c
> +++ b/drivers/gpu/drm/tegra/dc.c
> @@ -223,6 +223,7 @@ void tegra_dc_commit(struct tegra_dc *dc)
> tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
> tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
> }
> +EXPORT_SYMBOL_GPL(tegra_dc_commit);
>
> static inline u32 compute_dda_inc(unsigned int in, unsigned int out, bool v,
> unsigned int bpp)
> @@ -3279,9 +3280,15 @@ static int tegra_dc_init_opp_table(struct tegra_dc *dc)
> return 0;
> }
>
> +static void tegra_dc_release_dbi_pdev(void *pdev)
> +{
> + platform_device_unregister(pdev);
> +}
> +
> static int tegra_dc_probe(struct platform_device *pdev)
> {
> u64 dma_mask = dma_get_mask(pdev->dev.parent);
> + struct device_node *dbi_np;
> struct tegra_dc *dc;
> int err;
>
> @@ -3382,6 +3389,28 @@ static int tegra_dc_probe(struct platform_device *pdev)
> }
> }
>
> + dbi_np = of_get_available_child_by_name(pdev->dev.of_node, "dbi");
> + if (dbi_np) {
> + struct platform_device_info dbi_info = {
> + .parent = &pdev->dev,
> + .name = "tegra-dbi",
> + .id = PLATFORM_DEVID_AUTO,
> + .fwnode = of_fwnode_handle(dbi_np),
> + .of_node_reused = true,
> + };
> +
> + dc->dbi = platform_device_register_full(&dbi_info);
> + of_node_put(dbi_np);
> + if (IS_ERR(dc->dbi))
> + return dev_err_probe(&pdev->dev, PTR_ERR(dc->dbi),
> + "Failed to create DBI device\n");
> +
> + err = devm_add_action_or_reset(&pdev->dev, tegra_dc_release_dbi_pdev,
> + dc->dbi);
> + if (err)
> + return err;
> + }
> +
> err = tegra_dc_rgb_probe(dc);
> if (err < 0 && err != -ENODEV)
> return dev_err_probe(&pdev->dev, err,
> diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
> index 7005df32f485f..27dbee57f8f72 100644
> --- a/drivers/gpu/drm/tegra/dc.h
> +++ b/drivers/gpu/drm/tegra/dc.h
> @@ -95,6 +95,7 @@ struct tegra_dc {
>
> struct tegra_output *rgb;
> struct tegra_pmc *pmc;
> + struct platform_device *dbi;
>
> struct tegra_dc_stats stats;
> struct list_head list;
> diff --git a/drivers/gpu/drm/tegra/trace.c b/drivers/gpu/drm/tegra/trace.c
> index 006f65c72a344..3aeb3825e2100 100644
> --- a/drivers/gpu/drm/tegra/trace.c
> +++ b/drivers/gpu/drm/tegra/trace.c
> @@ -1,2 +1,5 @@
> #define CREATE_TRACE_POINTS
> #include "trace.h"
> +
> +EXPORT_TRACEPOINT_SYMBOL_GPL(dc_readl);
> +EXPORT_TRACEPOINT_SYMBOL_GPL(dc_writel);
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 4/7] drm/tegra: dc: Expand available registers layouts
2026-10-23 7:14 ` Mikko Perttunen
@ 2026-10-23 8:06 ` Svyatoslav Ryhel
0 siblings, 0 replies; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-23 8:06 UTC (permalink / raw)
To: Mikko Perttunen
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, devicetree, dri-devel, linux-kernel,
linux-tegra
пт, 23 жовт. 2026 р. о 10:14 Mikko Perttunen <mperttunen@nvidia.com> пише:
>
> On Thursday, October 22, 2026 10:22 PM Svyatoslav Ryhel wrote:
> > Expand existing DC register definitions with additional fields in
> > preparation for adding the 8-bit CPU interface.
> >
> > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > ---
> > drivers/gpu/drm/tegra/dc.c | 3 ++-
> > drivers/gpu/drm/tegra/dc.h | 49 +++++++++++++++++++++++++++++++++-----
> > 2 files changed, 45 insertions(+), 7 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
> > index b0bfa946e6979..5c67928bcabfa 100644
> > --- a/drivers/gpu/drm/tegra/dc.c
> > +++ b/drivers/gpu/drm/tegra/dc.c
> > @@ -2384,7 +2384,8 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
> >
> > if (dc->rgb) {
> > /* XXX: parameterize? */
> > - value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE;
> > + value = SC0_H_QUALIFIER(SC_H_QUALIFIER_NONE) |
> > + SC1_H_QUALIFIER(SC_H_QUALIFIER_NONE);
> > tegra_dc_writel(dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
> > }
> >
> > diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
> > index 0cb0515968b35..7005df32f485f 100644
> > --- a/drivers/gpu/drm/tegra/dc.h
> > +++ b/drivers/gpu/drm/tegra/dc.h
> > @@ -274,8 +274,11 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
> > #define DC_COM_CRC_CHECKSUM 0x301
> > #define DC_COM_PIN_OUTPUT_ENABLE(x) (0x302 + (x))
> > #define DC_COM_PIN_OUTPUT_POLARITY(x) (0x306 + (x))
> > +#define LSC0_OUTPUT_POLARITY_LOW BIT(24)
> > #define LVS_OUTPUT_POLARITY_LOW (1 << 28)
> > #define LHS_OUTPUT_POLARITY_LOW (1 << 30)
> > +#define LSPI_OUTPUT_POLARITY_LOW BIT(8)
>
> Please update the existing defines to use BIT(n) as well, and align the
> define values (incl. the lines around here that are already there) like
> elsewhere in the file. Also, currently it's hard to tell which register
> each bit define corresponds to, so I'd like some comments like ..
>
> /* DC_COM_PIN_OUTPUT_POLARITY(0): */
> #define ...
> #define ...
>
> > +#define LDC_OUTPUT_SELECT_V_PULSE1 BIT(14)
>
> Please move this below the DC_COM_PIN_OUTPUT_SELECT(x) define.
>
Sure, I will refactor this header in a more readable manner.
> > #define DC_COM_PIN_OUTPUT_DATA(x) (0x30a + (x))
> > #define DC_COM_PIN_INPUT_ENABLE(x) (0x30e + (x))
> > #define DC_COM_PIN_INPUT_DATA(x) (0x312 + (x))
> > @@ -303,9 +306,15 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
> > #define UNDERFLOW_REPORT_ENABLE (1 << 0)
> >
> > #define DC_DISP_DISP_SIGNAL_OPTIONS0 0x400
> > -#define H_PULSE0_ENABLE (1 << 8)
> > -#define H_PULSE1_ENABLE (1 << 10)
> > -#define H_PULSE2_ENABLE (1 << 12)
> > +#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)
> >
> > #define DC_DISP_DISP_SIGNAL_OPTIONS1 0x401
> >
> > @@ -451,12 +460,26 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
> > #define BASE_COLOR_SIZE_888 ( 8 << 0)
> > #define BASE_COLOR_SIZE_101010 ( 10 << 0)
> > #define BASE_COLOR_SIZE_121212 ( 12 << 0)
> > +#define DISP_COLOR_SWAP_BGR BIT(16)
> > #define CMU_ENABLE_ENABLE (1 << 20)
> >
> > #define DC_DISP_SHIFT_CLOCK_OPTIONS 0x431
> > -#define SC1_H_QUALIFIER_NONE (1 << 16)
> > -#define SC0_H_QUALIFIER_NONE (1 << 0)
> > -
> > +#define SC0_H_QUALIFIER(x) (((x) & 0x7) << 0)
> > +#define SC1_H_QUALIFIER(x) (((x) & 0x7) << 16)
> > +#define SC_H_QUALIFIER_DISABLE 0
> > +#define SC_H_QUALIFIER_NONE 1
> > +#define SC_H_QUALIFIER_HACTIVE 2
> > +#define SC_H_QUALIFIER_EXT_HACTIVE 3
> > +#define SC_H_QUALIFIER_HPULSE 4
> > +#define SC_H_QUALIFIER_EXT_HPULSE 5
> > +#define SC0_V_QUALIFIER(x) (((x) & 0x7) << 3)
> > +#define SC1_V_QUALIFIER(x) (((x) & 0x7) << 19)
> > +#define SC_V_QUALIFIER_NONE 0
> > +#define SC_V_QUALIFIER_RSVD 1
> > +#define SC_V_QUALIFIER_VACTIVE 2
> > +#define SC_V_QUALIFIER_EXT_VACTIVE 3
> > +#define SC_V_QUALIFIER_VPULSE 4
> > +#define SC_V_QUALIFIER_EXT_VPULSE 5
> > #define DC_DISP_DATA_ENABLE_OPTIONS 0x432
> > #define DE_SELECT_ACTIVE_BLANK (0 << 0)
> > #define DE_SELECT_ACTIVE (1 << 0)
> > @@ -493,6 +516,20 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
> > #define DC_DISP_CURSOR_POSITION_NS 0x441
> >
> > #define 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)
> > +#define NO_DC_SIGNAL 0
> > +#define DC_SIGNAL_VSYNC 1
> > +#define DC_SIGNAL_VPULSE0 2
> > +#define DC_SIGNAL_VPULSE1 3
> > +#define DC_SIGNAL_VPULSE2 4
> > +#define DC_SIGNAL_VPULSE3 5
> > +#define INIT_SEQ_DC_CONTROL_SHIFT 7
> > +#define FRAME_INIT_SEQ_CYCLES_SHIFT 8
> > +
> > #define DC_DISP_SPI_INIT_SEQ_DATA_A 0x443
> > #define DC_DISP_SPI_INIT_SEQ_DATA_B 0x444
> > #define DC_DISP_SPI_INIT_SEQ_DATA_C 0x445
> > --
> > 2.53.0
> >
> >
>
>
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 7/7] drm/tegra: Add support for 8-bit CPU interface
2026-10-23 7:53 ` Mikko Perttunen
@ 2026-10-23 8:26 ` Svyatoslav Ryhel
0 siblings, 0 replies; 12+ messages in thread
From: Svyatoslav Ryhel @ 2026-10-23 8:26 UTC (permalink / raw)
To: Mikko Perttunen
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, devicetree, dri-devel, linux-kernel,
linux-tegra
пт, 23 жовт. 2026 р. о 10:53 Mikko Perttunen <mperttunen@nvidia.com> пише:
>
> On Thursday, October 22, 2026 10:22 PM Svyatoslav Ryhel wrote:
> > The display controller in Tegra20/30 SoCs features an 8-bit SPI interface
> > that closely resembles the MIPI DBI Type B protocol and is referred to as
> > '8-bit CPU'. Each display controller provides two such interfaces, which
> > can be used to send MIPI DCS commands to initialize and control the panel
> > while image data is transmitted via 16/18/24-line RGB.
> >
> > The '8-bit CPU', although being the DC interface, is modeled using a
> > bridge framework for flexibility and convenience. This implementation does
> > not change the existing codebase besides registering an interface and can
> > be enabled/disabled without losing other Tegra DRM functionality.
> >
> > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > ---
> > drivers/gpu/drm/tegra/Kconfig | 7 +
> > drivers/gpu/drm/tegra/Makefile | 3 +
> > drivers/gpu/drm/tegra/dbi.c | 536 +++++++++++++++++++++++++++++++++
> > drivers/gpu/drm/tegra/dc.c | 29 ++
> > drivers/gpu/drm/tegra/dc.h | 1 +
> > drivers/gpu/drm/tegra/trace.c | 3 +
> > 6 files changed, 579 insertions(+)
> > create mode 100644 drivers/gpu/drm/tegra/dbi.c
> >
> > diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig
> > index 8a3b16aac5d68..ce3a424398713 100644
> > --- a/drivers/gpu/drm/tegra/Kconfig
> > +++ b/drivers/gpu/drm/tegra/Kconfig
> > @@ -30,6 +30,13 @@ config DRM_TEGRA
> >
> > if DRM_TEGRA
> >
> > +config DRM_TEGRA_DBI
> > + tristate "Enable MIPI DBI type B communication protocol for Tegra 20/30"
> > + help
> > + Tegra20 and Tegra30 feature 8 bit CPU driver panel control
> > + protocol (similar to MIPI DBI Type B). This option allows use
> > + it to set up and control compatible panel.
> > +
> > config DRM_TEGRA_DEBUG
> > bool "NVIDIA Tegra DRM debug support"
> > help
> > diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile
> > index e399b40d64a1d..d67e53248a082 100644
> > --- a/drivers/gpu/drm/tegra/Makefile
> > +++ b/drivers/gpu/drm/tegra/Makefile
> > @@ -33,3 +33,6 @@ tegra-drm-y += trace.o
> > tegra-drm-$(CONFIG_DRM_FBDEV_EMULATION) += fbdev.o
> >
> > obj-$(CONFIG_DRM_TEGRA) += tegra-drm.o
> > +
> > +obj-$(CONFIG_DRM_TEGRA_DBI) += tegra-dbi.o
> > +tegra-dbi-y := dbi.o
> > diff --git a/drivers/gpu/drm/tegra/dbi.c b/drivers/gpu/drm/tegra/dbi.c
> > new file mode 100644
> > index 0000000000000..7bd90d32053f2
> > --- /dev/null
> > +++ b/drivers/gpu/drm/tegra/dbi.c
> > @@ -0,0 +1,536 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/err.h>
> > +#include <linux/kernel.h>
> > +#include <linux/media-bus-format.h>
> > +#include <linux/module.h>
> > +#include <linux/of_platform.h>
> > +#include <linux/property.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_bridge.h>
> > +#include <drm/drm_drv.h>
> > +#include <drm/drm_mipi_dbi.h>
> > +#include <drm/drm_of.h>
> > +#include <drm/drm_panel.h>
> > +
> > +#include <video/mipi_display.h>
> > +
> > +#include "dc.h"
> > +
> > +#define TEGRA_DBI_COMMAND 0
> > +#define TEGRA_DBI_DATA 1
>
> I'd use an enum for COMMAND/DATA -- as is it's not quite obvious these
> are alternative values. It would also give a proper typing for the
> parameter to dbi_write.
>
Noted
> > +#define TEGRA_DBI_DATA_PINS_MAX 8
> > +#define TEGRA_DBI_INTERFACE_MAX 2
> > +
> > +struct tegra_dbi_output {
> > + struct drm_panel *panel;
> > + struct drm_bridge *bridge;
> > +};
> > +
> > +struct tegra_dbi_priv {
> > + struct mipi_dbi dbi; /* must be first */
> > + struct device *dev;
> > + struct tegra_dc *dc;
> > +
> > + struct drm_bridge bridge;
> > + struct tegra_dbi_output output;
> > +
> > + const struct tegra_dbi_interface *index;
> > +
> > + u32 bus_width;
> > + u32 idx;
> > + bool active;
> > +};
> > +
> > +struct reg_entry {
> > + u32 offset;
> > + u32 value;
> > +};
> > +
> > +/*
> > + * This is used to backup and restore COM pin state during
> > + * tegra_dbi_atomic_enable/disable.
> > + */
> > +static struct reg_entry com_pin_output[] = {
> > + { DC_COM_PIN_OUTPUT_ENABLE(0), 0 },
> > + { DC_COM_PIN_OUTPUT_ENABLE(1), 0 },
> > + { DC_COM_PIN_OUTPUT_ENABLE(2), 0 },
> > + { DC_COM_PIN_OUTPUT_ENABLE(3), 0 },
> > + { DC_COM_PIN_OUTPUT_POLARITY(0), 0 },
> > + { DC_COM_PIN_OUTPUT_POLARITY(1), 0 },
> > + { DC_COM_PIN_OUTPUT_POLARITY(2), 0 },
> > + { DC_COM_PIN_OUTPUT_POLARITY(3), 0 },
> > + { DC_COM_PIN_OUTPUT_DATA(0), 0 },
> > + { DC_COM_PIN_OUTPUT_DATA(1), 0 },
> > + { DC_COM_PIN_OUTPUT_DATA(2), 0 },
> > + { DC_COM_PIN_OUTPUT_DATA(3), 0 },
> > + { DC_COM_PIN_OUTPUT_SELECT(0), 0 },
> > + { DC_COM_PIN_OUTPUT_SELECT(1), 0 },
> > + { DC_COM_PIN_OUTPUT_SELECT(2), 0 },
> > + { DC_COM_PIN_OUTPUT_SELECT(3), 0 },
> > + { DC_COM_PIN_OUTPUT_SELECT(4), 0 },
> > + { DC_COM_PIN_OUTPUT_SELECT(5), 0 },
> > + { DC_COM_PIN_OUTPUT_SELECT(6), 0 },
> > +};
> > +
> > +struct pin {
> > + u32 reg;
> > + u32 pos;
> > +};
> > +
> > +struct tegra_dbi_interface {
> > + struct pin dc;
> > + struct pin rw;
> > + struct pin cs;
> > + struct pin data[TEGRA_DBI_DATA_PINS_MAX];
> > +};
> > +
> > +static const struct tegra_dbi_interface tegra_dbi_idx[TEGRA_DBI_INTERFACE_MAX] = {
> > + /*
> > + * Nvidia Primary 8-bit CPU interface configuration:
> > + * DC = LCD_DC0/LDC; RW = LCD_PCLK/LSC0; CS = LCD_CS0_N/LCSN
> > + * DATA = LD0 - LD7
> > + */
> > + {
> > + .dc = { DC_COM_PIN_OUTPUT_DATA(3), 6 },
> > + .rw = { DC_COM_PIN_OUTPUT_DATA(1), 24 },
> > + .cs = { DC_COM_PIN_OUTPUT_DATA(3), 4 },
> > + .data = {
> > + { DC_COM_PIN_OUTPUT_DATA(0), 0 },
> > + { DC_COM_PIN_OUTPUT_DATA(0), 2 },
> > + { DC_COM_PIN_OUTPUT_DATA(0), 4 },
> > + { DC_COM_PIN_OUTPUT_DATA(0), 6 },
> > + { DC_COM_PIN_OUTPUT_DATA(0), 8 },
> > + { DC_COM_PIN_OUTPUT_DATA(0), 10 },
> > + { DC_COM_PIN_OUTPUT_DATA(0), 12 },
> > + { DC_COM_PIN_OUTPUT_DATA(0), 14 },
>
> I'd like to see defines added for the bit offsets (in dc.h).
> Alternatively perhaps we could have an enum tegra_dc_pin with
> TEGRA_DC_PIN_... variants in the same order as the pins are in the
> various registers (I assume they are in the same order in all
> registers). Then we could refer to those enumeration values here
> and compute the register and bit from it dynamically.
>
That should be possible. I will include those in header rafactoring.
Thierry did not like my enums, so those will be definitions.
> > + },
> > + },
> > + /*
> > + * Nvidia Secondary 8-bit CPU interface configuration:
> > + * DC = LCD_DC1/LVP0; RW = LCD_WR_N/LSC1; CS = LCD_CS1_N/LM0
> > + * DATA0 = LCD_D18/LHP1 DATA4 = LCD_PWR0/LPW0
> > + * DATA1 = LCD_M1/LM1 DATA5 = LCD_PWR2/LPW2
> > + * DATA2 = LCD_D22/LDI DATA6 = LCD_D20/LVP1
> > + * DATA3 = LCD_D23/LPP DATA7 = LCD_D21/LHP0
> > + */
> > + {
> > + .dc = { DC_COM_PIN_OUTPUT_DATA(2), 8 },
> > + .rw = { DC_COM_PIN_OUTPUT_DATA(1), 26 },
> > + .cs = { DC_COM_PIN_OUTPUT_DATA(3), 16 },
> > + .data = {
> > + { DC_COM_PIN_OUTPUT_DATA(2), 2 },
> > + { DC_COM_PIN_OUTPUT_DATA(2), 18 },
> > + { DC_COM_PIN_OUTPUT_DATA(2), 20 },
> > + { DC_COM_PIN_OUTPUT_DATA(2), 22 },
> > + { DC_COM_PIN_OUTPUT_DATA(1), 16 },
> > + { DC_COM_PIN_OUTPUT_DATA(1), 20 },
> > + { DC_COM_PIN_OUTPUT_DATA(2), 10 },
> > + { DC_COM_PIN_OUTPUT_DATA(2), 0 },
> > + },
> > + },
> > +};
> > +
> > +static inline struct tegra_dbi_priv *mipi_dbi_to_tegra_dbi(struct mipi_dbi *dbi)
> > +{
> > + return container_of(dbi, struct tegra_dbi_priv, dbi);
> > +}
> > +
> > +static inline struct tegra_dbi_priv *bridge_to_tegra_dbi(struct drm_bridge *bridge)
> > +{
> > + return container_of(bridge, struct tegra_dbi_priv, bridge);
> > +}
> > +
> > +static void tegra_dbi_pin_set(struct tegra_dc *dc, const struct pin *pin, int value)
>
> I would change this to 'bool value', and and have '!!(BIT(i) & value)'
> for more descriptive types.
>
I had similar idea but ended up making it look like gpio helper. Maybe
FIELD_GET macro would work here instead of !!
> > +{
> > + u32 val;
> > +
> > + val = tegra_dc_readl(dc, pin->reg);
>
> You don't need to read the register value -- the purpose of the unmask
> bit is that only the data bits that are unmasked in the write are
> modified.
>
I was cautious not to accidentally set wrong pins. I will test if
plain writing works and remove read if it is not needed.
> > + val |= BIT(pin->pos + 1); /* unmask is 1 bit above position */
> > + if (value)
> > + val |= BIT(pin->pos);
> > + else
> > + val &= ~BIT(pin->pos);
> > + tegra_dc_writel(dc, val, pin->reg);
> > +}
> > +
> > +static void tegra_dbi_write(struct tegra_dbi_priv *priv, u8 type, u8 value)
> > +{
> > + struct tegra_dc *dc = priv->dc;
> > + const struct tegra_dbi_interface *idx = priv->index;
> > +
> > + tegra_dbi_pin_set(dc, &idx->dc, type);
> > +
> > + tegra_dbi_pin_set(dc, &idx->cs, 0);
> > + tegra_dbi_pin_set(dc, &idx->rw, 0);
> > +
> > + for (int i = 0; i < TEGRA_DBI_DATA_PINS_MAX; i++)
> > + tegra_dbi_pin_set(dc, &idx->data[i], BIT(i) & value);
> > +
> > + tegra_dbi_pin_set(dc, &idx->cs, 1);
> > + tegra_dbi_pin_set(dc, &idx->rw, 1);
> > +
> > + udelay(10);
> > +}
> > +
> > +static int tegra_dbi_dbi_command(struct mipi_dbi *dbi, u8 *cmd, u8 *param, size_t num)
> > +{
> > + struct tegra_dbi_priv *priv = mipi_dbi_to_tegra_dbi(dbi);
> > + u8 command = *cmd;
> > +
> > + /* Blocks all commands when interface is not active */
> > + if (!priv->active)
> > + return -ENODEV;
> > +
> > + tegra_dbi_write(priv, TEGRA_DBI_COMMAND, command);
> > +
> > + for (int i = 0; i < num; i++)
> > + tegra_dbi_write(priv, TEGRA_DBI_DATA, param[i]);
> > +
> > + return 0;
> > +}
> > +
> > +static void tegra_dbi_init_primary_interface(struct tegra_dbi_priv *priv)
> > +{
> > + struct tegra_dc *dc = priv->dc;
> > + u32 value;
> > +
> > + /* Set LCSN and LDC to output enable */
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_ENABLE(3));
> > + value &= ~(BIT(4) | BIT(6));
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_ENABLE(3));
>
> We should use defines, or something like the enum tegra_dc_pin thing to
> calculate the parameter to DC_COM_PIN_OUTPUT/SELECT and the bit offset.
>
Macro to make mask of the pin. BIT already does this, but I can wrap
it into smth. Since I will add pin definitions this will look like
BIT(PEN_LCSN) end so on. If you have preferred naming or ideas, let me
know.
> > +
> > + /* Set LSC0/LCSN/LDC output polarity HIGH */
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY(1));
> > + value &= ~BIT(24);
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_POLARITY(1));
> > +
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY(3));
> > + value &= ~(BIT(4) | BIT(6));
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_POLARITY(3));
> > +
> > + /* Select output for pins LD0 - LD7 */
> > + tegra_dc_writel(dc, 0x11111111, DC_COM_PIN_OUTPUT_SELECT(0));
> > +
> > + /* Select output for pins LSC0/LCSN/LDC */
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(3));
> > + value &= ~0x70000;
>
> Please write this as (0x7 << 16) for clarity, similarly below.
>
I will convert this into macro.
> > + value |= BIT(16);
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(3));
> > +
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(6));
> > + value &= ~0x7700;
> > + value |= BIT(12) | BIT(8);
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(6));
> > +}
> > +
> > +static void tegra_dbi_init_secondary_interface(struct tegra_dbi_priv *priv)
> > +{
> > + struct tegra_dc *dc = priv->dc;
> > + u32 value;
> > +
> > + /* Set LPP/LDI to output enable */
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_ENABLE(2));
> > + value &= ~(BIT(20) | BIT(22));
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_ENABLE(2));
> > +
> > + /* Select output for pins LSC1/LPW2/LPW0 */
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(3));
> > + value &= ~0x700707;
> > + value |= BIT(20) | BIT(8) | BIT(0);
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(3));
> > +
> > + /* Select output for pins LVP1/LVP0/LHP1/LHP0 */
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(4));
> > + value &= ~0x770077;
> > + value |= BIT(20) | BIT(16) | BIT(4) | BIT(0);
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(4));
> > +
> > + /* Select output for pins LPP/LDI/LM1/LM0 */
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(5));
> > + value &= ~0x7777;
> > + value |= BIT(12) | BIT(8) | BIT(4) | BIT(0);
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(5));
> > +}
> > +
> > +static void tegra_dbi_atomic_enable(struct drm_bridge *bridge,
> > + struct drm_atomic_commit *state)
> > +{
> > + struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
> > + struct tegra_dc *dc = priv->dc;
> > + u32 value;
> > +
> > + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
> > + value &= ~DISP_CTRL_MODE_MASK;
> > + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
> > +
> > + /* Disable all options except cursor if enabled */
> > + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
> > + value &= CURSOR_ENABLE;
> > + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
>
> Just 'tegra_dc_writel(dc, CURSOR_ENABLE, DC_DISP_DISP_WIN_OPTIONS);'
>
If CURSOR_ENABLE is not set, it will be enabled unconditionally. Not good.
> > +
> > + tegra_dc_commit(dc);
> > +
> > + tegra_dc_writel(dc, V_PULSE1_ENABLE, DC_DISP_DISP_SIGNAL_OPTIONS0);
> > + tegra_dc_writel(dc, PULSE_POLARITY_LOW, DC_DISP_V_PULSE1_CONTROL);
> > +
> > + tegra_dc_writel(dc, PULSE_END(1), DC_DISP_V_PULSE1_POSITION_A);
> > + tegra_dc_writel(dc, 0, DC_DISP_V_PULSE1_POSITION_B);
> > + tegra_dc_writel(dc, 0, DC_DISP_V_PULSE1_POSITION_C);
> > +
> > + value = 1 << FRAME_INIT_SEQ_CYCLES_SHIFT |
> > + DC_SIGNAL_VPULSE1 << INIT_SEQ_DC_SIGNAL_SHIFT |
> > + INIT_SEQUENCE_MODE_PLCD | SEND_INIT_SEQUENCE;
> > + tegra_dc_writel(dc, value, DC_DISP_INIT_SEQ_CONTROL);
> > +
> > + /* Generic sequence for DBI command panels */
> > + tegra_dc_writel(dc, 0x2c, DC_DISP_SPI_INIT_SEQ_DATA_A);
>
> MIPI_DCS_WRITE_MEMORY_START
>
Noted
> > + tegra_dc_writel(dc, 0x0, DC_DISP_SPI_INIT_SEQ_DATA_B);
> > + tegra_dc_writel(dc, 0x0, DC_DISP_SPI_INIT_SEQ_DATA_C);
> > + tegra_dc_writel(dc, 0x5000, DC_DISP_SPI_INIT_SEQ_DATA_D);
>
> (1 << 12) | (1 << 14), and comment above
>
> /* Init cycle 1: WRITE_MEMORY_START, set LCD_PCLK and COMMAND */
>
> or something in that vein.
>
Noted
> > +
> > + value = SC1_H_QUALIFIER(SC_H_QUALIFIER_NONE) |
> > + SC0_V_QUALIFIER(SC_V_QUALIFIER_VACTIVE) |
> > + SC0_H_QUALIFIER(SC_H_QUALIFIER_HACTIVE);
> > + tegra_dc_writel(dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
> > +
> > + value = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL);
> > + value &= ~BASE_COLOR_SIZE_MASK;
> > +
> > + switch (priv->bus_width) {
> > + case 16:
> > + value |= BASE_COLOR_SIZE_565;
> > + break;
> > + case 18:
> > + value |= BASE_COLOR_SIZE_666;
> > + break;
> > + case 24:
> > + default:
> > + value |= BASE_COLOR_SIZE_888;
> > + break;
> > + }
> > +
> > + tegra_dc_writel(dc, value, DC_DISP_DISP_COLOR_CONTROL);
> > +
> > + /*
> > + * Backup registers before unshadowing since actual register values
> > + * may differ and their state backed up now is the correct one.
> > + */
> > + for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
> > + com_pin_output[i].value = tegra_dc_readl(dc, com_pin_output[i].offset);
> > +
> > + /* Unshadow to set up command and data pins */
> > + tegra_dc_writel(dc, WRITE_MUX, DC_CMD_STATE_ACCESS);
> > +
> > + if (priv->idx)
> > + tegra_dbi_init_secondary_interface(priv);
> > + else
> > + tegra_dbi_init_primary_interface(priv);
> > +
> > + priv->active = true;
> > +
> > + if (priv->output.panel)
> > + drm_panel_enable(priv->output.panel);
> > +
> > + tegra_dbi_pin_set(dc, &priv->index->cs, 0);
> > +
> > + priv->active = false;
> > +
> > + for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
> > + tegra_dc_writel(dc, com_pin_output[i].value, com_pin_output[i].offset);
> > +
> > + /* Set LDC pin to V Pulse 1 */
> > + value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT(6));
> > + value |= LDC_OUTPUT_SELECT_V_PULSE1;
> > + tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_SELECT(6));
> > +
> > + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
> > + value &= ~DISP_CTRL_MODE_MASK;
> > + value |= DISP_CTRL_MODE_C_DISPLAY;
> > + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
> > +
> > + tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
> > +}
> > +
> > +static void tegra_dbi_atomic_disable(struct drm_bridge *bridge,
> > + struct drm_atomic_commit *state)
> > +{
> > + struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
> > + struct tegra_dc *dc = priv->dc;
> > +
> > + for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
> > + com_pin_output[i].value = tegra_dc_readl(dc, com_pin_output[i].offset);
> > +
> > + tegra_dc_writel(dc, WRITE_MUX, DC_CMD_STATE_ACCESS);
> > +
> > + if (priv->idx)
> > + tegra_dbi_init_secondary_interface(priv);
> > + else
> > + tegra_dbi_init_primary_interface(priv);
> > +
> > + priv->active = true;
> > +
> > + if (priv->output.panel)
> > + drm_panel_disable(priv->output.panel);
> > +
> > + priv->active = false;
> > +
> > + for (int i = 0; i < ARRAY_SIZE(com_pin_output); i++)
> > + tegra_dc_writel(dc, com_pin_output[i].value, com_pin_output[i].offset);
> > +
> > + tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
> > +}
> > +
> > +static int tegra_dbi_atomic_check(struct drm_bridge *bridge,
> > + struct drm_bridge_state *bridge_state,
> > + struct drm_crtc_state *crtc_state,
> > + struct drm_connector_state *conn_state)
> > +{
> > + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
> > +
> > + /* Set safe default bus flags if panel does not specify them */
> > + if (!bridge_state->input_bus_cfg.flags)
> > + bridge_state->input_bus_cfg.flags = bridge->timings->input_bus_flags;
> > +
> > + /* Default to positive sync */
> > + if (!(adjusted_mode->flags &
> > + (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC)))
> > + adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC;
> > +
> > + if (!(adjusted_mode->flags &
> > + (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC)))
> > + adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC;
> > +
> > + return 0;
> > +}
> > +
> > +static int tegra_dbi_attach(struct drm_bridge *bridge,
> > + struct drm_encoder *encoder,
> > + enum drm_bridge_attach_flags flags)
> > +{
> > + struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
> > + struct device *dev = priv->dev;
> > +
> > + priv->dc = dev_get_drvdata(dev->parent);
> > + if (!priv->dc)
> > + return -ENODEV;
> > +
> > + return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge,
> > + flags);
> > +}
> > +
> > +static enum drm_mode_status tegra_dbi_mode_valid(struct drm_bridge *bridge,
> > + const struct drm_display_info *info,
> > + const struct drm_display_mode *mode)
> > +{
> > + if (mode->hdisplay > 800)
> > + return MODE_H_ILLEGAL;
> > +
> > + if (mode->vdisplay > 800)
> > + return MODE_V_ILLEGAL;
> > +
> > + return MODE_OK;
> > +}
> > +
> > +static const struct drm_bridge_funcs tegra_dbi_funcs = {
> > + .attach = tegra_dbi_attach,
> > + .mode_valid = tegra_dbi_mode_valid,
> > +
> > + .atomic_enable = tegra_dbi_atomic_enable,
> > + .atomic_disable = tegra_dbi_atomic_disable,
> > + .atomic_check = tegra_dbi_atomic_check,
> > +
> > + .atomic_create_state = drm_atomic_helper_bridge_create_state,
> > + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> > + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> > +};
> > +
> > +static const struct drm_bridge_timings default_tegra_dbi_timings = {
> > + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
> > + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE |
> > + DRM_BUS_FLAG_DE_HIGH,
> > +};
> > +
> > +static int tegra_dbi_probe(struct platform_device *pdev)
> > +{
> > + struct tegra_dbi_priv *priv;
> > + struct device *dev = &pdev->dev;
> > + struct device_node *np = dev->of_node;
> > + struct drm_bridge *bridge;
> > + struct drm_panel *panel;
> > + struct device_node *ep;
> > + int ret;
> > +
> > + priv = devm_drm_bridge_alloc(dev, struct tegra_dbi_priv, bridge,
> > + &tegra_dbi_funcs);
> > + if (IS_ERR(priv))
> > + return PTR_ERR(priv);
> > +
> > + priv->dev = dev;
> > +
> > + /* Default to primaty */
>
> primary
>
> > + device_property_read_u32(dev, "nvidia,index", &priv->idx);
>
> Could we use the standard 'reg' property instead of this?
>
OMG, YES!
> > + if (priv->idx)
> > + priv->index = &tegra_dbi_idx[1];
> > + else
> > + priv->index = &tegra_dbi_idx[0];
> > +
> > + /* Initialize MIPI DBI interface */
> > + mutex_init(&priv->dbi.cmdlock);
> > + priv->dbi.command = tegra_dbi_dbi_command;
> > +
> > + ret = devm_of_platform_populate(dev);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to probe children\n");
> > +
> > + ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, &bridge);
> > + if (ret)
> > + return ret;
> > +
> > + if (panel) {
> > + bridge = devm_drm_panel_bridge_add_typed(dev, panel,
> > + DRM_MODE_CONNECTOR_DPI);
> > + if (IS_ERR(bridge))
> > + return PTR_ERR(bridge);
> > + }
> > +
> > + priv->output.bridge = bridge;
> > + priv->output.panel = panel;
> > +
> > + /* get input ep (port0/endpoint0) */
> > + ret = -EINVAL;
> > + ep = of_graph_get_endpoint_by_regs(np, 0, 0);
> > + if (ep) {
> > + ret = of_property_read_u32(ep, "bus-width", &priv->bus_width);
> > + of_node_put(ep);
> > + }
> > +
> > + if (ret)
> > + priv->bus_width = 24;
> > +
> > + priv->bridge.of_node = np;
> > + priv->bridge.timings = &default_tegra_dbi_timings;
> > +
> > + ret = devm_drm_bridge_add(dev, &priv->bridge);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to add bridge\n");
> > +
> > + platform_set_drvdata(pdev, priv);
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver tegra_dbi_driver = {
> > + .driver.name = "tegra-dbi",
> > + .probe = tegra_dbi_probe,
> > +};
> > +module_platform_driver(tegra_dbi_driver);
> > +
> > +MODULE_ALIAS("platform:tegra-dbi");
> > +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
> > +MODULE_DESCRIPTION("Nvidia Tegra20/30 MIPI-DBI Type B driver");
> > +MODULE_LICENSE("GPL");
>
> No separate platform driver or device. If a separate struct device is
> needed for some technical purpose, we need to create a minimal separate
> virtual device, but unless there is a strong reason to have that we
> should be fine with just the dc device.
>
> Thank you
> Mikko
>
ATM, I use bridge framework to describe interface and I am attaching
it to RGB since it basically uses RGB to transfer video data. It works
pretty nice so far.
You propose to replicate how RGB works just for DBI?
> > diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
> > index 5c67928bcabfa..8f12d30cd2d8b 100644
> > --- a/drivers/gpu/drm/tegra/dc.c
> > +++ b/drivers/gpu/drm/tegra/dc.c
> > @@ -223,6 +223,7 @@ void tegra_dc_commit(struct tegra_dc *dc)
> > tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
> > tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
> > }
> > +EXPORT_SYMBOL_GPL(tegra_dc_commit);
> >
> > static inline u32 compute_dda_inc(unsigned int in, unsigned int out, bool v,
> > unsigned int bpp)
> > @@ -3279,9 +3280,15 @@ static int tegra_dc_init_opp_table(struct tegra_dc *dc)
> > return 0;
> > }
> >
> > +static void tegra_dc_release_dbi_pdev(void *pdev)
> > +{
> > + platform_device_unregister(pdev);
> > +}
> > +
> > static int tegra_dc_probe(struct platform_device *pdev)
> > {
> > u64 dma_mask = dma_get_mask(pdev->dev.parent);
> > + struct device_node *dbi_np;
> > struct tegra_dc *dc;
> > int err;
> >
> > @@ -3382,6 +3389,28 @@ static int tegra_dc_probe(struct platform_device *pdev)
> > }
> > }
> >
> > + dbi_np = of_get_available_child_by_name(pdev->dev.of_node, "dbi");
> > + if (dbi_np) {
> > + struct platform_device_info dbi_info = {
> > + .parent = &pdev->dev,
> > + .name = "tegra-dbi",
> > + .id = PLATFORM_DEVID_AUTO,
> > + .fwnode = of_fwnode_handle(dbi_np),
> > + .of_node_reused = true,
> > + };
> > +
> > + dc->dbi = platform_device_register_full(&dbi_info);
> > + of_node_put(dbi_np);
> > + if (IS_ERR(dc->dbi))
> > + return dev_err_probe(&pdev->dev, PTR_ERR(dc->dbi),
> > + "Failed to create DBI device\n");
> > +
> > + err = devm_add_action_or_reset(&pdev->dev, tegra_dc_release_dbi_pdev,
> > + dc->dbi);
> > + if (err)
> > + return err;
> > + }
> > +
> > err = tegra_dc_rgb_probe(dc);
> > if (err < 0 && err != -ENODEV)
> > return dev_err_probe(&pdev->dev, err,
> > diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
> > index 7005df32f485f..27dbee57f8f72 100644
> > --- a/drivers/gpu/drm/tegra/dc.h
> > +++ b/drivers/gpu/drm/tegra/dc.h
> > @@ -95,6 +95,7 @@ struct tegra_dc {
> >
> > struct tegra_output *rgb;
> > struct tegra_pmc *pmc;
> > + struct platform_device *dbi;
> >
> > struct tegra_dc_stats stats;
> > struct list_head list;
> > diff --git a/drivers/gpu/drm/tegra/trace.c b/drivers/gpu/drm/tegra/trace.c
> > index 006f65c72a344..3aeb3825e2100 100644
> > --- a/drivers/gpu/drm/tegra/trace.c
> > +++ b/drivers/gpu/drm/tegra/trace.c
> > @@ -1,2 +1,5 @@
> > #define CREATE_TRACE_POINTS
> > #include "trace.h"
> > +
> > +EXPORT_TRACEPOINT_SYMBOL_GPL(dc_readl);
> > +EXPORT_TRACEPOINT_SYMBOL_GPL(dc_writel);
> > --
> > 2.53.0
> >
> >
>
>
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-23 8:26 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-22 13:22 [PATCH v4 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 1/7] dt-bindings: display: panel: Document Hitachi TX10D07VM0BAA and LG LH400WV3 panels Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 2/7] drm/panel: Add Hitachi TX10D07VM0BAA MIPI DBI Type B panel driver Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 3/7] drm/panel: Add LG LH400WV3-SD04 " Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 4/7] drm/tegra: dc: Expand available registers layouts Svyatoslav Ryhel
2026-10-23 7:14 ` Mikko Perttunen
2026-10-23 8:06 ` Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 5/7] drm/tegra: rgb: Parameterize configuration based on bus flags Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 6/7] dt-bindings: display: tegra: Document 8-bit CPU parallel interface Svyatoslav Ryhel
2026-10-22 13:22 ` [PATCH v4 7/7] drm/tegra: Add support for 8-bit CPU interface Svyatoslav Ryhel
2026-10-23 7:53 ` Mikko Perttunen
2026-10-23 8:26 ` Svyatoslav Ryhel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®