Re: [RFC PATCH 2/2] drm/panel: Add driver for Novatek NT37701 based OLED panels

From: Dmitry Baryshkov

Date: Sun Apr 12 2026 - 21:30:06 EST


On Sun, Apr 12, 2026 at 08:00:45AM -0300, Val Packett wrote:
> Add a driver for panels with the NT37701 DDIC, starting with the Tianma
> panel (unknown model) used in the Motorola Edge 30 (dubai) smartphone.
>
> Signed-off-by: Val Packett <val@xxxxxxxxxxxx>
> ---
> drivers/gpu/drm/panel/Makefile | 1 +
> drivers/gpu/drm/panel/panel-novatek-nt37701.c | 532 ++++++++++++++++++
> 2 files changed, 533 insertions(+)
> create mode 100644 drivers/gpu/drm/panel/panel-novatek-nt37701.c
>
> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index a4291dc3905b..183f968a5333 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -60,6 +60,7 @@ obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36523) += panel-novatek-nt36523.o
> obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o
> obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672E) += panel-novatek-nt36672e.o
> obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37700F) += panel-novatek-nt37700f.o
> +obj-m += panel-novatek-nt37701.o
> obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37801) += panel-novatek-nt37801.o
> obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o
> obj-$(CONFIG_DRM_PANEL_MANTIX_MLAF057WE51) += panel-mantix-mlaf057we51.o
> diff --git a/drivers/gpu/drm/panel/panel-novatek-nt37701.c b/drivers/gpu/drm/panel/panel-novatek-nt37701.c
> new file mode 100644
> index 000000000000..ea8a208fecb2
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-novatek-nt37701.c
> @@ -0,0 +1,532 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026, The Linux Foundation. All rights reserved.
> + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree:
> + * Copyright (c) 2013, The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of.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>
> +
> +enum nt37701_mode_idx {
> + MODE_144HZ,
> + MODE_120HZ,
> + MODE_90HZ,
> + MODE_60HZ,
> + MODE_48HZ,
> +};
> +
> +static const struct drm_display_mode nt37701_panel_modes[5];
> +
> +struct nt37701_panel {
> + struct drm_panel panel;
> + struct mipi_dsi_device *dsi;
> + struct drm_dsc_config dsc;
> + struct regulator *supply;
> + struct gpio_desc *reset_gpio;
> +};
> +
> +static inline
> +struct nt37701_panel *to_nt37701_panel(struct drm_panel *panel)
> +{
> + return container_of(panel, struct nt37701_panel, panel);
> +}
> +
> +static void nt37701_panel_reset(struct nt37701_panel *ctx)
> +{
> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> + usleep_range(1000, 2000);
> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> + usleep_range(10000, 11000);
> +}
> +
> +#define nt37701_panel_switch_page(ctx, page) \
> + mipi_dsi_dcs_write_seq_multi((ctx), 0xf0, 0x55, 0xaa, 0x52, 0x08, (page))
> +
> +static int nt37701_panel_on(struct nt37701_panel *ctx,
> + struct drm_display_mode *mode)
> +{
> + struct mipi_dsi_device *dsi = ctx->dsi;
> + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
> +
> + dsi->mode_flags |= MIPI_DSI_MODE_LPM;
> +
> + nt37701_panel_switch_page(&dsi_ctx, 0x00);
> + /* Voltage */
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x06);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb5, 0x00, 0x18, 0x4f);
> +
> + nt37701_panel_switch_page(&dsi_ctx, 0x00);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x11);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x0f);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2,
> + 0x60, 0x50, 0x66, 0x91, 0x86, 0x91);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3,
> + 0x00, 0x08, 0x01, 0x5f, 0x01, 0x5f, 0x02,
> + 0xa4, 0x02, 0xa4, 0x03, 0xbb);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x0c);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3,
> + 0x03, 0xbb, 0x05, 0x2f, 0x05, 0x2f, 0x06,
> + 0x91, 0x06, 0x91, 0x06, 0x92);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x18);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3,
> + 0x06, 0x92, 0x0a, 0x2f, 0x0a, 0x2f, 0x0d,
> + 0xb9, 0x0d, 0xb9, 0x0f, 0xff);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x00);
> +
> + nt37701_panel_switch_page(&dsi_ctx, 0x00);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x08);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc3, 0x04);
> +
> + nt37701_panel_switch_page(&dsi_ctx, 0x03);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbc, 0x11, 0x00, 0x09, 0x51);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x04);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbc,
> + 0x00, 0x09, 0x51, 0x00, 0x09, 0x51, 0x00,
> + 0x0b, 0x41);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x0d);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbc, 0x00, 0x11, 0x51);
> +
> + nt37701_panel_switch_page(&dsi_ctx, 0x00);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc6,
> + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
> + 0x66, 0x66, 0x66, 0x66, 0x66);
> +
> + nt37701_panel_switch_page(&dsi_ctx, 0x00);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x1c);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc0,
> + 0x15, 0x0a, 0x38, 0x27, 0x49);
> + /* DSC */
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x01);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x01);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x91,
> + 0xab, 0x28, 0x00, 0x0c, 0xc2, 0x00, 0x03,
> + 0x1c, 0x01, 0x7e, 0x00, 0x0f, 0x08, 0xbb,
> + 0x04, 0x3d, 0x10, 0xf0);
> +
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_MEMORY_START,
> + 0x00);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x01);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x88, 0x02, 0x1c, 0x08, 0x73);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x00);
> + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x0000);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY,
> + 0x20);
> + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x0437);
> + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x095f);
> + if (mode->clock == nt37701_panel_modes[MODE_144HZ].clock)
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x04);
> + else if (mode->clock == nt37701_panel_modes[MODE_120HZ].clock)
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x02);
> + else if (mode->clock == nt37701_panel_modes[MODE_90HZ].clock)
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x07);
> + else if (mode->clock == nt37701_panel_modes[MODE_60HZ].clock)
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x03);
> + else
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x08);

This should be coming through the drm_atomic_state for the CRTC used by
this panel. Most of the plumbing is already there, panel bridge needs to
pass atomic state to the drm_panel functions. Note, the bridge has a
reference to the encoder, so it can retrieve CRTC state. Panel functions
would need to get drm_connector so that they can perform the same
lookup. Once you get drm_crtc_state::mode, you can perform lookups here.

It's a longer path than the one you have RFC'd here, but I think, it is
a proper way to go.

> + nt37701_panel_switch_page(&dsi_ctx, 0x08);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1,
> + 0x00, 0x03, 0x03, 0x03, 0x00);
> + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe2,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00);

--
With best wishes
Dmitry