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); + 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); + if (mode->clock == nt37701_panel_modes[MODE_144HZ].clock) + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, + 0x48, 0xf3, 0x49, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfc); + else if (mode->clock == nt37701_panel_modes[MODE_120HZ].clock) + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, + 0x94, 0x36, 0x53, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff); + else if (mode->clock == nt37701_panel_modes[MODE_90HZ].clock) + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, + 0x25, 0xfe, 0xe1, 0x4f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x92); + else if (mode->clock == nt37701_panel_modes[MODE_60HZ].clock) + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, + 0x46, 0xcc, 0x11, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xf0); + else + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, + 0xc0, 0x0e, 0xf3, 0x5d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x98); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe5, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x00); + + nt37701_panel_switch_page(&dsi_ctx, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc0, 0x0e, 0xb3, 0x21); + + nt37701_panel_switch_page(&dsi_ctx, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x03); + if (mode->clock == nt37701_panel_modes[MODE_48HZ].clock) { + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xce, + 0x22, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + } else { + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xce, + 0x22, 0x00, 0x00, 0x22, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + } + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + nt37701_panel_switch_page(&dsi_ctx, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0x63); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int nt37701_panel_disable(struct drm_panel *panel) +{ + struct nt37701_panel *ctx = to_nt37701_panel(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 100); + + return dsi_ctx.accum_err; +} + +static int nt37701_panel_prepare(struct drm_panel *panel, + struct drm_display_mode *mode) +{ + struct nt37701_panel *ctx = to_nt37701_panel(panel); + struct drm_dsc_picture_parameter_set pps; + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_enable(ctx->supply); + if (ret < 0) { + dev_err(dev, "Failed to enable power supply: %d\n", ret); + return ret; + } + + nt37701_panel_reset(ctx); + + ret = nt37701_panel_on(ctx, mode); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + goto err; + } + + drm_dsc_pps_payload_pack(&pps, &ctx->dsc); + + ret = mipi_dsi_picture_parameter_set(ctx->dsi, &pps); + if (ret < 0) { + dev_err(panel->dev, "failed to transmit PPS: %d\n", ret); + goto err; + } + + ret = mipi_dsi_compression_mode(ctx->dsi, true); + if (ret < 0) { + dev_err(dev, "failed to enable compression mode: %d\n", ret); + goto err; + } + + msleep(28); + + return 0; + +err: + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + return ret; +} + +static int nt37701_panel_unprepare(struct drm_panel *panel) +{ + struct nt37701_panel *ctx = to_nt37701_panel(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_disable(ctx->supply); + + return 0; +} + +static const struct drm_display_mode nt37701_panel_modes[] = { + [MODE_144HZ] = { + .clock = (1080 + 32 + 16 + 32) * (2400 + 12 + 4 + 4) * 144 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 32, + .hsync_end = 1080 + 32 + 16, + .htotal = 1080 + 32 + 16 + 32, + .vdisplay = 2400, + .vsync_start = 2400 + 12, + .vsync_end = 2400 + 12 + 4, + .vtotal = 2400 + 12 + 4 + 4, + .width_mm = 69, + .height_mm = 152, + .type = DRM_MODE_TYPE_DRIVER, + }, + [MODE_120HZ] = { + .clock = (1080 + 32 + 16 + 32) * (2400 + 12 + 4 + 4) * 120 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 32, + .hsync_end = 1080 + 32 + 16, + .htotal = 1080 + 32 + 16 + 32, + .vdisplay = 2400, + .vsync_start = 2400 + 12, + .vsync_end = 2400 + 12 + 4, + .vtotal = 2400 + 12 + 4 + 4, + .width_mm = 69, + .height_mm = 152, + .type = DRM_MODE_TYPE_DRIVER, + }, + [MODE_90HZ] = { + .clock = (1080 + 32 + 16 + 32) * (2400 + 12 + 4 + 4) * 90 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 32, + .hsync_end = 1080 + 32 + 16, + .htotal = 1080 + 32 + 16 + 32, + .vdisplay = 2400, + .vsync_start = 2400 + 12, + .vsync_end = 2400 + 12 + 4, + .vtotal = 2400 + 12 + 4 + 4, + .width_mm = 69, + .height_mm = 152, + .type = DRM_MODE_TYPE_DRIVER, + }, + [MODE_60HZ] = { + .clock = (1080 + 32 + 16 + 32) * (2400 + 12 + 4 + 4) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 32, + .hsync_end = 1080 + 32 + 16, + .htotal = 1080 + 32 + 16 + 32, + .vdisplay = 2400, + .vsync_start = 2400 + 12, + .vsync_end = 2400 + 12 + 4, + .vtotal = 2400 + 12 + 4 + 4, + .width_mm = 69, + .height_mm = 152, + .type = DRM_MODE_TYPE_DRIVER, + }, + [MODE_48HZ] = { + .clock = (1080 + 32 + 16 + 32) * (2400 + 12 + 4 + 4) * 48 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 32, + .hsync_end = 1080 + 32 + 16, + .htotal = 1080 + 32 + 16 + 32, + .vdisplay = 2400, + .vsync_start = 2400 + 12, + .vsync_end = 2400 + 12 + 4, + .vtotal = 2400 + 12 + 4 + 4, + .width_mm = 69, + .height_mm = 152, + .type = DRM_MODE_TYPE_DRIVER, + }, +}; + +static int nt37701_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + int count = 0; + + for (int i = 0; i < ARRAY_SIZE(nt37701_panel_modes); i++) + count += drm_connector_helper_get_modes_fixed(connector, + &nt37701_panel_modes[i]); + + return count; +} + +static const struct drm_panel_funcs nt37701_panel_panel_funcs = { + .prepare_for_mode = nt37701_panel_prepare, + .unprepare = nt37701_panel_unprepare, + .disable = nt37701_panel_disable, + .get_modes = nt37701_panel_get_modes, +}; + +static int nt37701_panel_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 nt37701_panel_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 nt37701_panel_bl_ops = { + .update_status = nt37701_panel_bl_update_status, + .get_brightness = nt37701_panel_bl_get_brightness, +}; + +static struct backlight_device * +nt37701_panel_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 1757, + .max_brightness = 3514, + .scale = BACKLIGHT_SCALE_NON_LINEAR, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &nt37701_panel_bl_ops, &props); +} + +static int nt37701_panel_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct nt37701_panel *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(ctx->supply)) + return dev_err_probe(dev, PTR_ERR(ctx->supply), + "Failed to get power-supply\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB101010; + dsi->mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS; + + drm_panel_init(&ctx->panel, dev, &nt37701_panel_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = nt37701_panel_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + /* This panel only supports DSC; unconditionally enable it */ + dsi->dsc = &ctx->dsc; + + ctx->dsc.dsc_version_major = 1; + ctx->dsc.dsc_version_minor = 1; + + /* TODO: Pass slice_per_pkt = 1 */ + ctx->dsc.slice_height = 12; + ctx->dsc.slice_width = 1080; + ctx->dsc.slice_count = 1; + ctx->dsc.bits_per_component = 10; + ctx->dsc.bits_per_pixel = 10 << 4; + ctx->dsc.block_pred_enable = true; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void nt37701_panel_remove(struct mipi_dsi_device *dsi) +{ + struct nt37701_panel *ctx = 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(&ctx->panel); +} + +static const struct of_device_id nt37701_panel_of_match[] = { + { .compatible = "tianma,nt37701-dubai" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, nt37701_panel_of_match); + +static struct mipi_dsi_driver nt37701_panel_driver = { + .probe = nt37701_panel_probe, + .remove = nt37701_panel_remove, + .driver = { + .name = "panel-novatek-nt37701", + .of_match_table = nt37701_panel_of_match, + }, +}; +module_mipi_dsi_driver(nt37701_panel_driver); + +MODULE_AUTHOR("Val Packett <[email protected]>"); +MODULE_DESCRIPTION("Driver for Novatek NT37701 based DSI panels"); +MODULE_LICENSE("GPL"); -- 2.53.0

