xref: /linux/drivers/gpu/drm/panel/panel-novatek-nt36536.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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