mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/2] drm/panel: Add support for Novatek NT36532 panel
@ 2026-07-29 16:08 Junjie Cao
  2026-07-29 16:08 ` [PATCH v2 1/2] dt-bindings: display: panel: Add Novatek NT36532 Junjie Cao
  2026-07-29 16:08 ` [PATCH v2 2/2] drm/panel: Add Novatek NT36532 panel driver Junjie Cao
  0 siblings, 2 replies; 4+ messages in thread
From: Junjie Cao @ 2026-07-29 16:08 UTC (permalink / raw)
  To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Junjie Cao, Dmitry Baryshkov, Konrad Dybcio, Pengyu Luo,
	dri-devel, devicetree, linux-arm-msm, linux-kernel

This series adds support for panels using the Novatek NT36532 Display
Driver IC, a dual-DSI, dual-DSC controller that requires DPU support
for MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT.

The first user is the CSOT PPC100HB1-1 panel found in the OnePlus Pad 2
(OnePlus Pad Pro in China) tablet: 3000x2120, dual-DSI in bonded mode,
DSC 1.2 with two slices per line.

This has been tested on the OnePlus Pad 2: the panel comes up in its
native 3000x2120 mode at 120Hz over dual-DSI in bonded mode with DSC,
and displays a stable image.

Changes in v2:
- Rebase onto next-20260727.
- Update to the new DRM APIs: devm_drm_panel_alloc(), devm_drm_panel_add().
- Drop the driver-private 'dsc_slice_per_pkt' plumbing and rely on
  MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT instead.
- Send DCS commands through the second DSI link in dual-DSI mode.
- Change the refresh rate control register 0xb3 value from 0x41 to 0x40.
- Drop the of_node assignment when registering the secondary DSI device,
  matching panel-novatek-nt36523.
- Update author email to junjie.cao@linux.dev.
- Pick up Conor's Acked-by on the binding.
- Link to v1: https://lore.kernel.org/all/20251001135914.13754-1-caojunjie650@gmail.com

Junjie Cao (2):
  dt-bindings: display: panel: Add Novatek NT36532
  drm/panel: Add Novatek NT36532 panel driver

 .../display/panel/novatek,nt36532.yaml        |  83 ++++
 MAINTAINERS                                   |   7 +
 drivers/gpu/drm/panel/Kconfig                 |  10 +
 drivers/gpu/drm/panel/Makefile                |   1 +
 drivers/gpu/drm/panel/panel-novatek-nt36532.c | 430 ++++++++++++++++++
 5 files changed, 531 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/panel/novatek,nt36532.yaml
 create mode 100644 drivers/gpu/drm/panel/panel-novatek-nt36532.c


base-commit: 0d33d21e47d9dc66f91e44da3fc9220c74d93df7
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v2 1/2] dt-bindings: display: panel: Add Novatek NT36532
  2026-07-29 16:08 [PATCH v2 0/2] drm/panel: Add support for Novatek NT36532 panel Junjie Cao
@ 2026-07-29 16:08 ` Junjie Cao
  2026-07-29 16:08 ` [PATCH v2 2/2] drm/panel: Add Novatek NT36532 panel driver Junjie Cao
  1 sibling, 0 replies; 4+ messages in thread
From: Junjie Cao @ 2026-07-29 16:08 UTC (permalink / raw)
  To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Junjie Cao, Dmitry Baryshkov, Konrad Dybcio, Pengyu Luo,
	dri-devel, devicetree, linux-arm-msm, linux-kernel, Conor Dooley

NT36532 is a driver IC used to drive MIPI-DSI panels. It is found
in OnePlus Pad 2 tablets with CSOT panels.

Signed-off-by: Junjie Cao <junjie.cao@linux.dev>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
 .../display/panel/novatek,nt36532.yaml        | 83 +++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/panel/novatek,nt36532.yaml

