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 <[email protected]> > --- > 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

