xref: /openbmc/linux/drivers/gpu/drm/panel/panel-innolux-p079zca.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
214c8f2e9SChris Zhong /*
314c8f2e9SChris Zhong  * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
414c8f2e9SChris Zhong  */
514c8f2e9SChris Zhong 
6cb23eae3SSam Ravnborg #include <linux/delay.h>
714c8f2e9SChris Zhong #include <linux/gpio/consumer.h>
814c8f2e9SChris Zhong #include <linux/module.h>
914c8f2e9SChris Zhong #include <linux/of.h>
1014c8f2e9SChris Zhong #include <linux/regulator/consumer.h>
1114c8f2e9SChris Zhong 
1214c8f2e9SChris Zhong #include <video/mipi_display.h>
1314c8f2e9SChris Zhong 
14cb23eae3SSam Ravnborg #include <drm/drm_crtc.h>
15cb23eae3SSam Ravnborg #include <drm/drm_device.h>
16cb23eae3SSam Ravnborg #include <drm/drm_mipi_dsi.h>
17cb23eae3SSam Ravnborg #include <drm/drm_modes.h>
18cb23eae3SSam Ravnborg #include <drm/drm_panel.h>
19cb23eae3SSam Ravnborg 
20de04a462SLin Huang struct panel_init_cmd {
21de04a462SLin Huang 	size_t len;
22de04a462SLin Huang 	const char *data;
23de04a462SLin Huang };
24de04a462SLin Huang 
25de04a462SLin Huang #define _INIT_CMD(...) { \
26de04a462SLin Huang 	.len = sizeof((char[]){__VA_ARGS__}), \
27de04a462SLin Huang 	.data = (char[]){__VA_ARGS__} }
28de04a462SLin Huang 
297ad4e463SLin Huang struct panel_desc {
307ad4e463SLin Huang 	const struct drm_display_mode *mode;
317ad4e463SLin Huang 	unsigned int bpc;
327ad4e463SLin Huang 	struct {
337ad4e463SLin Huang 		unsigned int width;
347ad4e463SLin Huang 		unsigned int height;
357ad4e463SLin Huang 	} size;
367ad4e463SLin Huang 
377ad4e463SLin Huang 	unsigned long flags;
387ad4e463SLin Huang 	enum mipi_dsi_pixel_format format;
39de04a462SLin Huang 	const struct panel_init_cmd *init_cmds;
407ad4e463SLin Huang 	unsigned int lanes;
417ad4e463SLin Huang 	const char * const *supply_names;
427ad4e463SLin Huang 	unsigned int num_supplies;
4348bd379aSLin Huang 	unsigned int sleep_mode_delay;
4448bd379aSLin Huang 	unsigned int power_down_delay;
457ad4e463SLin Huang };
467ad4e463SLin Huang 
4714c8f2e9SChris Zhong struct innolux_panel {
4814c8f2e9SChris Zhong 	struct drm_panel base;
4914c8f2e9SChris Zhong 	struct mipi_dsi_device *link;
507ad4e463SLin Huang 	const struct panel_desc *desc;
5114c8f2e9SChris Zhong 
527ad4e463SLin Huang 	struct regulator_bulk_data *supplies;
5314c8f2e9SChris Zhong 	struct gpio_desc *enable_gpio;
5414c8f2e9SChris Zhong 
5514c8f2e9SChris Zhong 	bool prepared;
5614c8f2e9SChris Zhong 	bool enabled;
5714c8f2e9SChris Zhong };
5814c8f2e9SChris Zhong 
to_innolux_panel(struct drm_panel * panel)5914c8f2e9SChris Zhong static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel)
6014c8f2e9SChris Zhong {
6114c8f2e9SChris Zhong 	return container_of(panel, struct innolux_panel, base);
6214c8f2e9SChris Zhong }
6314c8f2e9SChris Zhong 
innolux_panel_disable(struct drm_panel * panel)6414c8f2e9SChris Zhong static int innolux_panel_disable(struct drm_panel *panel)
6514c8f2e9SChris Zhong {
6614c8f2e9SChris Zhong 	struct innolux_panel *innolux = to_innolux_panel(panel);
6714c8f2e9SChris Zhong 
6814c8f2e9SChris Zhong 	if (!innolux->enabled)
6914c8f2e9SChris Zhong 		return 0;
7014c8f2e9SChris Zhong 
7114c8f2e9SChris Zhong 	innolux->enabled = false;
7214c8f2e9SChris Zhong 
7314c8f2e9SChris Zhong 	return 0;
7414c8f2e9SChris Zhong }
7514c8f2e9SChris Zhong 
innolux_panel_unprepare(struct drm_panel * panel)7614c8f2e9SChris Zhong static int innolux_panel_unprepare(struct drm_panel *panel)
7714c8f2e9SChris Zhong {
7814c8f2e9SChris Zhong 	struct innolux_panel *innolux = to_innolux_panel(panel);
7914c8f2e9SChris Zhong 	int err;
8014c8f2e9SChris Zhong 
8114c8f2e9SChris Zhong 	if (!innolux->prepared)
8214c8f2e9SChris Zhong 		return 0;
8314c8f2e9SChris Zhong 
8446f3ceafSHsin-Yi, Wang 	err = mipi_dsi_dcs_set_display_off(innolux->link);
8546f3ceafSHsin-Yi, Wang 	if (err < 0)
86a25b6b27SSam Ravnborg 		dev_err(panel->dev, "failed to set display off: %d\n", err);
8746f3ceafSHsin-Yi, Wang 
8814c8f2e9SChris Zhong 	err = mipi_dsi_dcs_enter_sleep_mode(innolux->link);
8914c8f2e9SChris Zhong 	if (err < 0) {
90a25b6b27SSam Ravnborg 		dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
9114c8f2e9SChris Zhong 		return err;
9214c8f2e9SChris Zhong 	}
9314c8f2e9SChris Zhong 
9448bd379aSLin Huang 	if (innolux->desc->sleep_mode_delay)
9548bd379aSLin Huang 		msleep(innolux->desc->sleep_mode_delay);
9648bd379aSLin Huang 
9714c8f2e9SChris Zhong 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
9814c8f2e9SChris Zhong 
9948bd379aSLin Huang 	if (innolux->desc->power_down_delay)
10048bd379aSLin Huang 		msleep(innolux->desc->power_down_delay);
10114c8f2e9SChris Zhong 
1027ad4e463SLin Huang 	err = regulator_bulk_disable(innolux->desc->num_supplies,
1037ad4e463SLin Huang 				     innolux->supplies);
10414c8f2e9SChris Zhong 	if (err < 0)
10514c8f2e9SChris Zhong 		return err;
10614c8f2e9SChris Zhong 
10714c8f2e9SChris Zhong 	innolux->prepared = false;
10814c8f2e9SChris Zhong 
10914c8f2e9SChris Zhong 	return 0;
11014c8f2e9SChris Zhong }
11114c8f2e9SChris Zhong 
innolux_panel_prepare(struct drm_panel * panel)11214c8f2e9SChris Zhong static int innolux_panel_prepare(struct drm_panel *panel)
11314c8f2e9SChris Zhong {
11414c8f2e9SChris Zhong 	struct innolux_panel *innolux = to_innolux_panel(panel);
1157ad4e463SLin Huang 	int err;
11614c8f2e9SChris Zhong 
11714c8f2e9SChris Zhong 	if (innolux->prepared)
11814c8f2e9SChris Zhong 		return 0;
11914c8f2e9SChris Zhong 
12014c8f2e9SChris Zhong 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
12114c8f2e9SChris Zhong 
1227ad4e463SLin Huang 	err = regulator_bulk_enable(innolux->desc->num_supplies,
1237ad4e463SLin Huang 				    innolux->supplies);
12414c8f2e9SChris Zhong 	if (err < 0)
12514c8f2e9SChris Zhong 		return err;
12614c8f2e9SChris Zhong 
127de04a462SLin Huang 	/* p079zca: t2 (20ms), p097pfg: t4 (15ms) */
128de04a462SLin Huang 	usleep_range(20000, 21000);
12914c8f2e9SChris Zhong 
13014c8f2e9SChris Zhong 	gpiod_set_value_cansleep(innolux->enable_gpio, 1);
13114c8f2e9SChris Zhong 
132de04a462SLin Huang 	/* p079zca: t4, p097pfg: t5 */
133de04a462SLin Huang 	usleep_range(20000, 21000);
134de04a462SLin Huang 
135de04a462SLin Huang 	if (innolux->desc->init_cmds) {
136de04a462SLin Huang 		const struct panel_init_cmd *cmds =
137de04a462SLin Huang 					innolux->desc->init_cmds;
138de04a462SLin Huang 		unsigned int i;
139de04a462SLin Huang 
140de04a462SLin Huang 		for (i = 0; cmds[i].len != 0; i++) {
141de04a462SLin Huang 			const struct panel_init_cmd *cmd = &cmds[i];
142de04a462SLin Huang 
143de04a462SLin Huang 			err = mipi_dsi_generic_write(innolux->link, cmd->data,
144de04a462SLin Huang 						     cmd->len);
145de04a462SLin Huang 			if (err < 0) {
146a25b6b27SSam Ravnborg 				dev_err(panel->dev, "failed to write command %u\n", i);
147de04a462SLin Huang 				goto poweroff;
148de04a462SLin Huang 			}
149de04a462SLin Huang 
150de04a462SLin Huang 			/*
151de04a462SLin Huang 			 * Included by random guessing, because without this
152de04a462SLin Huang 			 * (or at least, some delay), the panel sometimes
153de04a462SLin Huang 			 * didn't appear to pick up the command sequence.
154de04a462SLin Huang 			 */
155de04a462SLin Huang 			err = mipi_dsi_dcs_nop(innolux->link);
156de04a462SLin Huang 			if (err < 0) {
157a25b6b27SSam Ravnborg 				dev_err(panel->dev, "failed to send DCS nop: %d\n", err);
158de04a462SLin Huang 				goto poweroff;
159de04a462SLin Huang 			}
160de04a462SLin Huang 		}
161de04a462SLin Huang 	}
16214c8f2e9SChris Zhong 
16314c8f2e9SChris Zhong 	err = mipi_dsi_dcs_exit_sleep_mode(innolux->link);
16414c8f2e9SChris Zhong 	if (err < 0) {
165a25b6b27SSam Ravnborg 		dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
16614c8f2e9SChris Zhong 		goto poweroff;
16714c8f2e9SChris Zhong 	}
16814c8f2e9SChris Zhong 
16914c8f2e9SChris Zhong 	/* T6: 120ms - 1000ms*/
17014c8f2e9SChris Zhong 	msleep(120);
17114c8f2e9SChris Zhong 
17214c8f2e9SChris Zhong 	err = mipi_dsi_dcs_set_display_on(innolux->link);
17314c8f2e9SChris Zhong 	if (err < 0) {
174a25b6b27SSam Ravnborg 		dev_err(panel->dev, "failed to set display on: %d\n", err);
17514c8f2e9SChris Zhong 		goto poweroff;
17614c8f2e9SChris Zhong 	}
17714c8f2e9SChris Zhong 
17814c8f2e9SChris Zhong 	/* T7: 5ms */
17914c8f2e9SChris Zhong 	usleep_range(5000, 6000);
18014c8f2e9SChris Zhong 
18114c8f2e9SChris Zhong 	innolux->prepared = true;
18214c8f2e9SChris Zhong 
18314c8f2e9SChris Zhong 	return 0;
18414c8f2e9SChris Zhong 
18514c8f2e9SChris Zhong poweroff:
18614c8f2e9SChris Zhong 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
1877ad4e463SLin Huang 	regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies);
1887ad4e463SLin Huang 
18914c8f2e9SChris Zhong 	return err;
19014c8f2e9SChris Zhong }
19114c8f2e9SChris Zhong 
innolux_panel_enable(struct drm_panel * panel)19214c8f2e9SChris Zhong static int innolux_panel_enable(struct drm_panel *panel)
19314c8f2e9SChris Zhong {
19414c8f2e9SChris Zhong 	struct innolux_panel *innolux = to_innolux_panel(panel);
19514c8f2e9SChris Zhong 
19614c8f2e9SChris Zhong 	if (innolux->enabled)
19714c8f2e9SChris Zhong 		return 0;
19814c8f2e9SChris Zhong 
19914c8f2e9SChris Zhong 	innolux->enabled = true;
20014c8f2e9SChris Zhong 
20114c8f2e9SChris Zhong 	return 0;
20214c8f2e9SChris Zhong }
20314c8f2e9SChris Zhong 
2047ad4e463SLin Huang static const char * const innolux_p079zca_supply_names[] = {
2057ad4e463SLin Huang 	"power",
2067ad4e463SLin Huang };
2077ad4e463SLin Huang 
2087ad4e463SLin Huang static const struct drm_display_mode innolux_p079zca_mode = {
20914c8f2e9SChris Zhong 	.clock = 56900,
21014c8f2e9SChris Zhong 	.hdisplay = 768,
21114c8f2e9SChris Zhong 	.hsync_start = 768 + 40,
21214c8f2e9SChris Zhong 	.hsync_end = 768 + 40 + 40,
21314c8f2e9SChris Zhong 	.htotal = 768 + 40 + 40 + 40,
21414c8f2e9SChris Zhong 	.vdisplay = 1024,
21514c8f2e9SChris Zhong 	.vsync_start = 1024 + 20,
21614c8f2e9SChris Zhong 	.vsync_end = 1024 + 20 + 4,
21714c8f2e9SChris Zhong 	.vtotal = 1024 + 20 + 4 + 20,
21814c8f2e9SChris Zhong };
21914c8f2e9SChris Zhong 
2207ad4e463SLin Huang static const struct panel_desc innolux_p079zca_panel_desc = {
2217ad4e463SLin Huang 	.mode = &innolux_p079zca_mode,
2227ad4e463SLin Huang 	.bpc = 8,
2237ad4e463SLin Huang 	.size = {
2247ad4e463SLin Huang 		.width = 120,
2257ad4e463SLin Huang 		.height = 160,
2267ad4e463SLin Huang 	},
2277ad4e463SLin Huang 	.flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
2287ad4e463SLin Huang 		 MIPI_DSI_MODE_LPM,
2297ad4e463SLin Huang 	.format = MIPI_DSI_FMT_RGB888,
2307ad4e463SLin Huang 	.lanes = 4,
2317ad4e463SLin Huang 	.supply_names = innolux_p079zca_supply_names,
2327ad4e463SLin Huang 	.num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names),
23348bd379aSLin Huang 	.power_down_delay = 80, /* T8: 80ms - 1000ms */
2347ad4e463SLin Huang };
2357ad4e463SLin Huang 
236de04a462SLin Huang static const char * const innolux_p097pfg_supply_names[] = {
237de04a462SLin Huang 	"avdd",
238de04a462SLin Huang 	"avee",
239de04a462SLin Huang };
240de04a462SLin Huang 
241de04a462SLin Huang static const struct drm_display_mode innolux_p097pfg_mode = {
242de04a462SLin Huang 	.clock = 229000,
243de04a462SLin Huang 	.hdisplay = 1536,
244de04a462SLin Huang 	.hsync_start = 1536 + 100,
245de04a462SLin Huang 	.hsync_end = 1536 + 100 + 24,
246de04a462SLin Huang 	.htotal = 1536 + 100 + 24 + 100,
247de04a462SLin Huang 	.vdisplay = 2048,
248de04a462SLin Huang 	.vsync_start = 2048 + 100,
249de04a462SLin Huang 	.vsync_end = 2048 + 100 + 2,
250de04a462SLin Huang 	.vtotal = 2048 + 100 + 2 + 18,
251de04a462SLin Huang };
252de04a462SLin Huang 
253de04a462SLin Huang /*
254de04a462SLin Huang  * Display manufacturer failed to provide init sequencing according to
255de04a462SLin Huang  * https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/892065/
256de04a462SLin Huang  * so the init sequence stems from a register dump of a working panel.
257de04a462SLin Huang  */
258de04a462SLin Huang static const struct panel_init_cmd innolux_p097pfg_init_cmds[] = {
259de04a462SLin Huang 	/* page 0 */
260de04a462SLin Huang 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
261de04a462SLin Huang 	_INIT_CMD(0xB1, 0xE8, 0x11),
262de04a462SLin Huang 	_INIT_CMD(0xB2, 0x25, 0x02),
263de04a462SLin Huang 	_INIT_CMD(0xB5, 0x08, 0x00),
264de04a462SLin Huang 	_INIT_CMD(0xBC, 0x0F, 0x00),
265de04a462SLin Huang 	_INIT_CMD(0xB8, 0x03, 0x06, 0x00, 0x00),
266de04a462SLin Huang 	_INIT_CMD(0xBD, 0x01, 0x90, 0x14, 0x14),
267de04a462SLin Huang 	_INIT_CMD(0x6F, 0x01),
268de04a462SLin Huang 	_INIT_CMD(0xC0, 0x03),
269de04a462SLin Huang 	_INIT_CMD(0x6F, 0x02),
270de04a462SLin Huang 	_INIT_CMD(0xC1, 0x0D),
271de04a462SLin Huang 	_INIT_CMD(0xD9, 0x01, 0x09, 0x70),
272de04a462SLin Huang 	_INIT_CMD(0xC5, 0x12, 0x21, 0x00),
273de04a462SLin Huang 	_INIT_CMD(0xBB, 0x93, 0x93),
274de04a462SLin Huang 
275de04a462SLin Huang 	/* page 1 */
276de04a462SLin Huang 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01),
277de04a462SLin Huang 	_INIT_CMD(0xB3, 0x3C, 0x3C),
278de04a462SLin Huang 	_INIT_CMD(0xB4, 0x0F, 0x0F),
279de04a462SLin Huang 	_INIT_CMD(0xB9, 0x45, 0x45),
280de04a462SLin Huang 	_INIT_CMD(0xBA, 0x14, 0x14),
281de04a462SLin Huang 	_INIT_CMD(0xCA, 0x02),
282de04a462SLin Huang 	_INIT_CMD(0xCE, 0x04),
283de04a462SLin Huang 	_INIT_CMD(0xC3, 0x9B, 0x9B),
284de04a462SLin Huang 	_INIT_CMD(0xD8, 0xC0, 0x03),
285de04a462SLin Huang 	_INIT_CMD(0xBC, 0x82, 0x01),
286de04a462SLin Huang 	_INIT_CMD(0xBD, 0x9E, 0x01),
287de04a462SLin Huang 
288de04a462SLin Huang 	/* page 2 */
289de04a462SLin Huang 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x02),
290de04a462SLin Huang 	_INIT_CMD(0xB0, 0x82),
291de04a462SLin Huang 	_INIT_CMD(0xD1, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x82, 0x00, 0xA5,
292de04a462SLin Huang 		  0x00, 0xC1, 0x00, 0xEA, 0x01, 0x0D, 0x01, 0x40),
293de04a462SLin Huang 	_INIT_CMD(0xD2, 0x01, 0x6A, 0x01, 0xA8, 0x01, 0xDC, 0x02, 0x29,
294de04a462SLin Huang 		  0x02, 0x67, 0x02, 0x68, 0x02, 0xA8, 0x02, 0xF0),
295de04a462SLin Huang 	_INIT_CMD(0xD3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8C,
296de04a462SLin Huang 		  0x03, 0xA6, 0x03, 0xC7, 0x03, 0xDE, 0x03, 0xEC),
297de04a462SLin Huang 	_INIT_CMD(0xD4, 0x03, 0xFF, 0x03, 0xFF),
298de04a462SLin Huang 	_INIT_CMD(0xE0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xC5, 0x00, 0xE5,
299de04a462SLin Huang 		  0x00, 0xFF, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75),
300de04a462SLin Huang 	_INIT_CMD(0xE1, 0x01, 0x9C, 0x01, 0xD5, 0x02, 0x05, 0x02, 0x4D,
301de04a462SLin Huang 		  0x02, 0x86, 0x02, 0x87, 0x02, 0xC3, 0x03, 0x03),
302de04a462SLin Huang 	_INIT_CMD(0xE2, 0x03, 0x2A, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94,
303de04a462SLin Huang 		  0x03, 0xAC, 0x03, 0xCB, 0x03, 0xE0, 0x03, 0xED),
304de04a462SLin Huang 	_INIT_CMD(0xE3, 0x03, 0xFF, 0x03, 0xFF),
305de04a462SLin Huang 
306de04a462SLin Huang 	/* page 3 */
307de04a462SLin Huang 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03),
308de04a462SLin Huang 	_INIT_CMD(0xB0, 0x00, 0x00, 0x00, 0x00),
309de04a462SLin Huang 	_INIT_CMD(0xB1, 0x00, 0x00, 0x00, 0x00),
310de04a462SLin Huang 	_INIT_CMD(0xB2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85),
311de04a462SLin Huang 	_INIT_CMD(0xB3, 0x10, 0x07, 0xFC, 0x04, 0x01, 0x40, 0x80),
312de04a462SLin Huang 	_INIT_CMD(0xB6, 0xF0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
313de04a462SLin Huang 		  0x40, 0x80),
314de04a462SLin Huang 	_INIT_CMD(0xBA, 0xC5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8C),
315de04a462SLin Huang 	_INIT_CMD(0xBB, 0xC5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8C),
316de04a462SLin Huang 	_INIT_CMD(0xC0, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
317de04a462SLin Huang 	_INIT_CMD(0xC1, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
318de04a462SLin Huang 	_INIT_CMD(0xC4, 0x00, 0x00),
319de04a462SLin Huang 	_INIT_CMD(0xEF, 0x41),
320de04a462SLin Huang 
321de04a462SLin Huang 	/* page 4 */
322de04a462SLin Huang 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04),
323de04a462SLin Huang 	_INIT_CMD(0xEC, 0x4C),
324de04a462SLin Huang 
325de04a462SLin Huang 	/* page 5 */
326de04a462SLin Huang 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x05),
327de04a462SLin Huang 	_INIT_CMD(0xB0, 0x13, 0x03, 0x03, 0x01),
328de04a462SLin Huang 	_INIT_CMD(0xB1, 0x30, 0x00),
329de04a462SLin Huang 	_INIT_CMD(0xB2, 0x02, 0x02, 0x00),
330de04a462SLin Huang 	_INIT_CMD(0xB3, 0x82, 0x23, 0x82, 0x9D),
331de04a462SLin Huang 	_INIT_CMD(0xB4, 0xC5, 0x75, 0x24, 0x57),
332de04a462SLin Huang 	_INIT_CMD(0xB5, 0x00, 0xD4, 0x72, 0x11, 0x11, 0xAB, 0x0A),
333de04a462SLin Huang 	_INIT_CMD(0xB6, 0x00, 0x00, 0xD5, 0x72, 0x24, 0x56),
334de04a462SLin Huang 	_INIT_CMD(0xB7, 0x5C, 0xDC, 0x5C, 0x5C),
335de04a462SLin Huang 	_INIT_CMD(0xB9, 0x0C, 0x00, 0x00, 0x01, 0x00),
336de04a462SLin Huang 	_INIT_CMD(0xC0, 0x75, 0x11, 0x11, 0x54, 0x05),
337de04a462SLin Huang 	_INIT_CMD(0xC6, 0x00, 0x00, 0x00, 0x00),
338de04a462SLin Huang 	_INIT_CMD(0xD0, 0x00, 0x48, 0x08, 0x00, 0x00),
339de04a462SLin Huang 	_INIT_CMD(0xD1, 0x00, 0x48, 0x09, 0x00, 0x00),
340de04a462SLin Huang 
341de04a462SLin Huang 	/* page 6 */
342de04a462SLin Huang 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x06),
343de04a462SLin Huang 	_INIT_CMD(0xB0, 0x02, 0x32, 0x32, 0x08, 0x2F),
344de04a462SLin Huang 	_INIT_CMD(0xB1, 0x2E, 0x15, 0x14, 0x13, 0x12),
345de04a462SLin Huang 	_INIT_CMD(0xB2, 0x11, 0x10, 0x00, 0x3D, 0x3D),
346de04a462SLin Huang 	_INIT_CMD(0xB3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
347de04a462SLin Huang 	_INIT_CMD(0xB4, 0x3D, 0x32),
348de04a462SLin Huang 	_INIT_CMD(0xB5, 0x03, 0x32, 0x32, 0x09, 0x2F),
349de04a462SLin Huang 	_INIT_CMD(0xB6, 0x2E, 0x1B, 0x1A, 0x19, 0x18),
350de04a462SLin Huang 	_INIT_CMD(0xB7, 0x17, 0x16, 0x01, 0x3D, 0x3D),
351de04a462SLin Huang 	_INIT_CMD(0xB8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
352de04a462SLin Huang 	_INIT_CMD(0xB9, 0x3D, 0x32),
353de04a462SLin Huang 	_INIT_CMD(0xC0, 0x01, 0x32, 0x32, 0x09, 0x2F),
354de04a462SLin Huang 	_INIT_CMD(0xC1, 0x2E, 0x1A, 0x1B, 0x16, 0x17),
355de04a462SLin Huang 	_INIT_CMD(0xC2, 0x18, 0x19, 0x03, 0x3D, 0x3D),
356de04a462SLin Huang 	_INIT_CMD(0xC3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
357de04a462SLin Huang 	_INIT_CMD(0xC4, 0x3D, 0x32),
358de04a462SLin Huang 	_INIT_CMD(0xC5, 0x00, 0x32, 0x32, 0x08, 0x2F),
359de04a462SLin Huang 	_INIT_CMD(0xC6, 0x2E, 0x14, 0x15, 0x10, 0x11),
360de04a462SLin Huang 	_INIT_CMD(0xC7, 0x12, 0x13, 0x02, 0x3D, 0x3D),
361de04a462SLin Huang 	_INIT_CMD(0xC8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
362de04a462SLin Huang 	_INIT_CMD(0xC9, 0x3D, 0x32),
363de04a462SLin Huang 
364de04a462SLin Huang 	{},
365de04a462SLin Huang };
366de04a462SLin Huang 
367de04a462SLin Huang static const struct panel_desc innolux_p097pfg_panel_desc = {
368de04a462SLin Huang 	.mode = &innolux_p097pfg_mode,
369de04a462SLin Huang 	.bpc = 8,
370de04a462SLin Huang 	.size = {
371de04a462SLin Huang 		.width = 147,
372de04a462SLin Huang 		.height = 196,
373de04a462SLin Huang 	},
374de04a462SLin Huang 	.flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
375de04a462SLin Huang 		 MIPI_DSI_MODE_LPM,
376de04a462SLin Huang 	.format = MIPI_DSI_FMT_RGB888,
377de04a462SLin Huang 	.init_cmds = innolux_p097pfg_init_cmds,
378de04a462SLin Huang 	.lanes = 4,
379de04a462SLin Huang 	.supply_names = innolux_p097pfg_supply_names,
380de04a462SLin Huang 	.num_supplies = ARRAY_SIZE(innolux_p097pfg_supply_names),
381de04a462SLin Huang 	.sleep_mode_delay = 100, /* T15 */
382de04a462SLin Huang };
383de04a462SLin Huang 
innolux_panel_get_modes(struct drm_panel * panel,struct drm_connector * connector)3840ce8ddd8SSam Ravnborg static int innolux_panel_get_modes(struct drm_panel *panel,
3850ce8ddd8SSam Ravnborg 				   struct drm_connector *connector)
38614c8f2e9SChris Zhong {
3877ad4e463SLin Huang 	struct innolux_panel *innolux = to_innolux_panel(panel);
3887ad4e463SLin Huang 	const struct drm_display_mode *m = innolux->desc->mode;
38914c8f2e9SChris Zhong 	struct drm_display_mode *mode;
39014c8f2e9SChris Zhong 
391aa6c4364SSam Ravnborg 	mode = drm_mode_duplicate(connector->dev, m);
39214c8f2e9SChris Zhong 	if (!mode) {
393a25b6b27SSam Ravnborg 		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
3940425662fSVille Syrjälä 			m->hdisplay, m->vdisplay, drm_mode_vrefresh(m));
39514c8f2e9SChris Zhong 		return -ENOMEM;
39614c8f2e9SChris Zhong 	}
39714c8f2e9SChris Zhong 
39814c8f2e9SChris Zhong 	drm_mode_set_name(mode);
39914c8f2e9SChris Zhong 
4000ce8ddd8SSam Ravnborg 	drm_mode_probed_add(connector, mode);
40114c8f2e9SChris Zhong 
4020ce8ddd8SSam Ravnborg 	connector->display_info.width_mm = innolux->desc->size.width;
4030ce8ddd8SSam Ravnborg 	connector->display_info.height_mm = innolux->desc->size.height;
4040ce8ddd8SSam Ravnborg 	connector->display_info.bpc = innolux->desc->bpc;
40514c8f2e9SChris Zhong 
40614c8f2e9SChris Zhong 	return 1;
40714c8f2e9SChris Zhong }
40814c8f2e9SChris Zhong 
40914c8f2e9SChris Zhong static const struct drm_panel_funcs innolux_panel_funcs = {
41014c8f2e9SChris Zhong 	.disable = innolux_panel_disable,
41114c8f2e9SChris Zhong 	.unprepare = innolux_panel_unprepare,
41214c8f2e9SChris Zhong 	.prepare = innolux_panel_prepare,
41314c8f2e9SChris Zhong 	.enable = innolux_panel_enable,
41414c8f2e9SChris Zhong 	.get_modes = innolux_panel_get_modes,
41514c8f2e9SChris Zhong };
41614c8f2e9SChris Zhong 
41714c8f2e9SChris Zhong static const struct of_device_id innolux_of_match[] = {
4187ad4e463SLin Huang 	{ .compatible = "innolux,p079zca",
4197ad4e463SLin Huang 	  .data = &innolux_p079zca_panel_desc
4207ad4e463SLin Huang 	},
421de04a462SLin Huang 	{ .compatible = "innolux,p097pfg",
422de04a462SLin Huang 	  .data = &innolux_p097pfg_panel_desc
423de04a462SLin Huang 	},
42414c8f2e9SChris Zhong 	{ }
42514c8f2e9SChris Zhong };
42614c8f2e9SChris Zhong MODULE_DEVICE_TABLE(of, innolux_of_match);
42714c8f2e9SChris Zhong 
innolux_panel_add(struct mipi_dsi_device * dsi,const struct panel_desc * desc)4287ad4e463SLin Huang static int innolux_panel_add(struct mipi_dsi_device *dsi,
4297ad4e463SLin Huang 			     const struct panel_desc *desc)
43014c8f2e9SChris Zhong {
4317ad4e463SLin Huang 	struct innolux_panel *innolux;
4327ad4e463SLin Huang 	struct device *dev = &dsi->dev;
4337ad4e463SLin Huang 	int err, i;
43414c8f2e9SChris Zhong 
4357ad4e463SLin Huang 	innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL);
4367ad4e463SLin Huang 	if (!innolux)
4377ad4e463SLin Huang 		return -ENOMEM;
4387ad4e463SLin Huang 
4397ad4e463SLin Huang 	innolux->desc = desc;
4407ad4e463SLin Huang 
4417ad4e463SLin Huang 	innolux->supplies = devm_kcalloc(dev, desc->num_supplies,
4427ad4e463SLin Huang 					 sizeof(*innolux->supplies),
4437ad4e463SLin Huang 					 GFP_KERNEL);
4447ad4e463SLin Huang 	if (!innolux->supplies)
4457ad4e463SLin Huang 		return -ENOMEM;
4467ad4e463SLin Huang 
4477ad4e463SLin Huang 	for (i = 0; i < desc->num_supplies; i++)
4487ad4e463SLin Huang 		innolux->supplies[i].supply = desc->supply_names[i];
4497ad4e463SLin Huang 
4507ad4e463SLin Huang 	err = devm_regulator_bulk_get(dev, desc->num_supplies,
4517ad4e463SLin Huang 				      innolux->supplies);
4527ad4e463SLin Huang 	if (err < 0)
4537ad4e463SLin Huang 		return err;
45414c8f2e9SChris Zhong 
45514c8f2e9SChris Zhong 	innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable",
45614c8f2e9SChris Zhong 						       GPIOD_OUT_HIGH);
45714c8f2e9SChris Zhong 	if (IS_ERR(innolux->enable_gpio)) {
45814c8f2e9SChris Zhong 		err = PTR_ERR(innolux->enable_gpio);
45914c8f2e9SChris Zhong 		dev_dbg(dev, "failed to get enable gpio: %d\n", err);
46014c8f2e9SChris Zhong 		innolux->enable_gpio = NULL;
46114c8f2e9SChris Zhong 	}
46214c8f2e9SChris Zhong 
4639a2654c0SLaurent Pinchart 	drm_panel_init(&innolux->base, dev, &innolux_panel_funcs,
4649a2654c0SLaurent Pinchart 		       DRM_MODE_CONNECTOR_DSI);
46514c8f2e9SChris Zhong 
46616793e00SSam Ravnborg 	err = drm_panel_of_backlight(&innolux->base);
46716793e00SSam Ravnborg 	if (err)
46816793e00SSam Ravnborg 		return err;
46916793e00SSam Ravnborg 
470c3ee8c65SBernard Zhao 	drm_panel_add(&innolux->base);
4717ad4e463SLin Huang 
4727ad4e463SLin Huang 	mipi_dsi_set_drvdata(dsi, innolux);
4737ad4e463SLin Huang 	innolux->link = dsi;
4747ad4e463SLin Huang 
4757ad4e463SLin Huang 	return 0;
47614c8f2e9SChris Zhong }
47714c8f2e9SChris Zhong 
innolux_panel_del(struct innolux_panel * innolux)47814c8f2e9SChris Zhong static void innolux_panel_del(struct innolux_panel *innolux)
47914c8f2e9SChris Zhong {
48014c8f2e9SChris Zhong 	drm_panel_remove(&innolux->base);
48114c8f2e9SChris Zhong }
48214c8f2e9SChris Zhong 
innolux_panel_probe(struct mipi_dsi_device * dsi)48314c8f2e9SChris Zhong static int innolux_panel_probe(struct mipi_dsi_device *dsi)
48414c8f2e9SChris Zhong {
4857ad4e463SLin Huang 	const struct panel_desc *desc;
48632a267e9SBrian Norris 	struct innolux_panel *innolux;
48714c8f2e9SChris Zhong 	int err;
48814c8f2e9SChris Zhong 
489b6d83fccSThierry Reding 	desc = of_device_get_match_data(&dsi->dev);
4907ad4e463SLin Huang 	dsi->mode_flags = desc->flags;
4917ad4e463SLin Huang 	dsi->format = desc->format;
4927ad4e463SLin Huang 	dsi->lanes = desc->lanes;
49314c8f2e9SChris Zhong 
4947ad4e463SLin Huang 	err = innolux_panel_add(dsi, desc);
49514c8f2e9SChris Zhong 	if (err < 0)
49614c8f2e9SChris Zhong 		return err;
49714c8f2e9SChris Zhong 
49832a267e9SBrian Norris 	err = mipi_dsi_attach(dsi);
49932a267e9SBrian Norris 	if (err < 0) {
50032a267e9SBrian Norris 		innolux = mipi_dsi_get_drvdata(dsi);
50132a267e9SBrian Norris 		innolux_panel_del(innolux);
50232a267e9SBrian Norris 		return err;
50332a267e9SBrian Norris 	}
50432a267e9SBrian Norris 
50532a267e9SBrian Norris 	return 0;
50614c8f2e9SChris Zhong }
50714c8f2e9SChris Zhong 
innolux_panel_remove(struct mipi_dsi_device * dsi)508*79abca2bSUwe Kleine-König static void innolux_panel_remove(struct mipi_dsi_device *dsi)
50914c8f2e9SChris Zhong {
51014c8f2e9SChris Zhong 	struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
51114c8f2e9SChris Zhong 	int err;
51214c8f2e9SChris Zhong 
51316793e00SSam Ravnborg 	err = drm_panel_unprepare(&innolux->base);
51414c8f2e9SChris Zhong 	if (err < 0)
515a25b6b27SSam Ravnborg 		dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
51614c8f2e9SChris Zhong 
51716793e00SSam Ravnborg 	err = drm_panel_disable(&innolux->base);
51814c8f2e9SChris Zhong 	if (err < 0)
519a25b6b27SSam Ravnborg 		dev_err(&dsi->dev, "failed to disable panel: %d\n", err);
52014c8f2e9SChris Zhong 
52114c8f2e9SChris Zhong 	err = mipi_dsi_detach(dsi);
52214c8f2e9SChris Zhong 	if (err < 0)
523a25b6b27SSam Ravnborg 		dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
52414c8f2e9SChris Zhong 
52514c8f2e9SChris Zhong 	innolux_panel_del(innolux);
52614c8f2e9SChris Zhong }
52714c8f2e9SChris Zhong 
innolux_panel_shutdown(struct mipi_dsi_device * dsi)52814c8f2e9SChris Zhong static void innolux_panel_shutdown(struct mipi_dsi_device *dsi)
52914c8f2e9SChris Zhong {
53014c8f2e9SChris Zhong 	struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
53114c8f2e9SChris Zhong 
53216793e00SSam Ravnborg 	drm_panel_unprepare(&innolux->base);
53316793e00SSam Ravnborg 	drm_panel_disable(&innolux->base);
53414c8f2e9SChris Zhong }
53514c8f2e9SChris Zhong 
53614c8f2e9SChris Zhong static struct mipi_dsi_driver innolux_panel_driver = {
53714c8f2e9SChris Zhong 	.driver = {
53814c8f2e9SChris Zhong 		.name = "panel-innolux-p079zca",
53914c8f2e9SChris Zhong 		.of_match_table = innolux_of_match,
54014c8f2e9SChris Zhong 	},
54114c8f2e9SChris Zhong 	.probe = innolux_panel_probe,
54214c8f2e9SChris Zhong 	.remove = innolux_panel_remove,
54314c8f2e9SChris Zhong 	.shutdown = innolux_panel_shutdown,
54414c8f2e9SChris Zhong };
54514c8f2e9SChris Zhong module_mipi_dsi_driver(innolux_panel_driver);
54614c8f2e9SChris Zhong 
54714c8f2e9SChris Zhong MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
5487ad4e463SLin Huang MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
54914c8f2e9SChris Zhong MODULE_DESCRIPTION("Innolux P079ZCA panel driver");
55014c8f2e9SChris Zhong MODULE_LICENSE("GPL v2");
551