diff --git a/Documentation/devicetree/bindings/display/panel/novatek,nt36532.yaml b/Documentation/devicetree/bindings/display/panel/novatek,nt36532.yaml
new file mode 100644
index 000000000000..c1ab793dd718
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/novatek,nt36532.yaml
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/novatek,nt36532.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Novatek NT36532 based DSI display Panels
+
+maintainers:
+  - Junjie Cao <junjie.cao@linux.dev>
+
+description: |
+  The NT36532 IC from Novatek is a Driver IC used to drive MIPI-DSI panels.
+
+allOf:
+  - $ref: panel-common-dual.yaml#
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - csot,ppc100hb1-1
+      - const: novatek,nt36532
+
+  reg:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+    description: phandle of gpio for reset line - This should be 8mA
+
+  vddio-supply:
+    description: regulator that supplies the I/O voltage
+
+  ports: true
+
+required:
+  - compatible
+  - reg
+  - vddio-supply
+  - reset-gpios
+  - ports
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    dsi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        panel@0 {
+            compatible = "csot,ppc100hb1-1", "novatek,nt36532";
+            reg = <0>;
+
+            reset-gpios = <&tlmm 133 GPIO_ACTIVE_LOW>;
+
+            vddio-supply = <&vreg_l12b_1p8>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    panel_in_0: endpoint {
+                        remote-endpoint = <&dsi0_out>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    panel_in_1: endpoint {
+                        remote-endpoint = <&dsi1_out>;
+                    };
+                };
+            };
+        };
+    };
+
+...

