Re: [PATCH 2/2] drm/panel: Add driver for Novatek NT37703 based panels
Neil Armstrong <[email protected]>
| Newsgroups | org.kernel.vger.phone-devel,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Organization | Linaro |
| Message-ID | <[email protected]> |
On 8/11/26 07:52, Esteban Urrutia via B4 Relay wrote: > From: Esteban Urrutia <[email protected]> > > Add driver for panels based on this DDIC. > Initial support is added for the Tianma "v2" panel (no panel model) found > in the Motorola ThinkPhone by motorola. > > Signed-off-by: Esteban Urrutia <[email protected]> > --- > MAINTAINERS | 6 + > drivers/gpu/drm/panel/Kconfig | 13 + > drivers/gpu/drm/panel/Makefile | 1 + > drivers/gpu/drm/panel/panel-novatek-nt37703.c | 467 ++++++++++++++++++++++++++ > 4 files changed, 487 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index da14a58df9e8..e2bb50a8b1af 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -8339,6 +8339,12 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git > F: Documentation/devicetree/bindings/display/panel/novatek,nt36672a.yaml > F: drivers/gpu/drm/panel/panel-novatek-nt36672a.c > > +DRM DRIVER FOR NOVATEK NT37703 PANELS > +M: Esteban Urrutia <[email protected]> > +S: Maintained > +F: Documentation/devicetree/bindings/display/panel/novatek,nt37703.yaml > +F: drivers/gpu/drm/panel/panel-novatek-nt37703.c > + > DRM DRIVER FOR NOVATEK NT37801 PANELS > M: Krzysztof Kozlowski <[email protected]> > S: Maintained > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig > index cbdf7b8f7f7a..9cd74ceef44e 100644 > --- a/drivers/gpu/drm/panel/Kconfig > +++ b/drivers/gpu/drm/panel/Kconfig > @@ -706,6 +706,19 @@ config DRM_PANEL_NOVATEK_NT37700F > Say Y here if you want to enable support for Novatek NT37700F DSI > panel module. The panel has a resolution of 1080x2160. > > +config DRM_PANEL_NOVATEK_NT37703 > + tristate "Novatek NT37703 DSI panel" > + depends on OF > + depends on DRM_MIPI_DSI > + depends on BACKLIGHT_CLASS_DEVICE > + select DRM_DISPLAY_DSC_HELPER > + select DRM_DISPLAY_HELPER > + help > + Say Y or M here if you want to enable support for panels based > + on the Novatek NT37703 display driver IC, such as Tianma > + panels found in some units of the Motorola ThinkPhone by > + motorola. > + > config DRM_PANEL_NOVATEK_NT37801 > tristate "Novatek NT37801/NT37810 AMOLED DSI panel" > depends on OF > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile > index 3b523cf37833..4a69b2657c5b 100644 > --- a/drivers/gpu/drm/panel/Makefile > +++ b/drivers/gpu/drm/panel/Makefile > @@ -68,6 +68,7 @@ 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 > obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37700F) += panel-novatek-nt37700f.o > +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37703) += panel-novatek-nt37703.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-nt37703.c b/drivers/gpu/drm/panel/panel-novatek-nt37703.c > new file mode 100644 > index 000000000000..5858bf12a310 > --- /dev/null > +++ b/drivers/gpu/drm/panel/panel-novatek-nt37703.c > @@ -0,0 +1,467 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Driver for Novatek NT37703 based MIPI DSI panels > + * > + * Multiple panel support based on Novatek NT36523 driver > + * Per panel DSC support based on Ilitek ILI9882T driver > + * > + * Copyright (c) 2026 Esteban Urrutia <[email protected]> > + */ > + > +#include <linux/backlight.h> > +#include <linux/delay.h> > +#include <linux/gpio/consumer.h> > +#include <linux/mod_devicetable.h> > +#include <linux/module.h> > +#include <linux/of.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> > + > +#define DSC_BPG_OFFSET(x) ((u8) ((x) & DSC_RANGE_BPG_OFFSET_MASK)) > +#define NT37703_DCS_SWITCH_PAGE 0xf0 > + > +#define nt37703_cmd_test_on(ctx) mipi_dsi_dcs_write_seq_multi((ctx), 0xff, 0xaa, 0x55, \ > + 0xa5, 0x80) > +#define nt37703_switch_page(ctx, page) mipi_dsi_dcs_write_seq_multi((ctx), \ > + NT37703_DCS_SWITCH_PAGE, \ > + 0x55, 0xaa, 0x52, 0x08, (page)) > + > +struct panel_info { > + struct drm_panel panel; > + const struct panel_desc *desc; > + struct mipi_dsi_device *dsi; > + struct drm_dsc_config dsc; > + struct regulator_bulk_data *supplies; > + struct gpio_desc *reset_gpio; > +}; > + > +struct panel_desc { > + unsigned int width_mm; > + unsigned int height_mm; > + > + unsigned int lanes; > + unsigned long mode_flags; > + enum mipi_dsi_pixel_format format; > + > + const struct drm_display_mode *modes; > + unsigned int num_modes; > + int (*init_sequence)(struct panel_info *pinfo); > + const struct drm_dsc_config *dsc; > +}; > + > +/* > + * These parameters are applied when using DSC 1.1 and when the DSC configuration specifies 10 b.p.c > + * and 8 b.p.p > + */ > +static const struct drm_dsc_rc_range_parameters nt37703_rc_range_params[DSC_NUM_BUF_RANGES] = { > + { 0, 8, DSC_BPG_OFFSET(2) }, > + { 4, 8, DSC_BPG_OFFSET(0) }, > + { 5, 9, DSC_BPG_OFFSET(0) }, > + { 5, 10, DSC_BPG_OFFSET(-2) }, > + { 7, 11, DSC_BPG_OFFSET(-4) }, > + { 7, 11, DSC_BPG_OFFSET(-6) }, > + { 7, 11, DSC_BPG_OFFSET(-8) }, > + { 7, 12, DSC_BPG_OFFSET(-8) }, > + { 7, 13, DSC_BPG_OFFSET(-8) }, > + { 8, 14, DSC_BPG_OFFSET(-10) }, > + { 9, 14, DSC_BPG_OFFSET(-10) }, > + { 9, 15, DSC_BPG_OFFSET(-10) }, > + { 9, 15, DSC_BPG_OFFSET(-12) }, > + { 12, 16, DSC_BPG_OFFSET(-12) }, > + { 16, 17, DSC_BPG_OFFSET(-12) }, > +}; > + > +static const struct regulator_bulk_data nt37703_supplies[] = { > + { .supply = "avdd" }, > + { .supply = "elvdd" }, > + { .supply = "elvss" }, > + { .supply = "vci" }, > + { .supply = "vdd" }, > + { .supply = "vddi" }, > +}; > + > +static inline struct panel_info *to_panel_info(struct drm_panel *panel) > +{ > + return container_of_const(panel, struct panel_info, panel); > +} > + > +static void nt37703_reset(struct panel_info *pinfo) > +{ > + gpiod_set_value_cansleep(pinfo->reset_gpio, 0); > + usleep_range(1000, 2000); > + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); > + usleep_range(1000, 2000); > + gpiod_set_value_cansleep(pinfo->reset_gpio, 0); > + usleep_range(10000, 11000); > +} > + > +static int nt37703_off(struct panel_info *pinfo) > +{ > + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; > + > + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); > + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); > + mipi_dsi_msleep(&dsi_ctx, 120); > + > + return dsi_ctx.accum_err; > +} > + > +static int nt37703_prepare(struct drm_panel *panel) > +{ > + struct panel_info *pinfo = to_panel_info(panel); > + struct device *dev = &pinfo->dsi->dev; > + struct drm_dsc_picture_parameter_set pps; > + int ret; > + int i; > + > + ret = regulator_bulk_enable(ARRAY_SIZE(nt37703_supplies), pinfo->supplies); > + if (ret < 0) { > + dev_err(dev, "Failed to enable regulators: %d\n", ret); > + return ret; > + } > + > + nt37703_reset(pinfo); > + > + ret = pinfo->desc->init_sequence(pinfo); > + if (ret < 0) { > + dev_err(dev, "Failed to initialize panel: %d\n", ret); > + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); > + regulator_bulk_disable(ARRAY_SIZE(nt37703_supplies), pinfo->supplies); > + return ret; > + } > + > + /* > + * For some reason this DDIC uses different parameters for the RC ranges under certain DSC > + * configurations. > + * Not applying them results in heavy visual glitches, so do so if all conditions are met. > + */ > + if (pinfo->dsc.dsc_version_major == 1 && pinfo->dsc.dsc_version_minor == 1 && > + pinfo->dsc.bits_per_component == 10 && pinfo->dsc.bits_per_pixel == 8 << 4) { > + for (i = 0; i < DSC_NUM_BUF_RANGES; i++) { > + pinfo->dsc.rc_range_params[i].range_min_qp = > + nt37703_rc_range_params[i].range_min_qp; > + pinfo->dsc.rc_range_params[i].range_max_qp = > + nt37703_rc_range_params[i].range_max_qp; > + pinfo->dsc.rc_range_params[i].range_bpg_offset = > + nt37703_rc_range_params[i].range_bpg_offset; > + } > + } > + > + /* > + * Given drm_dsc_setup_rc_params() inconditionally overrides certain members in pinfo->dsc > + * that may have been set in pinfo->desc->dsc, override these members once more with what's > + * found in pinfo->desc->dsc. > + */ > + if (pinfo->desc->dsc->first_line_bpg_offset) > + pinfo->dsc.first_line_bpg_offset = pinfo->desc->dsc->first_line_bpg_offset; > + > + drm_dsc_pps_payload_pack(&pps, &pinfo->dsc); > + > + ret = mipi_dsi_picture_parameter_set(pinfo->dsi, &pps); > + if (ret < 0) { > + dev_err(panel->dev, "Failed to transmit PPS: %d\n", ret); > + return ret; > + } > + > + ret = mipi_dsi_compression_mode(pinfo->dsi, true); > + if (ret < 0) { > + dev_err(dev, "Failed to enable compression mode: %d\n", ret); > + return ret; > + } > + > + msleep(28); > + > + return 0; > +} > + > +static int nt37703_unprepare(struct drm_panel *panel) > +{ > + struct panel_info *pinfo = to_panel_info(panel); > + struct device *dev = &pinfo->dsi->dev; > + int ret; > + > + ret = nt37703_off(pinfo); > + if (ret < 0) > + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); > + > + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); > + regulator_bulk_disable(ARRAY_SIZE(nt37703_supplies), pinfo->supplies); > + > + return 0; > +} > + > +static int nt37703_get_modes(struct drm_panel *panel, struct drm_connector *connector) > +{ > + struct panel_info *pinfo = to_panel_info(panel); > + int i; > + > + for (i = 0; i < pinfo->desc->num_modes; i++) { > + const struct drm_display_mode *m = &pinfo->desc->modes[i]; > + struct drm_display_mode *mode; > + > + mode = drm_mode_duplicate(connector->dev, m); > + if (!mode) { > + dev_err(panel->dev, "Failed to add mode %ux%u@%u\n", m->hdisplay, > + m->vdisplay, drm_mode_vrefresh(m)); > + return -ENOMEM; > + } > + > + mode->type = DRM_MODE_TYPE_DRIVER; > + if (i == 0) > + mode->type |= DRM_MODE_TYPE_PREFERRED; > + > + drm_mode_set_name(mode); > + drm_mode_probed_add(connector, mode); > + } > + > + connector->display_info.width_mm = pinfo->desc->width_mm; > + connector->display_info.height_mm = pinfo->desc->height_mm; > + > + return pinfo->desc->num_modes; The only supported panel only supports a single mode, just support a single mode for now and drop this open coded support for multiple modes. You can add it later if somehow you need multiple modes. > +} > + > +static const struct drm_panel_funcs nt37703_panel_funcs = { > + .prepare = nt37703_prepare, > + .unprepare = nt37703_unprepare, > + .get_modes = nt37703_get_modes, > +}; > + > +static int nt37703_bl_update_status(struct backlight_device *bl) > +{ > + struct mipi_dsi_device *dsi = bl_get_data(bl); > + u16 brightness = backlight_get_brightness(bl); > + int ret; > + > + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; > + > + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); > + if (ret < 0) > + return ret; > + > + dsi->mode_flags |= MIPI_DSI_MODE_LPM; > + > + return 0; > +} > + > +static int nt37703_bl_get_brightness(struct backlight_device *bl) > +{ > + struct mipi_dsi_device *dsi = bl_get_data(bl); > + u16 brightness; > + int ret; > + > + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; > + > + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); > + if (ret < 0) > + return ret; > + > + dsi->mode_flags |= MIPI_DSI_MODE_LPM; > + > + return brightness; > +} > + > +static const struct backlight_ops nt37703_bl_ops = { > + .update_status = nt37703_bl_update_status, > + .get_brightness = nt37703_bl_get_brightness, > +}; > + > +static struct backlight_device *nt37703_create_backlight(struct mipi_dsi_device *dsi) > +{ > + struct device *dev = &dsi->dev; > + const struct backlight_properties props = { > + .type = BACKLIGHT_RAW, > + .brightness = 2047, > + .max_brightness = 4095, > + }; > + > + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, &nt37703_bl_ops, > + &props); > +} > + > +static const struct drm_display_mode bronco_tianma_modes[] = { > + { > + .clock = (1080 + 16 + 4 + 16) * (2400 + 44 + 2 + 14) * 144 / 1000, > + .hdisplay = 1080, > + .hsync_start = 1080 + 16, > + .hsync_end = 1080 + 16 + 4, > + .htotal = 1080 + 16 + 4 + 16, > + .vdisplay = 2400, > + .vsync_start = 2400 + 44, > + .vsync_end = 2400 + 44 + 2, > + .vtotal = 2400 + 44 + 2 + 14, > + }, > +}; > + > +static int bronco_tianma_v2_init_sequence(struct panel_info *pinfo) > +{ > + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; > + > + nt37703_switch_page(&dsi_ctx, 0x00); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x06); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb5, 0x29); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x07); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb5, 0x2b, 0x1b, 0x00, 0x32); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x88, 0x01, 0x02, 0x1b, 0x08, 0x77); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x87, 0x20); > + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0, 1080 - 1); > + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0, 2400 - 1); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x03, 0x03); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); > + nt37703_cmd_test_on(&dsi_ctx); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x15); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, 0x01, 0xe1); > + nt37703_cmd_test_on(&dsi_ctx); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x17); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x02); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x13); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf9, 0x01); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x3c); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf5, 0x81); > + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); > + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x0000); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x00); > + nt37703_switch_page(&dsi_ctx, 0x01); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x03); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc7, 0x47); > + nt37703_switch_page(&dsi_ctx, 0x00); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0xc4); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x09, 0x84, 0x09, 0x84, 0x09, 0x84, 0x16, > + 0xd6); > + nt37703_switch_page(&dsi_ctx, 0x03); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x33); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc6, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, > + 0x18); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x01); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x83); > + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8b, 0x00, 0x00); > + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); > + mipi_dsi_msleep(&dsi_ctx, 120); > + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); > + > + return dsi_ctx.accum_err; > +} > + > +static const struct drm_dsc_config bronco_tianma_dsc = { > + .dsc_version_major = 1, > + .dsc_version_minor = 1, > + .slice_height = 20, > + .slice_width = 540, > + .slice_count = 2, /* 1080 / slice_width */ > + .bits_per_component = 10, > + .bits_per_pixel = 8 << 4, /* 4 fractional bits */ > + .block_pred_enable = true, > + .first_line_bpg_offset = 13, > +}; > + > +static const struct panel_desc bronco_tianma_v2_desc = { > + .modes = bronco_tianma_modes, > + .num_modes = ARRAY_SIZE(bronco_tianma_modes), > + .width_mm = 68, > + .height_mm = 152, > + .lanes = 4, > + .format = MIPI_DSI_FMT_RGB101010, > + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_NO_EOT_PACKET | > + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM | > + MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT; > + .init_sequence = bronco_tianma_v2_init_sequence, > + .dsc = &bronco_tianma_dsc, > +}; > + > +static int nt37703_probe(struct mipi_dsi_device *dsi) > +{ > + struct device *dev = &dsi->dev; > + struct panel_info *pinfo; > + int ret; > + > + pinfo = devm_drm_panel_alloc(dev, struct panel_info, panel, &nt37703_panel_funcs, > + DRM_MODE_CONNECTOR_DSI); > + if (IS_ERR(pinfo)) > + return PTR_ERR(pinfo); > + > + pinfo->desc = of_device_get_match_data(dev); > + if (IS_ERR(pinfo->desc)) > + return PTR_ERR(pinfo->desc); > + > + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(nt37703_supplies), nt37703_supplies, > + &pinfo->supplies); > + if (ret < 0) > + return dev_err_probe(dev, ret, "Failed to get regulators\n"); > + > + pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(pinfo->reset_gpio)) > + return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio), > + "Failed to get reset-gpios\n"); > + > + pinfo->dsi = dsi; > + mipi_dsi_set_drvdata(dsi, pinfo); > + > + dsi->lanes = pinfo->desc->lanes; > + dsi->format = pinfo->desc->format; > + dsi->mode_flags = pinfo->desc->mode_flags; > + > + pinfo->panel.prepare_prev_first = true; > + > + pinfo->panel.backlight = nt37703_create_backlight(dsi); > + if (IS_ERR(pinfo->panel.backlight)) > + return dev_err_probe(dev, PTR_ERR(pinfo->panel.backlight), > + "Failed to create backlight\n"); > + > + drm_panel_add(&pinfo->panel); Use devm_ variant > + > + /* This panel only supports DSC; unconditionally enable it */ > + pinfo->dsc = *(pinfo->desc->dsc); > + dsi->dsc = &pinfo->dsc; > + > + ret = mipi_dsi_attach(dsi); Use devm_ variant > + if (ret < 0) { > + drm_panel_remove(&pinfo->panel); > + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); > + } > + > + return 0; > +} > + > +static void nt37703_remove(struct mipi_dsi_device *dsi) > +{ > + struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi); > + int ret; > + > + ret = mipi_dsi_detach(dsi); > + if (ret < 0) > + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); > + > + drm_panel_remove(&pinfo->panel); > +} And drop the remove() Thanks. Neil > + > +static const struct of_device_id nt37703_of_match[] = { > + { > + .compatible = "motorola,bronco-tianma-v2-nt37703", > + .data = &bronco_tianma_v2_desc, > + }, > + { /* sentinel */ }, > +}; > +MODULE_DEVICE_TABLE(of, nt37703_of_match); > + > +static struct mipi_dsi_driver nt37703_driver = { > + .probe = nt37703_probe, > + .remove = nt37703_remove, > + .driver = { > + .name = "panel-novatek-nt37703", > + .of_match_table = nt37703_of_match, > + }, > +}; > +module_mipi_dsi_driver(nt37703_driver); > + > +MODULE_AUTHOR("Esteban Urrutia <[email protected]>"); > +MODULE_DESCRIPTION("DRM driver for Novatek NT37703 based MIPI DSI panels"); > +MODULE_LICENSE("GPL"); >