175a5dbd1SPengyu Luo // SPDX-License-Identifier: GPL-2.0-only 275a5dbd1SPengyu Luo /* 375a5dbd1SPengyu Luo * Novatek NT36536 DriverIC panels driver 475a5dbd1SPengyu Luo * Copyright (c) 2026 Pengyu Luo <mitltlatltl@gmail.com> 575a5dbd1SPengyu Luo * 675a5dbd1SPengyu Luo * Based on the sample code which is generated with 775a5dbd1SPengyu Luo * linux-mdss-dsi-panel-driver-generator 875a5dbd1SPengyu Luo */ 975a5dbd1SPengyu Luo 1075a5dbd1SPengyu Luo #include <linux/backlight.h> 1175a5dbd1SPengyu Luo #include <linux/delay.h> 1275a5dbd1SPengyu Luo #include <linux/gpio/consumer.h> 1375a5dbd1SPengyu Luo #include <linux/device-id/of.h> 1475a5dbd1SPengyu Luo #include <linux/module.h> 1575a5dbd1SPengyu Luo #include <linux/of.h> 1675a5dbd1SPengyu Luo #include <linux/of_graph.h> 1775a5dbd1SPengyu Luo #include <linux/regulator/consumer.h> 1875a5dbd1SPengyu Luo 1975a5dbd1SPengyu Luo #include <drm/display/drm_dsc.h> 2075a5dbd1SPengyu Luo #include <drm/display/drm_dsc_helper.h> 2175a5dbd1SPengyu Luo #include <drm/drm_mipi_dsi.h> 2275a5dbd1SPengyu Luo #include <drm/drm_modes.h> 2375a5dbd1SPengyu Luo #include <drm/drm_panel.h> 2475a5dbd1SPengyu Luo #include <drm/drm_probe_helper.h> 2575a5dbd1SPengyu Luo 2675a5dbd1SPengyu Luo #include <video/mipi_display.h> 2775a5dbd1SPengyu Luo 2875a5dbd1SPengyu Luo struct novatek { 2975a5dbd1SPengyu Luo struct drm_panel panel; 3075a5dbd1SPengyu Luo struct mipi_dsi_device *dsi[2]; 3175a5dbd1SPengyu Luo const struct panel_desc *desc; 3275a5dbd1SPengyu Luo struct drm_dsc_config dsc; 3375a5dbd1SPengyu Luo struct gpio_desc *reset_gpio; 3475a5dbd1SPengyu Luo struct regulator_bulk_data *supplies; 3575a5dbd1SPengyu Luo struct backlight_device *backlight; 3675a5dbd1SPengyu Luo }; 3775a5dbd1SPengyu Luo 3875a5dbd1SPengyu Luo struct panel_desc { 3975a5dbd1SPengyu Luo unsigned int width_mm; 4075a5dbd1SPengyu Luo unsigned int height_mm; 4175a5dbd1SPengyu Luo unsigned int bpc; 4275a5dbd1SPengyu Luo unsigned int lanes; 4375a5dbd1SPengyu Luo enum mipi_dsi_pixel_format format; 4475a5dbd1SPengyu Luo unsigned long mode_flags; 4575a5dbd1SPengyu Luo const struct drm_dsc_config *dsc_cfg; 4675a5dbd1SPengyu Luo const struct drm_display_mode *modes; 4775a5dbd1SPengyu Luo unsigned int num_modes; 4875a5dbd1SPengyu Luo 4975a5dbd1SPengyu Luo int (*init_sequence)(struct mipi_dsi_multi_context *dsi_ctx); 5075a5dbd1SPengyu Luo 5175a5dbd1SPengyu Luo bool is_dual_dsi; 5275a5dbd1SPengyu Luo bool has_dcs_backlight; 5375a5dbd1SPengyu Luo }; 5475a5dbd1SPengyu Luo 5575a5dbd1SPengyu Luo static const struct regulator_bulk_data novatek_supplies[] = { 5675a5dbd1SPengyu Luo { .supply = "vddio" }, 5775a5dbd1SPengyu Luo { .supply = "vsp" }, 5875a5dbd1SPengyu Luo { .supply = "vsn" }, 5975a5dbd1SPengyu Luo }; 6075a5dbd1SPengyu Luo 6175a5dbd1SPengyu Luo static inline struct novatek *to_novatek(struct drm_panel *panel) 6275a5dbd1SPengyu Luo { 6375a5dbd1SPengyu Luo return container_of(panel, struct novatek, panel); 6475a5dbd1SPengyu Luo } 6575a5dbd1SPengyu Luo 6675a5dbd1SPengyu Luo static inline struct mipi_dsi_device *to_primary_dsi(struct novatek *ctx) 6775a5dbd1SPengyu Luo { 6875a5dbd1SPengyu Luo /* Sync on DSI1 for dual dsi */ 6975a5dbd1SPengyu Luo return ctx->desc->is_dual_dsi ? ctx->dsi[1] : ctx->dsi[0]; 7075a5dbd1SPengyu Luo } 7175a5dbd1SPengyu Luo 7275a5dbd1SPengyu Luo static void novatek_reset(struct novatek *ctx) 7375a5dbd1SPengyu Luo { 7475a5dbd1SPengyu Luo gpiod_set_value_cansleep(ctx->reset_gpio, 1); 7575a5dbd1SPengyu Luo usleep_range(11000, 12000); 7675a5dbd1SPengyu Luo gpiod_set_value_cansleep(ctx->reset_gpio, 0); 7775a5dbd1SPengyu Luo usleep_range(1000, 2000); 7875a5dbd1SPengyu Luo gpiod_set_value_cansleep(ctx->reset_gpio, 1); 7975a5dbd1SPengyu Luo usleep_range(3000, 4000); 8075a5dbd1SPengyu Luo gpiod_set_value_cansleep(ctx->reset_gpio, 0); 8175a5dbd1SPengyu Luo usleep_range(10000, 11000); 8275a5dbd1SPengyu Luo } 8375a5dbd1SPengyu Luo 8475a5dbd1SPengyu Luo static int novatek_prepare(struct drm_panel *panel) 8575a5dbd1SPengyu Luo { 8675a5dbd1SPengyu Luo struct novatek *ctx = to_novatek(panel); 8775a5dbd1SPengyu Luo struct mipi_dsi_device *dsi = to_primary_dsi(ctx); 8875a5dbd1SPengyu Luo struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 8975a5dbd1SPengyu Luo struct drm_dsc_picture_parameter_set pps; 9075a5dbd1SPengyu Luo struct device *dev = &dsi->dev; 9175a5dbd1SPengyu Luo int ret; 9275a5dbd1SPengyu Luo 9375a5dbd1SPengyu Luo ret = regulator_bulk_enable(ARRAY_SIZE(novatek_supplies), 9475a5dbd1SPengyu Luo ctx->supplies); 9575a5dbd1SPengyu Luo if (ret < 0) 9675a5dbd1SPengyu Luo return ret; 9775a5dbd1SPengyu Luo 9875a5dbd1SPengyu Luo novatek_reset(ctx); 9975a5dbd1SPengyu Luo 10075a5dbd1SPengyu Luo ret = ctx->desc->init_sequence(&dsi_ctx); 10175a5dbd1SPengyu Luo if (ret < 0) { 10275a5dbd1SPengyu Luo dev_err(dev, "Failed to initialize panel: %d\n", ret); 10375a5dbd1SPengyu Luo gpiod_set_value_cansleep(ctx->reset_gpio, 1); 10475a5dbd1SPengyu Luo regulator_bulk_disable(ARRAY_SIZE(novatek_supplies), 10575a5dbd1SPengyu Luo ctx->supplies); 10675a5dbd1SPengyu Luo return ret; 10775a5dbd1SPengyu Luo } 10875a5dbd1SPengyu Luo 10975a5dbd1SPengyu Luo drm_dsc_pps_payload_pack(&pps, &ctx->dsc); 11075a5dbd1SPengyu Luo mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); 11175a5dbd1SPengyu Luo mipi_dsi_compression_mode_multi(&dsi_ctx, true); 11275a5dbd1SPengyu Luo mipi_dsi_msleep(&dsi_ctx, 28); 11375a5dbd1SPengyu Luo 11475a5dbd1SPengyu Luo return backlight_enable(ctx->backlight); 11575a5dbd1SPengyu Luo } 11675a5dbd1SPengyu Luo 11775a5dbd1SPengyu Luo static int novatek_off(struct mipi_dsi_multi_context *dsi_ctx) 11875a5dbd1SPengyu Luo { 11975a5dbd1SPengyu Luo mipi_dsi_dcs_set_display_off_multi(dsi_ctx); 12075a5dbd1SPengyu Luo mipi_dsi_dcs_enter_sleep_mode_multi(dsi_ctx); 12175a5dbd1SPengyu Luo 12275a5dbd1SPengyu Luo return dsi_ctx->accum_err; 12375a5dbd1SPengyu Luo } 12475a5dbd1SPengyu Luo 12575a5dbd1SPengyu Luo static int novatek_unprepare(struct drm_panel *panel) 12675a5dbd1SPengyu Luo { 12775a5dbd1SPengyu Luo struct novatek *ctx = to_novatek(panel); 12875a5dbd1SPengyu Luo struct mipi_dsi_device *dsi = to_primary_dsi(ctx); 12975a5dbd1SPengyu Luo struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 13075a5dbd1SPengyu Luo struct device *dev = &dsi->dev; 13175a5dbd1SPengyu Luo int ret; 13275a5dbd1SPengyu Luo 13375a5dbd1SPengyu Luo backlight_disable(ctx->backlight); 13475a5dbd1SPengyu Luo 13575a5dbd1SPengyu Luo ret = novatek_off(&dsi_ctx); 13675a5dbd1SPengyu Luo if (ret < 0) 13775a5dbd1SPengyu Luo dev_err(dev, "Failed to un-initialize panel: %d\n", ret); 13875a5dbd1SPengyu Luo 13975a5dbd1SPengyu Luo gpiod_set_value_cansleep(ctx->reset_gpio, 1); 14075a5dbd1SPengyu Luo regulator_bulk_disable(ARRAY_SIZE(novatek_supplies), ctx->supplies); 14175a5dbd1SPengyu Luo 14275a5dbd1SPengyu Luo return 0; 14375a5dbd1SPengyu Luo } 14475a5dbd1SPengyu Luo 14575a5dbd1SPengyu Luo static int novatek_get_modes(struct drm_panel *panel, 14675a5dbd1SPengyu Luo struct drm_connector *connector) 14775a5dbd1SPengyu Luo { 14875a5dbd1SPengyu Luo struct novatek *ctx = to_novatek(panel); 14975a5dbd1SPengyu Luo const struct panel_desc *desc = ctx->desc; 15075a5dbd1SPengyu Luo int i; 15175a5dbd1SPengyu Luo 15275a5dbd1SPengyu Luo for (i = 0; i < desc->num_modes; i++) { 15375a5dbd1SPengyu Luo const struct drm_display_mode *m = &desc->modes[i]; 15475a5dbd1SPengyu Luo struct drm_display_mode *mode; 15575a5dbd1SPengyu Luo 15675a5dbd1SPengyu Luo mode = drm_mode_duplicate(connector->dev, m); 15775a5dbd1SPengyu Luo if (!mode) { 15875a5dbd1SPengyu Luo dev_err(panel->dev, "failed to add mode %ux%u@%u\n", 15975a5dbd1SPengyu Luo m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); 16075a5dbd1SPengyu Luo return -ENOMEM; 16175a5dbd1SPengyu Luo } 16275a5dbd1SPengyu Luo 16375a5dbd1SPengyu Luo mode->type = DRM_MODE_TYPE_DRIVER; 16475a5dbd1SPengyu Luo if (i == 0) 16575a5dbd1SPengyu Luo mode->type |= DRM_MODE_TYPE_PREFERRED; 16675a5dbd1SPengyu Luo 16775a5dbd1SPengyu Luo drm_mode_set_name(mode); 16875a5dbd1SPengyu Luo drm_mode_probed_add(connector, mode); 16975a5dbd1SPengyu Luo } 17075a5dbd1SPengyu Luo 17175a5dbd1SPengyu Luo connector->display_info.width_mm = desc->width_mm; 17275a5dbd1SPengyu Luo connector->display_info.height_mm = desc->height_mm; 17375a5dbd1SPengyu Luo connector->display_info.bpc = desc->bpc; 17475a5dbd1SPengyu Luo 17575a5dbd1SPengyu Luo return desc->num_modes; 17675a5dbd1SPengyu Luo } 17775a5dbd1SPengyu Luo 17875a5dbd1SPengyu Luo static const struct drm_panel_funcs novatek_panel_funcs = { 17975a5dbd1SPengyu Luo .prepare = novatek_prepare, 18075a5dbd1SPengyu Luo .unprepare = novatek_unprepare, 18175a5dbd1SPengyu Luo .get_modes = novatek_get_modes, 18275a5dbd1SPengyu Luo }; 18375a5dbd1SPengyu Luo 18475a5dbd1SPengyu Luo static int novatek_bl_update_status(struct backlight_device *bl) 18575a5dbd1SPengyu Luo { 18675a5dbd1SPengyu Luo struct novatek *ctx = bl_get_data(bl); 18775a5dbd1SPengyu Luo u16 brightness = backlight_get_brightness(bl); 18875a5dbd1SPengyu Luo 18975a5dbd1SPengyu Luo return mipi_dsi_dcs_set_display_brightness_large(to_primary_dsi(ctx), 19075a5dbd1SPengyu Luo brightness); 19175a5dbd1SPengyu Luo } 19275a5dbd1SPengyu Luo 19375a5dbd1SPengyu Luo static const struct backlight_ops novatek_bl_ops = { 19475a5dbd1SPengyu Luo .update_status = novatek_bl_update_status, 19575a5dbd1SPengyu Luo }; 19675a5dbd1SPengyu Luo 19775a5dbd1SPengyu Luo static struct backlight_device *novatek_create_backlight(struct novatek *ctx) 19875a5dbd1SPengyu Luo { 19975a5dbd1SPengyu Luo struct mipi_dsi_device *dsi = to_primary_dsi(ctx); 20075a5dbd1SPengyu Luo struct device *dev = &dsi->dev; 20175a5dbd1SPengyu Luo const struct backlight_properties props = { 20275a5dbd1SPengyu Luo .type = BACKLIGHT_RAW, 20375a5dbd1SPengyu Luo .brightness = 512, 20475a5dbd1SPengyu Luo .max_brightness = 4095, 20575a5dbd1SPengyu Luo .scale = BACKLIGHT_SCALE_NON_LINEAR, 20675a5dbd1SPengyu Luo }; 20775a5dbd1SPengyu Luo 20875a5dbd1SPengyu Luo return devm_backlight_device_register(dev, dev_name(dev), dev, ctx, 20975a5dbd1SPengyu Luo &novatek_bl_ops, &props); 21075a5dbd1SPengyu Luo } 21175a5dbd1SPengyu Luo 21275a5dbd1SPengyu Luo static int csot_pp8807hb1_1_init_seq(struct mipi_dsi_multi_context *dsi_ctx) 21375a5dbd1SPengyu Luo { 21475a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x20); 21575a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); 21675a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, 0x50); 21775a5dbd1SPengyu Luo 21875a5dbd1SPengyu Luo /* cabc */ 21975a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x23); 22075a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); 22175a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x89, 0xa4); 22275a5dbd1SPengyu Luo 22375a5dbd1SPengyu Luo /* pen code */ 22475a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x26); 22575a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); 22675a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xcd, 0x8f, 0xa9, 0x00); 22775a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xce, 0x8c, 0xa9, 0x00); 22875a5dbd1SPengyu Luo 22975a5dbd1SPengyu Luo /* esd init */ 23075a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x27); 23175a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); 23275a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x79, 0x22); 23375a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd0, 0x31); 23475a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd1, 0x08, 0x08); 23575a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd2, 0x08); 23675a5dbd1SPengyu Luo 23775a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0xd0); 23875a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); 23975a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x00, 0x31); 24075a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x09, 0xee); 24175a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1c, 0x77); 24275a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1d, 0x07); 24375a5dbd1SPengyu Luo 24475a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0xe0); 24575a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); 24675a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbb, 0x00); 24775a5dbd1SPengyu Luo 24875a5dbd1SPengyu Luo /* IC transfer */ 24975a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0xf0); 25075a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); 25175a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xe6, 0x02); 25275a5dbd1SPengyu Luo 25375a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x10); 25475a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); 25575a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x3b, 25675a5dbd1SPengyu Luo 0x03, 0x4e, 0x1a, 0x04, 0x04, 0x01, 0x80, 25775a5dbd1SPengyu Luo 0x36, 0x36); 25875a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x60, 0x00); 25975a5dbd1SPengyu Luo /* enable DSC */ 26075a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x90, 0x03, 0x00); 26175a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x91, 26275a5dbd1SPengyu Luo 0xab, 0xa8, 0x00, 0x14, 0xd2, 0x00, 0x00, 26375a5dbd1SPengyu Luo 0x00, 0x01, 0xb9, 0x00, 0x06, 0x05, 0x7a, 26475a5dbd1SPengyu Luo 0x05, 0xb8); 26575a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x92, 0x10, 0xe0); 26675a5dbd1SPengyu Luo /* frame ctl */ 26775a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x91, 0x80); 26875a5dbd1SPengyu Luo mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb3, 0x40); 26975a5dbd1SPengyu Luo mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); 27075a5dbd1SPengyu Luo mipi_dsi_msleep(dsi_ctx, 105); 27175a5dbd1SPengyu Luo mipi_dsi_dcs_set_display_on_multi(dsi_ctx); 27275a5dbd1SPengyu Luo mipi_dsi_msleep(dsi_ctx, 40); 27375a5dbd1SPengyu Luo 27475a5dbd1SPengyu Luo return dsi_ctx->accum_err; 27575a5dbd1SPengyu Luo } 27675a5dbd1SPengyu Luo 27775a5dbd1SPengyu Luo static struct drm_dsc_config csot_pp8807hb1_1_dsc_cfg = { 27875a5dbd1SPengyu Luo .dsc_version_major = 1, 27975a5dbd1SPengyu Luo .dsc_version_minor = 2, 28075a5dbd1SPengyu Luo .slice_height = 20, 28175a5dbd1SPengyu Luo .slice_width = 476, 28275a5dbd1SPengyu Luo .slice_count = 2, 28375a5dbd1SPengyu Luo .bits_per_component = 10, 28475a5dbd1SPengyu Luo .bits_per_pixel = 8 << 4, 28575a5dbd1SPengyu Luo .block_pred_enable = true, 28675a5dbd1SPengyu Luo }; 28775a5dbd1SPengyu Luo 28875a5dbd1SPengyu Luo static const struct drm_display_mode csot_pp8807hb1_1_modes[] = { 28975a5dbd1SPengyu Luo /* 915552 KHz */ 29075a5dbd1SPengyu Luo { 29175a5dbd1SPengyu Luo .clock = (952 + 138 + 16 + 16) * 2 * (3040 + 26 + 4 + 330) * 120 / 1000, 29275a5dbd1SPengyu Luo .hdisplay = 952 * 2, 29375a5dbd1SPengyu Luo .hsync_start = (952 + 138) * 2, 29475a5dbd1SPengyu Luo .hsync_end = (952 + 138 + 16) * 2, 29575a5dbd1SPengyu Luo .htotal = (952 + 138 + 16 + 16) * 2, 29675a5dbd1SPengyu Luo .vdisplay = 3040, 29775a5dbd1SPengyu Luo .vsync_start = 3040 + 26, 29875a5dbd1SPengyu Luo .vsync_end = 3040 + 26 + 4, 29975a5dbd1SPengyu Luo .vtotal = 3040 + 26 + 4 + 330, 30075a5dbd1SPengyu Luo }, 30175a5dbd1SPengyu Luo { 30275a5dbd1SPengyu Luo .clock = (952 + 138 + 16 + 16) * 2 * (3040 + 3426 + 4 + 330) * 60 / 1000, 30375a5dbd1SPengyu Luo .hdisplay = 952 * 2, 30475a5dbd1SPengyu Luo .hsync_start = (952 + 138) * 2, 30575a5dbd1SPengyu Luo .hsync_end = (952 + 138 + 16) * 2, 30675a5dbd1SPengyu Luo .htotal = (952 + 138 + 16 + 16) * 2, 30775a5dbd1SPengyu Luo .vdisplay = 3040, 30875a5dbd1SPengyu Luo .vsync_start = 3040 + 3426, 30975a5dbd1SPengyu Luo .vsync_end = 3040 + 3426 + 4, 31075a5dbd1SPengyu Luo .vtotal = 3040 + 3426 + 4 + 330, 31175a5dbd1SPengyu Luo }, 31275a5dbd1SPengyu Luo { 31375a5dbd1SPengyu Luo .clock = (952 + 138 + 16 + 16) * 2 * (3040 + 10226 + 4 + 330) * 30 / 1000, 31475a5dbd1SPengyu Luo .hdisplay = 952 * 2, 31575a5dbd1SPengyu Luo .hsync_start = (952 + 138) * 2, 31675a5dbd1SPengyu Luo .hsync_end = (952 + 138 + 16) * 2, 31775a5dbd1SPengyu Luo .htotal = (952 + 138 + 16 + 16) * 2, 31875a5dbd1SPengyu Luo .vdisplay = 3040, 31975a5dbd1SPengyu Luo .vsync_start = 3040 + 10226, 32075a5dbd1SPengyu Luo .vsync_end = 3040 + 10226 + 4, 32175a5dbd1SPengyu Luo .vtotal = 3040 + 10226 + 4 + 330, 32275a5dbd1SPengyu Luo }, 32375a5dbd1SPengyu Luo /* 1064606.4 KHz */ 32475a5dbd1SPengyu Luo { 32575a5dbd1SPengyu Luo .clock = (952 + 50 + 16 + 16) * 2 * (3040 + 26 + 4 + 50) * 165 / 1000, 32675a5dbd1SPengyu Luo .hdisplay = 952 * 2, 32775a5dbd1SPengyu Luo .hsync_start = (952 + 50) * 2, 32875a5dbd1SPengyu Luo .hsync_end = (952 + 50 + 16) * 2, 32975a5dbd1SPengyu Luo .htotal = (952 + 50 + 16 + 16) * 2, 33075a5dbd1SPengyu Luo .vdisplay = 3040, 33175a5dbd1SPengyu Luo .vsync_start = 3040 + 26, 33275a5dbd1SPengyu Luo .vsync_end = 3040 + 26 + 4, 33375a5dbd1SPengyu Luo .vtotal = 3040 + 26 + 4 + 50, 33475a5dbd1SPengyu Luo }, 33575a5dbd1SPengyu Luo { 33675a5dbd1SPengyu Luo .clock = (952 + 50 + 16 + 16) * 2 * (3040 + 481 + 4 + 50) * 144 / 1000, 33775a5dbd1SPengyu Luo .hdisplay = 952 * 2, 33875a5dbd1SPengyu Luo .hsync_start = (952 + 50) * 2, 33975a5dbd1SPengyu Luo .hsync_end = (952 + 50 + 16) * 2, 34075a5dbd1SPengyu Luo .htotal = (952 + 50 + 16 + 16) * 2, 34175a5dbd1SPengyu Luo .vdisplay = 3040, 34275a5dbd1SPengyu Luo .vsync_start = 3040 + 481, 34375a5dbd1SPengyu Luo .vsync_end = 3040 + 481 + 4, 34475a5dbd1SPengyu Luo .vtotal = 3040 + 481 + 4 + 50, 34575a5dbd1SPengyu Luo }, 34675a5dbd1SPengyu Luo /* 737942.4 KHz */ 34775a5dbd1SPengyu Luo { 34875a5dbd1SPengyu Luo .clock = (952 + 330 + 16 + 16) * 2 * (3040 + 26 + 4 + 50) * 90 / 1000, 34975a5dbd1SPengyu Luo .hdisplay = 952 * 2, 35075a5dbd1SPengyu Luo .hsync_start = (952 + 330) * 2, 35175a5dbd1SPengyu Luo .hsync_end = (952 + 330 + 16) * 2, 35275a5dbd1SPengyu Luo .htotal = (952 + 330 + 16 + 16) * 2, 35375a5dbd1SPengyu Luo .vdisplay = 3040, 35475a5dbd1SPengyu Luo .vsync_start = 3040 + 26, 35575a5dbd1SPengyu Luo .vsync_end = 3040 + 26 + 4, 35675a5dbd1SPengyu Luo .vtotal = 3040 + 26 + 4 + 50, 35775a5dbd1SPengyu Luo }, 35875a5dbd1SPengyu Luo }; 35975a5dbd1SPengyu Luo 36075a5dbd1SPengyu Luo static int novatek_probe(struct mipi_dsi_device *dsi) 36175a5dbd1SPengyu Luo { 36275a5dbd1SPengyu Luo struct mipi_dsi_device_info dsi_info = {"dsi-secondary", 0, NULL}; 36375a5dbd1SPengyu Luo struct mipi_dsi_host *dsi1_host; 36475a5dbd1SPengyu Luo struct device *dev = &dsi->dev; 36575a5dbd1SPengyu Luo const struct panel_desc *desc; 36675a5dbd1SPengyu Luo struct device_node *dsi1; 36775a5dbd1SPengyu Luo struct novatek *ctx; 36875a5dbd1SPengyu Luo int num_dsi = 1; 36975a5dbd1SPengyu Luo int ret, i; 37075a5dbd1SPengyu Luo 37175a5dbd1SPengyu Luo ctx = devm_drm_panel_alloc(dev, struct novatek, panel, 37275a5dbd1SPengyu Luo &novatek_panel_funcs, 37375a5dbd1SPengyu Luo DRM_MODE_CONNECTOR_DSI); 37475a5dbd1SPengyu Luo if (IS_ERR(ctx)) 37575a5dbd1SPengyu Luo return PTR_ERR(ctx); 37675a5dbd1SPengyu Luo 37775a5dbd1SPengyu Luo ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(novatek_supplies), 37875a5dbd1SPengyu Luo novatek_supplies, &ctx->supplies); 37975a5dbd1SPengyu Luo if (ret < 0) 38075a5dbd1SPengyu Luo return ret; 38175a5dbd1SPengyu Luo 38275a5dbd1SPengyu Luo ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 38375a5dbd1SPengyu Luo if (IS_ERR(ctx->reset_gpio)) 38475a5dbd1SPengyu Luo return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 38575a5dbd1SPengyu Luo "Failed to get reset-gpios\n"); 38675a5dbd1SPengyu Luo 38775a5dbd1SPengyu Luo desc = of_device_get_match_data(dev); 38875a5dbd1SPengyu Luo if (!desc) 38975a5dbd1SPengyu Luo return -ENODEV; 39075a5dbd1SPengyu Luo ctx->desc = desc; 39175a5dbd1SPengyu Luo ctx->dsc = *desc->dsc_cfg; 39275a5dbd1SPengyu Luo 39375a5dbd1SPengyu Luo if (desc->is_dual_dsi) { 39475a5dbd1SPengyu Luo num_dsi = 2; 39575a5dbd1SPengyu Luo dsi1 = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); 39675a5dbd1SPengyu Luo if (!dsi1) { 39775a5dbd1SPengyu Luo dev_err(dev, "cannot get secondary DSI node.\n"); 39875a5dbd1SPengyu Luo return -ENODEV; 39975a5dbd1SPengyu Luo } 40075a5dbd1SPengyu Luo 40175a5dbd1SPengyu Luo dsi1_host = of_find_mipi_dsi_host_by_node(dsi1); 40275a5dbd1SPengyu Luo of_node_put(dsi1); 40375a5dbd1SPengyu Luo if (!dsi1_host) 40475a5dbd1SPengyu Luo return dev_err_probe(dev, -EPROBE_DEFER, 40575a5dbd1SPengyu Luo "cannot get secondary DSI host\n"); 40675a5dbd1SPengyu Luo 40775a5dbd1SPengyu Luo ctx->dsi[1] = devm_mipi_dsi_device_register_full(dev, dsi1_host, 40875a5dbd1SPengyu Luo &dsi_info); 40975a5dbd1SPengyu Luo if (IS_ERR(ctx->dsi[1])) { 41075a5dbd1SPengyu Luo dev_err(dev, "cannot get secondary DSI device\n"); 41175a5dbd1SPengyu Luo return PTR_ERR(ctx->dsi[1]); 41275a5dbd1SPengyu Luo } 41375a5dbd1SPengyu Luo 41475a5dbd1SPengyu Luo mipi_dsi_set_drvdata(ctx->dsi[1], ctx); 41575a5dbd1SPengyu Luo } 41675a5dbd1SPengyu Luo 41775a5dbd1SPengyu Luo ctx->dsi[0] = dsi; 41875a5dbd1SPengyu Luo mipi_dsi_set_drvdata(dsi, ctx); 41975a5dbd1SPengyu Luo 42075a5dbd1SPengyu Luo ctx->panel.prepare_prev_first = true; 42175a5dbd1SPengyu Luo 42275a5dbd1SPengyu Luo ret = devm_drm_panel_add(dev, &ctx->panel); 42375a5dbd1SPengyu Luo if (ret < 0) 42475a5dbd1SPengyu Luo return dev_err_probe(dev, ret, "failed to add panel\n"); 42575a5dbd1SPengyu Luo 42675a5dbd1SPengyu Luo for (i = 0; i < num_dsi; i++) { 42775a5dbd1SPengyu Luo ctx->dsi[i]->lanes = desc->lanes; 42875a5dbd1SPengyu Luo ctx->dsi[i]->format = desc->format; 42975a5dbd1SPengyu Luo ctx->dsi[i]->mode_flags = desc->mode_flags; 43075a5dbd1SPengyu Luo ctx->dsi[i]->dsc = &ctx->dsc; 43175a5dbd1SPengyu Luo ret = devm_mipi_dsi_attach(dev, ctx->dsi[i]); 432*4085da1eSDavid Carlier if (ret < 0) 43375a5dbd1SPengyu Luo return dev_err_probe(dev, ret, 43475a5dbd1SPengyu Luo "Failed to attach to DSI host\n"); 43575a5dbd1SPengyu Luo } 43675a5dbd1SPengyu Luo 43775a5dbd1SPengyu Luo if (desc->has_dcs_backlight) { 43875a5dbd1SPengyu Luo ctx->backlight = novatek_create_backlight(ctx); 43975a5dbd1SPengyu Luo if (IS_ERR(ctx->backlight)) 44075a5dbd1SPengyu Luo return dev_err_probe(dev, PTR_ERR(ctx->backlight), 44175a5dbd1SPengyu Luo "Failed to create backlight\n"); 44275a5dbd1SPengyu Luo } else { 44375a5dbd1SPengyu Luo ret = drm_panel_of_backlight(&ctx->panel); 44475a5dbd1SPengyu Luo if (ret) 44575a5dbd1SPengyu Luo return dev_err_probe(dev, ret, "Failed to get backlight\n"); 44675a5dbd1SPengyu Luo } 44775a5dbd1SPengyu Luo 44875a5dbd1SPengyu Luo return 0; 44975a5dbd1SPengyu Luo } 45075a5dbd1SPengyu Luo 45175a5dbd1SPengyu Luo /* Model name: CSOT PP8807HB1-1 */ 45275a5dbd1SPengyu Luo static const struct panel_desc csot_pp8807hb1_1_desc = { 45375a5dbd1SPengyu Luo .width_mm = 118, 45475a5dbd1SPengyu Luo .height_mm = 190, 45575a5dbd1SPengyu Luo .bpc = 10, /* set this to 8 with RGB888 format to support 8-bit mode */ 45675a5dbd1SPengyu Luo .lanes = 3, 45775a5dbd1SPengyu Luo .format = MIPI_DSI_FMT_RGB101010, 45875a5dbd1SPengyu Luo .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS | 45975a5dbd1SPengyu Luo MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_BURST | 46075a5dbd1SPengyu Luo MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT, 46175a5dbd1SPengyu Luo .dsc_cfg = &csot_pp8807hb1_1_dsc_cfg, 46275a5dbd1SPengyu Luo .modes = csot_pp8807hb1_1_modes, 46375a5dbd1SPengyu Luo .num_modes = ARRAY_SIZE(csot_pp8807hb1_1_modes), 46475a5dbd1SPengyu Luo .init_sequence = csot_pp8807hb1_1_init_seq, 46575a5dbd1SPengyu Luo .is_dual_dsi = true, 46675a5dbd1SPengyu Luo .has_dcs_backlight = false, 46775a5dbd1SPengyu Luo }; 46875a5dbd1SPengyu Luo 46975a5dbd1SPengyu Luo static const struct of_device_id novatek_of_match[] = { 47075a5dbd1SPengyu Luo { .compatible = "csot,pp8807hb1-1", .data = &csot_pp8807hb1_1_desc }, 47175a5dbd1SPengyu Luo { /* sentinel */ } 47275a5dbd1SPengyu Luo }; 47375a5dbd1SPengyu Luo MODULE_DEVICE_TABLE(of, novatek_of_match); 47475a5dbd1SPengyu Luo 47575a5dbd1SPengyu Luo static struct mipi_dsi_driver novatek_driver = { 47675a5dbd1SPengyu Luo .probe = novatek_probe, 47775a5dbd1SPengyu Luo .driver = { 47875a5dbd1SPengyu Luo .name = "panel-novatek-nt36536", 47975a5dbd1SPengyu Luo .of_match_table = novatek_of_match, 48075a5dbd1SPengyu Luo }, 48175a5dbd1SPengyu Luo }; 48275a5dbd1SPengyu Luo module_mipi_dsi_driver(novatek_driver); 48375a5dbd1SPengyu Luo 48475a5dbd1SPengyu Luo MODULE_AUTHOR("Pengyu Luo <mitltlatltl@gmail.com>"); 48575a5dbd1SPengyu Luo MODULE_DESCRIPTION("Novatek NT36536 DriverIC panels driver"); 48675a5dbd1SPengyu Luo MODULE_LICENSE("GPL"); 487