base-commit: 0d33d21e47d9dc66f91e44da3fc9220c74d93df7
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v2 2/2] drm/panel: Add Novatek NT36532 panel driver
  2026-07-29 16:08 [PATCH v2 0/2] drm/panel: Add support for Novatek NT36532 panel Junjie Cao
  2026-07-29 16:08 ` [PATCH v2 1/2] dt-bindings: display: panel: Add Novatek NT36532 Junjie Cao
@ 2026-07-29 16:08 ` Junjie Cao
       [not found]   ` <20260729162847.9F4A51F00A3A@smtp.kernel.org>
  1 sibling, 1 reply; 4+ messages in thread
From: Junjie Cao @ 2026-07-29 16:08 UTC (permalink / raw)
  To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Junjie Cao, Dmitry Baryshkov, Konrad Dybcio, Pengyu Luo,
	dri-devel, devicetree, linux-arm-msm, linux-kernel

Add a driver for panels using the Novatek NT36532 Display Driver IC,
including support for the CSOT PPC100HB1-1, found in the OnePlus Pad 2
tablets.

It has been tested on the OnePlus Pad 2: the panel comes up in its native
3000x2120 mode at 120Hz over dual-DSI in bonded mode with DSC, and
displays a stable image.

Signed-off-by: Junjie Cao <junjie.cao@linux.dev>
---
 MAINTAINERS                                   |   7 +
 drivers/gpu/drm/panel/Kconfig                 |  10 +
 drivers/gpu/drm/panel/Makefile                |   1 +
 drivers/gpu/drm/panel/panel-novatek-nt36532.c | 430 ++++++++++++++++++
 4 files changed, 448 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-novatek-nt36532.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 92a2167f1eb8..bcbdd38981be 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8324,6 +8324,13 @@ T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:	Documentation/devicetree/bindings/display/panel/novatek,nt36523.yaml
 F:	drivers/gpu/drm/panel/panel-novatek-nt36523.c
 
+DRM DRIVER FOR NOVATEK NT36532 PANELS
+M:	Junjie Cao <junjie.cao@linux.dev>
+S:	Maintained
+T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F:	Documentation/devicetree/bindings/display/panel/novatek,nt36532.yaml
+F:	drivers/gpu/drm/panel/panel-novatek-nt36532.c
+
 DRM DRIVER FOR NOVATEK NT36672A PANELS
 M:	Sumit Semwal <sumit.semwal@linaro.org>
 S:	Maintained
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index cbdf7b8f7f7a..e88211e6859d 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -664,6 +664,16 @@ config DRM_PANEL_NOVATEK_NT36523
 	  around the Novatek NT36523 display controller, such as some
 	  Boe panels used in Xiaomi Mi Pad 5 and 5 Pro tablets.
 
+config DRM_PANEL_NOVATEK_NT36532
+	tristate "Novatek NT36532-based MIPI-DSI panels"
+	depends on OF
+	depends on DRM_MIPI_DSI
+	help
+	  Say Y here if you want to enable support for the panels built
+	  around the Novatek NT36532 display controller, such as some
+	  CSOT panels used in OnePlus Pad 2 tablets. These panels are
+	  typically dual-DSI and may use DSC (Display Stream Compression).
+
 config DRM_PANEL_NOVATEK_NT36536
 	tristate "Novatek NT36536 panel driver"
 	depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 3b523cf37833..0f920cff7c2a 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35532) += panel-novatek-nt35532.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35560) += panel-novatek-nt35560.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35950) += panel-novatek-nt35950.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36523) += panel-novatek-nt36523.o
+obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36532) += panel-novatek-nt36532.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36536) += panel-novatek-nt36536.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672E) += panel-novatek-nt36672e.o
diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36532.c b/drivers/gpu/drm/panel/panel-novatek-nt36532.c
new file mode 100644
index 000000000000..03f9a1b6aa5a
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-novatek-nt36532.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Novatek NT36532 DriverIC panels driver
+ * Based on the template generated by linux-mdss-dsi-panel-driver-generator
+ *
+ * Copyright (c) 2025, 2026 Junjie Cao <junjie.cao@linux.dev>
+ */
+
+#include <linux/delay.h>
+#include <linux/device-id/of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+struct nt36532 {
+	struct drm_panel panel;
+	struct mipi_dsi_device *dsi[2];
+	const struct panel_info *panel_info;
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data *supplies;
+};
+
+struct panel_info {
+	unsigned int width_mm;
+	unsigned int height_mm;
+
+	unsigned int lanes;
+	enum mipi_dsi_pixel_format format;
+	unsigned long mode_flags;
+
+	const struct drm_display_mode display_mode;
+
+	struct drm_dsc_config * const dsc_cfg;
+
+	int (*init_sequence)(struct nt36532 *ctx);
+
+	bool is_dual_dsi;
+};
+
+static const struct regulator_bulk_data nt36532_supplies[] = {
+	{ .supply = "vddio" }, /* 1.8v */
+};
+
+static inline struct nt36532 *to_nt36532(struct drm_panel *panel)
+{
+	return container_of(panel, struct nt36532, panel);
+}
+
+static inline struct mipi_dsi_device *to_primary_dsi(struct nt36532 *ctx)
+{
+	/* Sync on DSI1 for dual dsi */
+	return ctx->panel_info->is_dual_dsi ? ctx->dsi[1] : ctx->dsi[0];
+}
+
+static void nt36532_reset(struct nt36532 *ctx)
+{
+	gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+	usleep_range(10000, 10100);
+	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+	usleep_range(5000, 5100);
+	gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+	usleep_range(15000, 15100);
+}
+
+static int nt36532_prepare(struct drm_panel *panel)
+{
+	struct nt36532 *ctx = to_nt36532(panel);
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(nt36532_supplies),
+				    ctx->supplies);
+	if (ret < 0)
+		return ret;
+
+	nt36532_reset(ctx);
+
+	ret = ctx->panel_info->init_sequence(ctx);
+	if (ret < 0) {
+		gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+		regulator_bulk_disable(ARRAY_SIZE(nt36532_supplies),
+				       ctx->supplies);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int nt36532_off(struct nt36532 *ctx)
+{
+	struct mipi_dsi_device *dsi = to_primary_dsi(ctx);
+	struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
+
+	mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
+	mipi_dsi_usleep_range(&dsi_ctx, 10000, 10100);
+	mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
+	mipi_dsi_msleep(&dsi_ctx, 65);
+
+	return dsi_ctx.accum_err;
+}
+
+static int nt36532_unprepare(struct drm_panel *panel)
+{
+	struct nt36532 *ctx = to_nt36532(panel);
+	struct device *dev = &ctx->dsi[0]->dev;
+	int ret;
+
+	ret = nt36532_off(ctx);
+	if (ret < 0)
+		dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+	regulator_bulk_disable(ARRAY_SIZE(nt36532_supplies), ctx->supplies);
+
+	return 0;
+}
+
+static int nt36532_get_modes(struct drm_panel *panel,
+			     struct drm_connector *connector)
+{
+	struct nt36532 *ctx = to_nt36532(panel);
+	const struct panel_info *panel_info = ctx->panel_info;
+
+	return drm_connector_helper_get_modes_fixed(connector,
+						    &panel_info->display_mode);
+}
+
+static const struct drm_panel_funcs nt36532_panel_funcs = {
+	.prepare = nt36532_prepare,
+	.unprepare = nt36532_unprepare,
+	.get_modes = nt36532_get_modes,
+};
+
+static int csot_init_sequence(struct nt36532 *ctx)
+{
+	struct mipi_dsi_device *dsi = to_primary_dsi(ctx);
+	struct drm_dsc_picture_parameter_set pps;
+	struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
+
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x22);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_LUT, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x24);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbc,
+				     0x00, 0x00, 0x03, 0x22, 0x00, 0x41);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x23);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x60);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x20);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x5a);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x10, 0x0c);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x03);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0xe1);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0xa9);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x16);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0a, 0x8e);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0b, 0x8e);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0c, 0x8e);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0d, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1a, 0x04);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1b, 0x08);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1c, 0x0c);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1d, 0x10);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1e, 0x14);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0x18);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x1c);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x21, 0x20);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x24);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x28);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x2c);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x30);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x34);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x38);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x3c);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x20);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x20);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, 0xff);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_COLUMNS,
+				     0xfd);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x32, 0xfc);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0xfa);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0xf8);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0xf6);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0xf4);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0xf2);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0xf0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0xee);
+	mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0xec);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0xea);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_3D_CONTROL, 0xe9);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3f, 0xe8);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_VSYNC_TIMING, 0xe7);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0xe6);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_GET_SCANLINE, 0xff);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x46, 0xf9);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x47, 0xf6);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0xf2);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0xf0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4a, 0xec);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4b, 0xe8);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4c, 0xe4);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4d, 0xe0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4e, 0xde);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4f, 0xd9);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0xd6);
+	mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x00d4);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x52, 0xc2);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+				     0xd0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x54, 0xcd);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0xff);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0xf6);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0xf0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0xeb);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0xe8);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0xe5);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS,
+				     0xe3);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0xe0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0xde);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0xda);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0xd7);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0xd4);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0xd2);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0xd0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0xcc);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0xc8);
+
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x27);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, 0xf0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_COLUMNS,
+				     0x2a, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd0, 0x31);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd1, 0x54);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xde, 0x40);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdf, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x2a);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc4, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xf0);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfa, 0x05);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x16);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x23);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xba, 0xaa, 0x2a);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbb, 0xa0, 0x2a);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x27);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x8c);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x10);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01);
+	mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0xff07);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+				     0x2c);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b,
+				     0x03, 0xd2, 0x1a, 0x04, 0x04, 0x00);
+
+	/* Enable DSC */
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x03);
+	drm_dsc_pps_payload_pack(&pps, ctx->panel_info->dsc_cfg);
+	mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9d, 0x01);
+
+	/* Program refresh rate control registers */
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x91);
+	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x40);
+
+	mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
+	mipi_dsi_msleep(&dsi_ctx, 120);
+
+	mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
+	mipi_dsi_usleep_range(&dsi_ctx, 10000, 10100);
+
+	return dsi_ctx.accum_err;
+}
+
+static const struct drm_display_mode csot_display_mode = {
+	/* 120Hz, the typical value */
+	.clock = (1500 + 118 + 20 + 100) * 2 * (2120 + 26 + 2 + 208) * 120 / 1000,
+	.hdisplay = 1500 * 2,
+	.hsync_start = (1500 + 118) * 2,
+	.hsync_end = (1500 + 118 + 20) * 2,
+	.htotal = (1500 + 118 + 20 + 100) * 2,
+	.vdisplay = 2120,
+	.vsync_start = 2120 + 26,
+	.vsync_end = 2120 + 26 + 2,
+	.vtotal = 2120 + 26 + 2 + 208,
+};
+
+static struct drm_dsc_config csot_dsc_cfg = {
+	.dsc_version_major = 1,
+	.dsc_version_minor = 2,
+	.slice_height = 20,
+	.slice_width = 750,
+	.slice_count = 2,
+	.bits_per_component = 8,
+	.bits_per_pixel = 8 << 4,
+	.block_pred_enable = true,
+};
+
+static int nt36532_probe(struct mipi_dsi_device *dsi)
+{
+	struct mipi_dsi_device_info dsi_info = {"nt36532-secondary", 0, NULL};
+	const struct panel_info *panel_info;
+	struct mipi_dsi_host *dsi1_host;
+	struct device *dev = &dsi->dev;
+	struct device_node *dsi1;
+	struct nt36532 *ctx;
+	int num_dsi = 1;
+	int ret, i;
+
+	ctx = devm_drm_panel_alloc(dev, struct nt36532, panel,
+				   &nt36532_panel_funcs,
+				   DRM_MODE_CONNECTOR_DSI);
+	if (IS_ERR(ctx))
+		return PTR_ERR(ctx);
+
+	ret = devm_regulator_bulk_get_const(&dsi->dev,
+					    ARRAY_SIZE(nt36532_supplies),
+					    nt36532_supplies, &ctx->supplies);
+	if (ret < 0)
+		return ret;
+
+	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(ctx->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
+				     "Failed to get reset-gpios\n");
+
+	ctx->panel_info = of_device_get_match_data(dev);
+	panel_info = ctx->panel_info;
+	if (!panel_info)
+		return -ENODEV;
+
+	if (panel_info->is_dual_dsi) {
+		num_dsi = 2;
+		dsi1 = of_graph_get_remote_node(dsi->dev.of_node, 1, -1);
+		if (!dsi1) {
+			dev_err(dev, "cannot get secondary DSI node.\n");
+			return -ENODEV;
+		}
+
+		dsi1_host = of_find_mipi_dsi_host_by_node(dsi1);
+		of_node_put(dsi1);
+		if (!dsi1_host)
+			return dev_err_probe(dev, -EPROBE_DEFER,
+					     "cannot get secondary DSI host\n");
+
+		ctx->dsi[1] = devm_mipi_dsi_device_register_full(dev, dsi1_host,
+								 &dsi_info);
+		if (IS_ERR(ctx->dsi[1])) {
+			dev_err(dev, "cannot get secondary DSI device\n");
+			return PTR_ERR(ctx->dsi[1]);
+		}
+
+		mipi_dsi_set_drvdata(ctx->dsi[1], ctx);
+	}
+
+	ctx->dsi[0] = dsi;
+	mipi_dsi_set_drvdata(dsi, ctx);
+
+	ctx->panel.prepare_prev_first = true;
+
+	ret = devm_drm_panel_add(dev, &ctx->panel);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "failed to add panel\n");
+
+	for (i = 0; i < num_dsi; i++) {
+		ctx->dsi[i]->lanes = panel_info->lanes;
+		ctx->dsi[i]->format = panel_info->format;
+		ctx->dsi[i]->mode_flags = panel_info->mode_flags;
+		ctx->dsi[i]->dsc = panel_info->dsc_cfg;
+		ret = devm_mipi_dsi_attach(dev, ctx->dsi[i]);
+		if (ret < 0) {
+			return dev_err_probe(dev, ret,
+					     "Failed to attach to DSI host\n");
+		}
+	}
+
+	return 0;
+}
+
+static const struct panel_info csot_panel_info = {
+	.width_mm = 250,
+	.height_mm = 177,
+	.lanes = 4,
+	.format = MIPI_DSI_FMT_RGB888,
+	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT,
+	.display_mode = csot_display_mode,
+	.dsc_cfg = &csot_dsc_cfg,
+	.init_sequence = csot_init_sequence,
+	.is_dual_dsi = true,
+};
+
+static const struct of_device_id nt36532_of_match[] = {
+	{ .compatible = "csot,ppc100hb1-1", .data = &csot_panel_info },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, nt36532_of_match);
+
+static struct mipi_dsi_driver nt36532_driver = {
+	.probe = nt36532_probe,
+	.driver = {
+		.name = "panel-novatek-nt36532",
+		.of_match_table = nt36532_of_match,
+	},
+};
+module_mipi_dsi_driver(nt36532_driver);
+
+MODULE_AUTHOR("Junjie Cao <junjie.cao@linux.dev>");
+MODULE_DESCRIPTION("DRM driver for Novatek NT36532 based MIPI DSI panels");
+MODULE_LICENSE("GPL");
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2 2/2] drm/panel: Add Novatek NT36532 panel driver
       [not found]   ` <20260729162847.9F4A51F00A3A@smtp.kernel.org>
