162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/delay.h>
762306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/of.h>
1062306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <video/mipi_display.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <drm/drm_crtc.h>
1562306a36Sopenharmony_ci#include <drm/drm_device.h>
1662306a36Sopenharmony_ci#include <drm/drm_mipi_dsi.h>
1762306a36Sopenharmony_ci#include <drm/drm_modes.h>
1862306a36Sopenharmony_ci#include <drm/drm_panel.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct kingdisplay_panel {
2162306a36Sopenharmony_ci	struct drm_panel base;
2262306a36Sopenharmony_ci	struct mipi_dsi_device *link;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	struct regulator *supply;
2562306a36Sopenharmony_ci	struct gpio_desc *enable_gpio;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	bool prepared;
2862306a36Sopenharmony_ci	bool enabled;
2962306a36Sopenharmony_ci};
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct kingdisplay_panel_cmd {
3262306a36Sopenharmony_ci	char cmd;
3362306a36Sopenharmony_ci	char data;
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/*
3762306a36Sopenharmony_ci * According to the discussion on
3862306a36Sopenharmony_ci * https://review.coreboot.org/#/c/coreboot/+/22472/
3962306a36Sopenharmony_ci * the panel init array is not part of the panels datasheet but instead
4062306a36Sopenharmony_ci * just came in this form from the panel vendor.
4162306a36Sopenharmony_ci */
4262306a36Sopenharmony_cistatic const struct kingdisplay_panel_cmd init_code[] = {
4362306a36Sopenharmony_ci	/* voltage setting */
4462306a36Sopenharmony_ci	{ 0xB0, 0x00 },
4562306a36Sopenharmony_ci	{ 0xB2, 0x02 },
4662306a36Sopenharmony_ci	{ 0xB3, 0x11 },
4762306a36Sopenharmony_ci	{ 0xB4, 0x00 },
4862306a36Sopenharmony_ci	{ 0xB6, 0x80 },
4962306a36Sopenharmony_ci	/* VCOM disable */
5062306a36Sopenharmony_ci	{ 0xB7, 0x02 },
5162306a36Sopenharmony_ci	{ 0xB8, 0x80 },
5262306a36Sopenharmony_ci	{ 0xBA, 0x43 },
5362306a36Sopenharmony_ci	/* VCOM setting */
5462306a36Sopenharmony_ci	{ 0xBB, 0x53 },
5562306a36Sopenharmony_ci	/* VSP setting */
5662306a36Sopenharmony_ci	{ 0xBC, 0x0A },
5762306a36Sopenharmony_ci	/* VSN setting */
5862306a36Sopenharmony_ci	{ 0xBD, 0x4A },
5962306a36Sopenharmony_ci	/* VGH setting */
6062306a36Sopenharmony_ci	{ 0xBE, 0x2F },
6162306a36Sopenharmony_ci	/* VGL setting */
6262306a36Sopenharmony_ci	{ 0xBF, 0x1A },
6362306a36Sopenharmony_ci	{ 0xF0, 0x39 },
6462306a36Sopenharmony_ci	{ 0xF1, 0x22 },
6562306a36Sopenharmony_ci	/* Gamma setting */
6662306a36Sopenharmony_ci	{ 0xB0, 0x02 },
6762306a36Sopenharmony_ci	{ 0xC0, 0x00 },
6862306a36Sopenharmony_ci	{ 0xC1, 0x01 },
6962306a36Sopenharmony_ci	{ 0xC2, 0x0B },
7062306a36Sopenharmony_ci	{ 0xC3, 0x15 },
7162306a36Sopenharmony_ci	{ 0xC4, 0x22 },
7262306a36Sopenharmony_ci	{ 0xC5, 0x11 },
7362306a36Sopenharmony_ci	{ 0xC6, 0x15 },
7462306a36Sopenharmony_ci	{ 0xC7, 0x19 },
7562306a36Sopenharmony_ci	{ 0xC8, 0x1A },
7662306a36Sopenharmony_ci	{ 0xC9, 0x16 },
7762306a36Sopenharmony_ci	{ 0xCA, 0x18 },
7862306a36Sopenharmony_ci	{ 0xCB, 0x13 },
7962306a36Sopenharmony_ci	{ 0xCC, 0x18 },
8062306a36Sopenharmony_ci	{ 0xCD, 0x13 },
8162306a36Sopenharmony_ci	{ 0xCE, 0x1C },
8262306a36Sopenharmony_ci	{ 0xCF, 0x19 },
8362306a36Sopenharmony_ci	{ 0xD0, 0x21 },
8462306a36Sopenharmony_ci	{ 0xD1, 0x2C },
8562306a36Sopenharmony_ci	{ 0xD2, 0x2F },
8662306a36Sopenharmony_ci	{ 0xD3, 0x30 },
8762306a36Sopenharmony_ci	{ 0xD4, 0x19 },
8862306a36Sopenharmony_ci	{ 0xD5, 0x1F },
8962306a36Sopenharmony_ci	{ 0xD6, 0x00 },
9062306a36Sopenharmony_ci	{ 0xD7, 0x01 },
9162306a36Sopenharmony_ci	{ 0xD8, 0x0B },
9262306a36Sopenharmony_ci	{ 0xD9, 0x15 },
9362306a36Sopenharmony_ci	{ 0xDA, 0x22 },
9462306a36Sopenharmony_ci	{ 0xDB, 0x11 },
9562306a36Sopenharmony_ci	{ 0xDC, 0x15 },
9662306a36Sopenharmony_ci	{ 0xDD, 0x19 },
9762306a36Sopenharmony_ci	{ 0xDE, 0x1A },
9862306a36Sopenharmony_ci	{ 0xDF, 0x16 },
9962306a36Sopenharmony_ci	{ 0xE0, 0x18 },
10062306a36Sopenharmony_ci	{ 0xE1, 0x13 },
10162306a36Sopenharmony_ci	{ 0xE2, 0x18 },
10262306a36Sopenharmony_ci	{ 0xE3, 0x13 },
10362306a36Sopenharmony_ci	{ 0xE4, 0x1C },
10462306a36Sopenharmony_ci	{ 0xE5, 0x19 },
10562306a36Sopenharmony_ci	{ 0xE6, 0x21 },
10662306a36Sopenharmony_ci	{ 0xE7, 0x2C },
10762306a36Sopenharmony_ci	{ 0xE8, 0x2F },
10862306a36Sopenharmony_ci	{ 0xE9, 0x30 },
10962306a36Sopenharmony_ci	{ 0xEA, 0x19 },
11062306a36Sopenharmony_ci	{ 0xEB, 0x1F },
11162306a36Sopenharmony_ci	/* GOA MUX setting */
11262306a36Sopenharmony_ci	{ 0xB0, 0x01 },
11362306a36Sopenharmony_ci	{ 0xC0, 0x10 },
11462306a36Sopenharmony_ci	{ 0xC1, 0x0F },
11562306a36Sopenharmony_ci	{ 0xC2, 0x0E },
11662306a36Sopenharmony_ci	{ 0xC3, 0x0D },
11762306a36Sopenharmony_ci	{ 0xC4, 0x0C },
11862306a36Sopenharmony_ci	{ 0xC5, 0x0B },
11962306a36Sopenharmony_ci	{ 0xC6, 0x0A },
12062306a36Sopenharmony_ci	{ 0xC7, 0x09 },
12162306a36Sopenharmony_ci	{ 0xC8, 0x08 },
12262306a36Sopenharmony_ci	{ 0xC9, 0x07 },
12362306a36Sopenharmony_ci	{ 0xCA, 0x06 },
12462306a36Sopenharmony_ci	{ 0xCB, 0x05 },
12562306a36Sopenharmony_ci	{ 0xCC, 0x00 },
12662306a36Sopenharmony_ci	{ 0xCD, 0x01 },
12762306a36Sopenharmony_ci	{ 0xCE, 0x02 },
12862306a36Sopenharmony_ci	{ 0xCF, 0x03 },
12962306a36Sopenharmony_ci	{ 0xD0, 0x04 },
13062306a36Sopenharmony_ci	{ 0xD6, 0x10 },
13162306a36Sopenharmony_ci	{ 0xD7, 0x0F },
13262306a36Sopenharmony_ci	{ 0xD8, 0x0E },
13362306a36Sopenharmony_ci	{ 0xD9, 0x0D },
13462306a36Sopenharmony_ci	{ 0xDA, 0x0C },
13562306a36Sopenharmony_ci	{ 0xDB, 0x0B },
13662306a36Sopenharmony_ci	{ 0xDC, 0x0A },
13762306a36Sopenharmony_ci	{ 0xDD, 0x09 },
13862306a36Sopenharmony_ci	{ 0xDE, 0x08 },
13962306a36Sopenharmony_ci	{ 0xDF, 0x07 },
14062306a36Sopenharmony_ci	{ 0xE0, 0x06 },
14162306a36Sopenharmony_ci	{ 0xE1, 0x05 },
14262306a36Sopenharmony_ci	{ 0xE2, 0x00 },
14362306a36Sopenharmony_ci	{ 0xE3, 0x01 },
14462306a36Sopenharmony_ci	{ 0xE4, 0x02 },
14562306a36Sopenharmony_ci	{ 0xE5, 0x03 },
14662306a36Sopenharmony_ci	{ 0xE6, 0x04 },
14762306a36Sopenharmony_ci	{ 0xE7, 0x00 },
14862306a36Sopenharmony_ci	{ 0xEC, 0xC0 },
14962306a36Sopenharmony_ci	/* GOA timing setting */
15062306a36Sopenharmony_ci	{ 0xB0, 0x03 },
15162306a36Sopenharmony_ci	{ 0xC0, 0x01 },
15262306a36Sopenharmony_ci	{ 0xC2, 0x6F },
15362306a36Sopenharmony_ci	{ 0xC3, 0x6F },
15462306a36Sopenharmony_ci	{ 0xC5, 0x36 },
15562306a36Sopenharmony_ci	{ 0xC8, 0x08 },
15662306a36Sopenharmony_ci	{ 0xC9, 0x04 },
15762306a36Sopenharmony_ci	{ 0xCA, 0x41 },
15862306a36Sopenharmony_ci	{ 0xCC, 0x43 },
15962306a36Sopenharmony_ci	{ 0xCF, 0x60 },
16062306a36Sopenharmony_ci	{ 0xD2, 0x04 },
16162306a36Sopenharmony_ci	{ 0xD3, 0x04 },
16262306a36Sopenharmony_ci	{ 0xD4, 0x03 },
16362306a36Sopenharmony_ci	{ 0xD5, 0x02 },
16462306a36Sopenharmony_ci	{ 0xD6, 0x01 },
16562306a36Sopenharmony_ci	{ 0xD7, 0x00 },
16662306a36Sopenharmony_ci	{ 0xDB, 0x01 },
16762306a36Sopenharmony_ci	{ 0xDE, 0x36 },
16862306a36Sopenharmony_ci	{ 0xE6, 0x6F },
16962306a36Sopenharmony_ci	{ 0xE7, 0x6F },
17062306a36Sopenharmony_ci	/* GOE setting */
17162306a36Sopenharmony_ci	{ 0xB0, 0x06 },
17262306a36Sopenharmony_ci	{ 0xB8, 0xA5 },
17362306a36Sopenharmony_ci	{ 0xC0, 0xA5 },
17462306a36Sopenharmony_ci	{ 0xD5, 0x3F },
17562306a36Sopenharmony_ci};
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic inline
17862306a36Sopenharmony_cistruct kingdisplay_panel *to_kingdisplay_panel(struct drm_panel *panel)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	return container_of(panel, struct kingdisplay_panel, base);
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int kingdisplay_panel_disable(struct drm_panel *panel)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel);
18662306a36Sopenharmony_ci	int err;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	if (!kingdisplay->enabled)
18962306a36Sopenharmony_ci		return 0;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	err = mipi_dsi_dcs_set_display_off(kingdisplay->link);
19262306a36Sopenharmony_ci	if (err < 0)
19362306a36Sopenharmony_ci		dev_err(panel->dev, "failed to set display off: %d\n", err);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	kingdisplay->enabled = false;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return 0;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic int kingdisplay_panel_unprepare(struct drm_panel *panel)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel);
20362306a36Sopenharmony_ci	int err;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	if (!kingdisplay->prepared)
20662306a36Sopenharmony_ci		return 0;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	err = mipi_dsi_dcs_enter_sleep_mode(kingdisplay->link);
20962306a36Sopenharmony_ci	if (err < 0) {
21062306a36Sopenharmony_ci		dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
21162306a36Sopenharmony_ci		return err;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	/* T15: 120ms */
21562306a36Sopenharmony_ci	msleep(120);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	gpiod_set_value_cansleep(kingdisplay->enable_gpio, 0);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	err = regulator_disable(kingdisplay->supply);
22062306a36Sopenharmony_ci	if (err < 0)
22162306a36Sopenharmony_ci		return err;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	kingdisplay->prepared = false;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	return 0;
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic int kingdisplay_panel_prepare(struct drm_panel *panel)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel);
23162306a36Sopenharmony_ci	int err, regulator_err;
23262306a36Sopenharmony_ci	unsigned int i;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (kingdisplay->prepared)
23562306a36Sopenharmony_ci		return 0;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	gpiod_set_value_cansleep(kingdisplay->enable_gpio, 0);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	err = regulator_enable(kingdisplay->supply);
24062306a36Sopenharmony_ci	if (err < 0)
24162306a36Sopenharmony_ci		return err;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	/* T2: 15ms */
24462306a36Sopenharmony_ci	usleep_range(15000, 16000);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	gpiod_set_value_cansleep(kingdisplay->enable_gpio, 1);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	/* T4: 15ms */
24962306a36Sopenharmony_ci	usleep_range(15000, 16000);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(init_code); i++) {
25262306a36Sopenharmony_ci		err = mipi_dsi_generic_write(kingdisplay->link, &init_code[i],
25362306a36Sopenharmony_ci					sizeof(struct kingdisplay_panel_cmd));
25462306a36Sopenharmony_ci		if (err < 0) {
25562306a36Sopenharmony_ci			dev_err(panel->dev, "failed write init cmds: %d\n", err);
25662306a36Sopenharmony_ci			goto poweroff;
25762306a36Sopenharmony_ci		}
25862306a36Sopenharmony_ci	}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	err = mipi_dsi_dcs_exit_sleep_mode(kingdisplay->link);
26162306a36Sopenharmony_ci	if (err < 0) {
26262306a36Sopenharmony_ci		dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
26362306a36Sopenharmony_ci		goto poweroff;
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	/* T6: 120ms */
26762306a36Sopenharmony_ci	msleep(120);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	err = mipi_dsi_dcs_set_display_on(kingdisplay->link);
27062306a36Sopenharmony_ci	if (err < 0) {
27162306a36Sopenharmony_ci		dev_err(panel->dev, "failed to set display on: %d\n", err);
27262306a36Sopenharmony_ci		goto poweroff;
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	/* T7: 10ms */
27662306a36Sopenharmony_ci	usleep_range(10000, 11000);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	kingdisplay->prepared = true;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	return 0;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_cipoweroff:
28362306a36Sopenharmony_ci	gpiod_set_value_cansleep(kingdisplay->enable_gpio, 0);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	regulator_err = regulator_disable(kingdisplay->supply);
28662306a36Sopenharmony_ci	if (regulator_err)
28762306a36Sopenharmony_ci		dev_err(panel->dev, "failed to disable regulator: %d\n", regulator_err);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	return err;
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cistatic int kingdisplay_panel_enable(struct drm_panel *panel)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	if (kingdisplay->enabled)
29762306a36Sopenharmony_ci		return 0;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	kingdisplay->enabled = true;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	return 0;
30262306a36Sopenharmony_ci}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cistatic const struct drm_display_mode default_mode = {
30562306a36Sopenharmony_ci	.clock = 229000,
30662306a36Sopenharmony_ci	.hdisplay = 1536,
30762306a36Sopenharmony_ci	.hsync_start = 1536 + 100,
30862306a36Sopenharmony_ci	.hsync_end = 1536 + 100 + 24,
30962306a36Sopenharmony_ci	.htotal = 1536 + 100 + 24 + 100,
31062306a36Sopenharmony_ci	.vdisplay = 2048,
31162306a36Sopenharmony_ci	.vsync_start = 2048 + 95,
31262306a36Sopenharmony_ci	.vsync_end = 2048 + 95 + 2,
31362306a36Sopenharmony_ci	.vtotal = 2048 + 95 + 2 + 23,
31462306a36Sopenharmony_ci};
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_cistatic int kingdisplay_panel_get_modes(struct drm_panel *panel,
31762306a36Sopenharmony_ci				       struct drm_connector *connector)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	struct drm_display_mode *mode;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	mode = drm_mode_duplicate(connector->dev, &default_mode);
32262306a36Sopenharmony_ci	if (!mode) {
32362306a36Sopenharmony_ci		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
32462306a36Sopenharmony_ci			default_mode.hdisplay, default_mode.vdisplay,
32562306a36Sopenharmony_ci			drm_mode_vrefresh(&default_mode));
32662306a36Sopenharmony_ci		return -ENOMEM;
32762306a36Sopenharmony_ci	}
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	drm_mode_set_name(mode);
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	drm_mode_probed_add(connector, mode);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	connector->display_info.width_mm = 147;
33462306a36Sopenharmony_ci	connector->display_info.height_mm = 196;
33562306a36Sopenharmony_ci	connector->display_info.bpc = 8;
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	return 1;
33862306a36Sopenharmony_ci}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_cistatic const struct drm_panel_funcs kingdisplay_panel_funcs = {
34162306a36Sopenharmony_ci	.disable = kingdisplay_panel_disable,
34262306a36Sopenharmony_ci	.unprepare = kingdisplay_panel_unprepare,
34362306a36Sopenharmony_ci	.prepare = kingdisplay_panel_prepare,
34462306a36Sopenharmony_ci	.enable = kingdisplay_panel_enable,
34562306a36Sopenharmony_ci	.get_modes = kingdisplay_panel_get_modes,
34662306a36Sopenharmony_ci};
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_cistatic const struct of_device_id kingdisplay_of_match[] = {
34962306a36Sopenharmony_ci	{ .compatible = "kingdisplay,kd097d04", },
35062306a36Sopenharmony_ci	{ }
35162306a36Sopenharmony_ci};
35262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, kingdisplay_of_match);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_cistatic int kingdisplay_panel_add(struct kingdisplay_panel *kingdisplay)
35562306a36Sopenharmony_ci{
35662306a36Sopenharmony_ci	struct device *dev = &kingdisplay->link->dev;
35762306a36Sopenharmony_ci	int err;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	kingdisplay->supply = devm_regulator_get(dev, "power");
36062306a36Sopenharmony_ci	if (IS_ERR(kingdisplay->supply))
36162306a36Sopenharmony_ci		return PTR_ERR(kingdisplay->supply);
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	kingdisplay->enable_gpio = devm_gpiod_get_optional(dev, "enable",
36462306a36Sopenharmony_ci							   GPIOD_OUT_HIGH);
36562306a36Sopenharmony_ci	if (IS_ERR(kingdisplay->enable_gpio)) {
36662306a36Sopenharmony_ci		err = PTR_ERR(kingdisplay->enable_gpio);
36762306a36Sopenharmony_ci		dev_dbg(dev, "failed to get enable gpio: %d\n", err);
36862306a36Sopenharmony_ci		kingdisplay->enable_gpio = NULL;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	drm_panel_init(&kingdisplay->base, &kingdisplay->link->dev,
37262306a36Sopenharmony_ci		       &kingdisplay_panel_funcs, DRM_MODE_CONNECTOR_DSI);
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	err = drm_panel_of_backlight(&kingdisplay->base);
37562306a36Sopenharmony_ci	if (err)
37662306a36Sopenharmony_ci		return err;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	drm_panel_add(&kingdisplay->base);
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	return 0;
38162306a36Sopenharmony_ci}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_cistatic void kingdisplay_panel_del(struct kingdisplay_panel *kingdisplay)
38462306a36Sopenharmony_ci{
38562306a36Sopenharmony_ci	drm_panel_remove(&kingdisplay->base);
38662306a36Sopenharmony_ci}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_cistatic int kingdisplay_panel_probe(struct mipi_dsi_device *dsi)
38962306a36Sopenharmony_ci{
39062306a36Sopenharmony_ci	struct kingdisplay_panel *kingdisplay;
39162306a36Sopenharmony_ci	int err;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	dsi->lanes = 4;
39462306a36Sopenharmony_ci	dsi->format = MIPI_DSI_FMT_RGB888;
39562306a36Sopenharmony_ci	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
39662306a36Sopenharmony_ci			  MIPI_DSI_MODE_LPM;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	kingdisplay = devm_kzalloc(&dsi->dev, sizeof(*kingdisplay), GFP_KERNEL);
39962306a36Sopenharmony_ci	if (!kingdisplay)
40062306a36Sopenharmony_ci		return -ENOMEM;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	mipi_dsi_set_drvdata(dsi, kingdisplay);
40362306a36Sopenharmony_ci	kingdisplay->link = dsi;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	err = kingdisplay_panel_add(kingdisplay);
40662306a36Sopenharmony_ci	if (err < 0)
40762306a36Sopenharmony_ci		return err;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	err = mipi_dsi_attach(dsi);
41062306a36Sopenharmony_ci	if (err < 0) {
41162306a36Sopenharmony_ci		kingdisplay_panel_del(kingdisplay);
41262306a36Sopenharmony_ci		return err;
41362306a36Sopenharmony_ci	}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	return 0;
41662306a36Sopenharmony_ci}
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_cistatic void kingdisplay_panel_remove(struct mipi_dsi_device *dsi)
41962306a36Sopenharmony_ci{
42062306a36Sopenharmony_ci	struct kingdisplay_panel *kingdisplay = mipi_dsi_get_drvdata(dsi);
42162306a36Sopenharmony_ci	int err;
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	err = drm_panel_unprepare(&kingdisplay->base);
42462306a36Sopenharmony_ci	if (err < 0)
42562306a36Sopenharmony_ci		dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	err = drm_panel_disable(&kingdisplay->base);
42862306a36Sopenharmony_ci	if (err < 0)
42962306a36Sopenharmony_ci		dev_err(&dsi->dev, "failed to disable panel: %d\n", err);
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	err = mipi_dsi_detach(dsi);
43262306a36Sopenharmony_ci	if (err < 0)
43362306a36Sopenharmony_ci		dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	kingdisplay_panel_del(kingdisplay);
43662306a36Sopenharmony_ci}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_cistatic void kingdisplay_panel_shutdown(struct mipi_dsi_device *dsi)
43962306a36Sopenharmony_ci{
44062306a36Sopenharmony_ci	struct kingdisplay_panel *kingdisplay = mipi_dsi_get_drvdata(dsi);
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	drm_panel_unprepare(&kingdisplay->base);
44362306a36Sopenharmony_ci	drm_panel_disable(&kingdisplay->base);
44462306a36Sopenharmony_ci}
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_cistatic struct mipi_dsi_driver kingdisplay_panel_driver = {
44762306a36Sopenharmony_ci	.driver = {
44862306a36Sopenharmony_ci		.name = "panel-kingdisplay-kd097d04",
44962306a36Sopenharmony_ci		.of_match_table = kingdisplay_of_match,
45062306a36Sopenharmony_ci	},
45162306a36Sopenharmony_ci	.probe = kingdisplay_panel_probe,
45262306a36Sopenharmony_ci	.remove = kingdisplay_panel_remove,
45362306a36Sopenharmony_ci	.shutdown = kingdisplay_panel_shutdown,
45462306a36Sopenharmony_ci};
45562306a36Sopenharmony_cimodule_mipi_dsi_driver(kingdisplay_panel_driver);
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ciMODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
45862306a36Sopenharmony_ciMODULE_AUTHOR("Nickey Yang <nickey.yang@rock-chips.com>");
45962306a36Sopenharmony_ciMODULE_DESCRIPTION("kingdisplay KD097D04 panel driver");
46062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
461