@ 2026-08-03 14:49     ` Junjie Cao
  0 siblings, 0 replies; 4+ messages in thread
From: Junjie Cao @ 2026-08-03 14:49 UTC (permalink / raw)
  To: sashiko-reviews
  Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Dmitry Baryshkov,
	Konrad Dybcio, Pengyu Luo, dri-devel, devicetree, linux-arm-msm,
	linux-kernel

On Wed, 29 Jul 2026 16:28:47 +0000, sashiko-bot@kernel.org wrote:
> - [High] Missing Kconfig dependency for DRM_DISPLAY_DSC_HELPER leads
>   to linker errors.
> - [High] Improper use of DCS brightness helper inside a vendor
>   sequence corrupts the initialization payload.
> - [Medium] Global static DSC configuration structure is assigned to
>   device context and mutated by host drivers.
> - [Medium] Missing DRM backlight device registration and OF backlight
>   mapping.
> - [Low] Panel physical dimensions are defined but not reported to DRM
>   userspace.

Thanks for the report. What I'll fix in v3 first, then the two claims
I disagree with.

Will fix in v3:

1) Kconfig: add select DRM_DISPLAY_DSC_HELPER + DRM_DISPLAY_HELPER (for
   drm_dsc_pps_payload_pack()) and DRM_KMS_HELPER (for
   drm_connector_helper_get_modes_fixed()), plus depends on
   BACKLIGHT_CLASS_DEVICE, like the other DSC panels. Verified on
   arm64: disable DRM_MSM/amdgpu and the other DSC panels and Kconfig
   happily gives you NT36532=m with the DSC helper off, leaving
   drm_dsc_pps_payload_pack without a provider. Most DSI hosts and all
   the DSI bridges select DRM_MIPI_DSI but not the DSC helper, so this
   is easy to hit. Good catch.

2) The set_display_brightness_multi(0x00d4) call sits in the page 0x23
   vendor dimming table (0x4e..0x52, one byte each); 0x51 there is a
   table entry, not the DCS brightness command. The stock OnePlus
   Pad 2 firmware DT sends it as an 0x15 short write (51 d4), while
   the helper emits a long write with a trailing 0x00. Will switch to
   a plain one-byte write. The "51 07 ff" after switching to page 0x10
   is the real brightness command and stays; I'll write it as
   MIPI_DCS_SET_DISPLAY_BRIGHTNESS with an explicit 07 ff payload
   instead of the byte-swapped 0xff07 (same bytes on the wire), and
   drop the other accidental MIPI_DCS_* macro names in the vendor pages
   (SET_GAMMA_CURVE, SET_ADDRESS_MODE, set_pixel_format, ...).

3) Make the DSC template const and copy it into the instance at probe
   time (ctx->dsc = *panel_info->dsc_cfg), like panel-novatek-nt36536 does,
   so the module-global struct is never written.

4) Call drm_panel_of_backlight(). Brightness on this device comes from
   a separate KTZ8866 backlight controller via the standard
   'backlight' property (already allowed through panel-common-dual.yaml,
   so no binding change). Without the hookup the backlight stays on
   after a panel DPMS off; reproducible on the device here.

5) Move width_mm/height_mm into csot_display_mode; modetest currently
   reports the connector as 0x0 mm.

Won't change:

a) "extra byte could shift subsequent commands out of alignment" - no.
   Each DCS write is its own DSI packet with its own payload length; a
   longer packet can't corrupt the ones after it. The 0x51 change
   above is about matching the vendor sequence, not corruption - the
   panel comes up fine as is.

b) The two DSI devices keep sharing one drm_dsc_config; only its
   storage moves, per 3). Both links of a bonded panel must run
   identical DSC parameters, and the host derives the rc parameters
   deterministically from the same seed values while the pair is
   brought up sequentially in one thread, so there is nothing to race.
   Per-link copies would just be two structs that must never diverge.

Thanks,
Junjie

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-08-04  4:10 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-29 16:08 [PATCH v2 0/2] drm/panel: Add support for Novatek NT36532 panel Junjie Cao
2026-07-29 16:08 ` [PATCH v2 1/2] dt-bindings: display: panel: Add Novatek NT36532 Junjie Cao
2026-07-29 16:08 ` [PATCH v2 2/2] drm/panel: Add Novatek NT36532 panel driver Junjie Cao
     [not found]   ` <20260729162847.9F4A51F00A3A@smtp.kernel.org>
2026-08-03 14:49     ` Junjie Cao

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®