162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
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 panel_init_cmd {
2162306a36Sopenharmony_ci	size_t len;
2262306a36Sopenharmony_ci	const char *data;
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define _INIT_CMD(...) { \
2662306a36Sopenharmony_ci	.len = sizeof((char[]){__VA_ARGS__}), \
2762306a36Sopenharmony_ci	.data = (char[]){__VA_ARGS__} }
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct panel_desc {
3062306a36Sopenharmony_ci	const struct drm_display_mode *mode;
3162306a36Sopenharmony_ci	unsigned int bpc;
3262306a36Sopenharmony_ci	struct {
3362306a36Sopenharmony_ci		unsigned int width;
3462306a36Sopenharmony_ci		unsigned int height;
3562306a36Sopenharmony_ci	} size;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	unsigned long flags;
3862306a36Sopenharmony_ci	enum mipi_dsi_pixel_format format;
3962306a36Sopenharmony_ci	const struct panel_init_cmd *init_cmds;
4062306a36Sopenharmony_ci	unsigned int lanes;
4162306a36Sopenharmony_ci	const char * const *supply_names;
4262306a36Sopenharmony_ci	unsigned int num_supplies;
4362306a36Sopenharmony_ci	unsigned int sleep_mode_delay;
4462306a36Sopenharmony_ci	unsigned int power_down_delay;
4562306a36Sopenharmony_ci};
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistruct innolux_panel {
4862306a36Sopenharmony_ci	struct drm_panel base;
4962306a36Sopenharmony_ci	struct mipi_dsi_device *link;
5062306a36Sopenharmony_ci	const struct panel_desc *desc;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	struct regulator_bulk_data *supplies;
5362306a36Sopenharmony_ci	struct gpio_desc *enable_gpio;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	bool prepared;
5662306a36Sopenharmony_ci	bool enabled;
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	return container_of(panel, struct innolux_panel, base);
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic int innolux_panel_disable(struct drm_panel *panel)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	struct innolux_panel *innolux = to_innolux_panel(panel);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (!innolux->enabled)
6962306a36Sopenharmony_ci		return 0;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	innolux->enabled = false;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return 0;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic int innolux_panel_unprepare(struct drm_panel *panel)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct innolux_panel *innolux = to_innolux_panel(panel);
7962306a36Sopenharmony_ci	int err;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	if (!innolux->prepared)
8262306a36Sopenharmony_ci		return 0;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	err = mipi_dsi_dcs_set_display_off(innolux->link);
8562306a36Sopenharmony_ci	if (err < 0)
8662306a36Sopenharmony_ci		dev_err(panel->dev, "failed to set display off: %d\n", err);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	err = mipi_dsi_dcs_enter_sleep_mode(innolux->link);
8962306a36Sopenharmony_ci	if (err < 0) {
9062306a36Sopenharmony_ci		dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
9162306a36Sopenharmony_ci		return err;
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (innolux->desc->sleep_mode_delay)
9562306a36Sopenharmony_ci		msleep(innolux->desc->sleep_mode_delay);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (innolux->desc->power_down_delay)
10062306a36Sopenharmony_ci		msleep(innolux->desc->power_down_delay);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	err = regulator_bulk_disable(innolux->desc->num_supplies,
10362306a36Sopenharmony_ci				     innolux->supplies);
10462306a36Sopenharmony_ci	if (err < 0)
10562306a36Sopenharmony_ci		return err;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	innolux->prepared = false;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	return 0;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int innolux_panel_prepare(struct drm_panel *panel)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct innolux_panel *innolux = to_innolux_panel(panel);
11562306a36Sopenharmony_ci	int err;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (innolux->prepared)
11862306a36Sopenharmony_ci		return 0;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	err = regulator_bulk_enable(innolux->desc->num_supplies,
12362306a36Sopenharmony_ci				    innolux->supplies);
12462306a36Sopenharmony_ci	if (err < 0)
12562306a36Sopenharmony_ci		return err;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* p079zca: t2 (20ms), p097pfg: t4 (15ms) */
12862306a36Sopenharmony_ci	usleep_range(20000, 21000);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	gpiod_set_value_cansleep(innolux->enable_gpio, 1);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* p079zca: t4, p097pfg: t5 */
13362306a36Sopenharmony_ci	usleep_range(20000, 21000);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (innolux->desc->init_cmds) {
13662306a36Sopenharmony_ci		const struct panel_init_cmd *cmds =
13762306a36Sopenharmony_ci					innolux->desc->init_cmds;
13862306a36Sopenharmony_ci		unsigned int i;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci		for (i = 0; cmds[i].len != 0; i++) {
14162306a36Sopenharmony_ci			const struct panel_init_cmd *cmd = &cmds[i];
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci			err = mipi_dsi_generic_write(innolux->link, cmd->data,
14462306a36Sopenharmony_ci						     cmd->len);
14562306a36Sopenharmony_ci			if (err < 0) {
14662306a36Sopenharmony_ci				dev_err(panel->dev, "failed to write command %u\n", i);
14762306a36Sopenharmony_ci				goto poweroff;
14862306a36Sopenharmony_ci			}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci			/*
15162306a36Sopenharmony_ci			 * Included by random guessing, because without this
15262306a36Sopenharmony_ci			 * (or at least, some delay), the panel sometimes
15362306a36Sopenharmony_ci			 * didn't appear to pick up the command sequence.
15462306a36Sopenharmony_ci			 */
15562306a36Sopenharmony_ci			err = mipi_dsi_dcs_nop(innolux->link);
15662306a36Sopenharmony_ci			if (err < 0) {
15762306a36Sopenharmony_ci				dev_err(panel->dev, "failed to send DCS nop: %d\n", err);
15862306a36Sopenharmony_ci				goto poweroff;
15962306a36Sopenharmony_ci			}
16062306a36Sopenharmony_ci		}
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	err = mipi_dsi_dcs_exit_sleep_mode(innolux->link);
16462306a36Sopenharmony_ci	if (err < 0) {
16562306a36Sopenharmony_ci		dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
16662306a36Sopenharmony_ci		goto poweroff;
16762306a36Sopenharmony_ci	}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	/* T6: 120ms - 1000ms*/
17062306a36Sopenharmony_ci	msleep(120);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	err = mipi_dsi_dcs_set_display_on(innolux->link);
17362306a36Sopenharmony_ci	if (err < 0) {
17462306a36Sopenharmony_ci		dev_err(panel->dev, "failed to set display on: %d\n", err);
17562306a36Sopenharmony_ci		goto poweroff;
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	/* T7: 5ms */
17962306a36Sopenharmony_ci	usleep_range(5000, 6000);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	innolux->prepared = true;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	return 0;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cipoweroff:
18662306a36Sopenharmony_ci	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
18762306a36Sopenharmony_ci	regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return err;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic int innolux_panel_enable(struct drm_panel *panel)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct innolux_panel *innolux = to_innolux_panel(panel);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	if (innolux->enabled)
19762306a36Sopenharmony_ci		return 0;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	innolux->enabled = true;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	return 0;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic const char * const innolux_p079zca_supply_names[] = {
20562306a36Sopenharmony_ci	"power",
20662306a36Sopenharmony_ci};
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic const struct drm_display_mode innolux_p079zca_mode = {
20962306a36Sopenharmony_ci	.clock = 56900,
21062306a36Sopenharmony_ci	.hdisplay = 768,
21162306a36Sopenharmony_ci	.hsync_start = 768 + 40,
21262306a36Sopenharmony_ci	.hsync_end = 768 + 40 + 40,
21362306a36Sopenharmony_ci	.htotal = 768 + 40 + 40 + 40,
21462306a36Sopenharmony_ci	.vdisplay = 1024,
21562306a36Sopenharmony_ci	.vsync_start = 1024 + 20,
21662306a36Sopenharmony_ci	.vsync_end = 1024 + 20 + 4,
21762306a36Sopenharmony_ci	.vtotal = 1024 + 20 + 4 + 20,
21862306a36Sopenharmony_ci};
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic const struct panel_desc innolux_p079zca_panel_desc = {
22162306a36Sopenharmony_ci	.mode = &innolux_p079zca_mode,
22262306a36Sopenharmony_ci	.bpc = 8,
22362306a36Sopenharmony_ci	.size = {
22462306a36Sopenharmony_ci		.width = 120,
22562306a36Sopenharmony_ci		.height = 160,
22662306a36Sopenharmony_ci	},
22762306a36Sopenharmony_ci	.flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
22862306a36Sopenharmony_ci		 MIPI_DSI_MODE_LPM,
22962306a36Sopenharmony_ci	.format = MIPI_DSI_FMT_RGB888,
23062306a36Sopenharmony_ci	.lanes = 4,
23162306a36Sopenharmony_ci	.supply_names = innolux_p079zca_supply_names,
23262306a36Sopenharmony_ci	.num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names),
23362306a36Sopenharmony_ci	.power_down_delay = 80, /* T8: 80ms - 1000ms */
23462306a36Sopenharmony_ci};
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic const char * const innolux_p097pfg_supply_names[] = {
23762306a36Sopenharmony_ci	"avdd",
23862306a36Sopenharmony_ci	"avee",
23962306a36Sopenharmony_ci};
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_cistatic const struct drm_display_mode innolux_p097pfg_mode = {
24262306a36Sopenharmony_ci	.clock = 229000,
24362306a36Sopenharmony_ci	.hdisplay = 1536,
24462306a36Sopenharmony_ci	.hsync_start = 1536 + 100,
24562306a36Sopenharmony_ci	.hsync_end = 1536 + 100 + 24,
24662306a36Sopenharmony_ci	.htotal = 1536 + 100 + 24 + 100,
24762306a36Sopenharmony_ci	.vdisplay = 2048,
24862306a36Sopenharmony_ci	.vsync_start = 2048 + 100,
24962306a36Sopenharmony_ci	.vsync_end = 2048 + 100 + 2,
25062306a36Sopenharmony_ci	.vtotal = 2048 + 100 + 2 + 18,
25162306a36Sopenharmony_ci};
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci/*
25462306a36Sopenharmony_ci * Display manufacturer failed to provide init sequencing according to
25562306a36Sopenharmony_ci * https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/892065/
25662306a36Sopenharmony_ci * so the init sequence stems from a register dump of a working panel.
25762306a36Sopenharmony_ci */
25862306a36Sopenharmony_cistatic const struct panel_init_cmd innolux_p097pfg_init_cmds[] = {
25962306a36Sopenharmony_ci	/* page 0 */
26062306a36Sopenharmony_ci	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
26162306a36Sopenharmony_ci	_INIT_CMD(0xB1, 0xE8, 0x11),
26262306a36Sopenharmony_ci	_INIT_CMD(0xB2, 0x25, 0x02),
26362306a36Sopenharmony_ci	_INIT_CMD(0xB5, 0x08, 0x00),
26462306a36Sopenharmony_ci	_INIT_CMD(0xBC, 0x0F, 0x00),
26562306a36Sopenharmony_ci	_INIT_CMD(0xB8, 0x03, 0x06, 0x00, 0x00),
26662306a36Sopenharmony_ci	_INIT_CMD(0xBD, 0x01, 0x90, 0x14, 0x14),
26762306a36Sopenharmony_ci	_INIT_CMD(0x6F, 0x01),
26862306a36Sopenharmony_ci	_INIT_CMD(0xC0, 0x03),
26962306a36Sopenharmony_ci	_INIT_CMD(0x6F, 0x02),
27062306a36Sopenharmony_ci	_INIT_CMD(0xC1, 0x0D),
27162306a36Sopenharmony_ci	_INIT_CMD(0xD9, 0x01, 0x09, 0x70),
27262306a36Sopenharmony_ci	_INIT_CMD(0xC5, 0x12, 0x21, 0x00),
27362306a36Sopenharmony_ci	_INIT_CMD(0xBB, 0x93, 0x93),
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	/* page 1 */
27662306a36Sopenharmony_ci	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01),
27762306a36Sopenharmony_ci	_INIT_CMD(0xB3, 0x3C, 0x3C),
27862306a36Sopenharmony_ci	_INIT_CMD(0xB4, 0x0F, 0x0F),
27962306a36Sopenharmony_ci	_INIT_CMD(0xB9, 0x45, 0x45),
28062306a36Sopenharmony_ci	_INIT_CMD(0xBA, 0x14, 0x14),
28162306a36Sopenharmony_ci	_INIT_CMD(0xCA, 0x02),
28262306a36Sopenharmony_ci	_INIT_CMD(0xCE, 0x04),
28362306a36Sopenharmony_ci	_INIT_CMD(0xC3, 0x9B, 0x9B),
28462306a36Sopenharmony_ci	_INIT_CMD(0xD8, 0xC0, 0x03),
28562306a36Sopenharmony_ci	_INIT_CMD(0xBC, 0x82, 0x01),
28662306a36Sopenharmony_ci	_INIT_CMD(0xBD, 0x9E, 0x01),
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	/* page 2 */
28962306a36Sopenharmony_ci	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x02),
29062306a36Sopenharmony_ci	_INIT_CMD(0xB0, 0x82),
29162306a36Sopenharmony_ci	_INIT_CMD(0xD1, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x82, 0x00, 0xA5,
29262306a36Sopenharmony_ci		  0x00, 0xC1, 0x00, 0xEA, 0x01, 0x0D, 0x01, 0x40),
29362306a36Sopenharmony_ci	_INIT_CMD(0xD2, 0x01, 0x6A, 0x01, 0xA8, 0x01, 0xDC, 0x02, 0x29,
29462306a36Sopenharmony_ci		  0x02, 0x67, 0x02, 0x68, 0x02, 0xA8, 0x02, 0xF0),
29562306a36Sopenharmony_ci	_INIT_CMD(0xD3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8C,
29662306a36Sopenharmony_ci		  0x03, 0xA6, 0x03, 0xC7, 0x03, 0xDE, 0x03, 0xEC),
29762306a36Sopenharmony_ci	_INIT_CMD(0xD4, 0x03, 0xFF, 0x03, 0xFF),
29862306a36Sopenharmony_ci	_INIT_CMD(0xE0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xC5, 0x00, 0xE5,
29962306a36Sopenharmony_ci		  0x00, 0xFF, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75),
30062306a36Sopenharmony_ci	_INIT_CMD(0xE1, 0x01, 0x9C, 0x01, 0xD5, 0x02, 0x05, 0x02, 0x4D,
30162306a36Sopenharmony_ci		  0x02, 0x86, 0x02, 0x87, 0x02, 0xC3, 0x03, 0x03),
30262306a36Sopenharmony_ci	_INIT_CMD(0xE2, 0x03, 0x2A, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94,
30362306a36Sopenharmony_ci		  0x03, 0xAC, 0x03, 0xCB, 0x03, 0xE0, 0x03, 0xED),
30462306a36Sopenharmony_ci	_INIT_CMD(0xE3, 0x03, 0xFF, 0x03, 0xFF),
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	/* page 3 */
30762306a36Sopenharmony_ci	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03),
30862306a36Sopenharmony_ci	_INIT_CMD(0xB0, 0x00, 0x00, 0x00, 0x00),
30962306a36Sopenharmony_ci	_INIT_CMD(0xB1, 0x00, 0x00, 0x00, 0x00),
31062306a36Sopenharmony_ci	_INIT_CMD(0xB2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85),
31162306a36Sopenharmony_ci	_INIT_CMD(0xB3, 0x10, 0x07, 0xFC, 0x04, 0x01, 0x40, 0x80),
31262306a36Sopenharmony_ci	_INIT_CMD(0xB6, 0xF0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
31362306a36Sopenharmony_ci		  0x40, 0x80),
31462306a36Sopenharmony_ci	_INIT_CMD(0xBA, 0xC5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8C),
31562306a36Sopenharmony_ci	_INIT_CMD(0xBB, 0xC5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8C),
31662306a36Sopenharmony_ci	_INIT_CMD(0xC0, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
31762306a36Sopenharmony_ci	_INIT_CMD(0xC1, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
31862306a36Sopenharmony_ci	_INIT_CMD(0xC4, 0x00, 0x00),
31962306a36Sopenharmony_ci	_INIT_CMD(0xEF, 0x41),
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	/* page 4 */
32262306a36Sopenharmony_ci	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04),
32362306a36Sopenharmony_ci	_INIT_CMD(0xEC, 0x4C),
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	/* page 5 */
32662306a36Sopenharmony_ci	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x05),
32762306a36Sopenharmony_ci	_INIT_CMD(0xB0, 0x13, 0x03, 0x03, 0x01),
32862306a36Sopenharmony_ci	_INIT_CMD(0xB1, 0x30, 0x00),
32962306a36Sopenharmony_ci	_INIT_CMD(0xB2, 0x02, 0x02, 0x00),
33062306a36Sopenharmony_ci	_INIT_CMD(0xB3, 0x82, 0x23, 0x82, 0x9D),
33162306a36Sopenharmony_ci	_INIT_CMD(0xB4, 0xC5, 0x75, 0x24, 0x57),
33262306a36Sopenharmony_ci	_INIT_CMD(0xB5, 0x00, 0xD4, 0x72, 0x11, 0x11, 0xAB, 0x0A),
33362306a36Sopenharmony_ci	_INIT_CMD(0xB6, 0x00, 0x00, 0xD5, 0x72, 0x24, 0x56),
33462306a36Sopenharmony_ci	_INIT_CMD(0xB7, 0x5C, 0xDC, 0x5C, 0x5C),
33562306a36Sopenharmony_ci	_INIT_CMD(0xB9, 0x0C, 0x00, 0x00, 0x01, 0x00),
33662306a36Sopenharmony_ci	_INIT_CMD(0xC0, 0x75, 0x11, 0x11, 0x54, 0x05),
33762306a36Sopenharmony_ci	_INIT_CMD(0xC6, 0x00, 0x00, 0x00, 0x00),
33862306a36Sopenharmony_ci	_INIT_CMD(0xD0, 0x00, 0x48, 0x08, 0x00, 0x00),
33962306a36Sopenharmony_ci	_INIT_CMD(0xD1, 0x00, 0x48, 0x09, 0x00, 0x00),
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	/* page 6 */
34262306a36Sopenharmony_ci	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x06),
34362306a36Sopenharmony_ci	_INIT_CMD(0xB0, 0x02, 0x32, 0x32, 0x08, 0x2F),
34462306a36Sopenharmony_ci	_INIT_CMD(0xB1, 0x2E, 0x15, 0x14, 0x13, 0x12),
34562306a36Sopenharmony_ci	_INIT_CMD(0xB2, 0x11, 0x10, 0x00, 0x3D, 0x3D),
34662306a36Sopenharmony_ci	_INIT_CMD(0xB3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
34762306a36Sopenharmony_ci	_INIT_CMD(0xB4, 0x3D, 0x32),
34862306a36Sopenharmony_ci	_INIT_CMD(0xB5, 0x03, 0x32, 0x32, 0x09, 0x2F),
34962306a36Sopenharmony_ci	_INIT_CMD(0xB6, 0x2E, 0x1B, 0x1A, 0x19, 0x18),
35062306a36Sopenharmony_ci	_INIT_CMD(0xB7, 0x17, 0x16, 0x01, 0x3D, 0x3D),
35162306a36Sopenharmony_ci	_INIT_CMD(0xB8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
35262306a36Sopenharmony_ci	_INIT_CMD(0xB9, 0x3D, 0x32),
35362306a36Sopenharmony_ci	_INIT_CMD(0xC0, 0x01, 0x32, 0x32, 0x09, 0x2F),
35462306a36Sopenharmony_ci	_INIT_CMD(0xC1, 0x2E, 0x1A, 0x1B, 0x16, 0x17),
35562306a36Sopenharmony_ci	_INIT_CMD(0xC2, 0x18, 0x19, 0x03, 0x3D, 0x3D),
35662306a36Sopenharmony_ci	_INIT_CMD(0xC3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
35762306a36Sopenharmony_ci	_INIT_CMD(0xC4, 0x3D, 0x32),
35862306a36Sopenharmony_ci	_INIT_CMD(0xC5, 0x00, 0x32, 0x32, 0x08, 0x2F),
35962306a36Sopenharmony_ci	_INIT_CMD(0xC6, 0x2E, 0x14, 0x15, 0x10, 0x11),
36062306a36Sopenharmony_ci	_INIT_CMD(0xC7, 0x12, 0x13, 0x02, 0x3D, 0x3D),
36162306a36Sopenharmony_ci	_INIT_CMD(0xC8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
36262306a36Sopenharmony_ci	_INIT_CMD(0xC9, 0x3D, 0x32),
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	{},
36562306a36Sopenharmony_ci};
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_cistatic const struct panel_desc innolux_p097pfg_panel_desc = {
36862306a36Sopenharmony_ci	.mode = &innolux_p097pfg_mode,
36962306a36Sopenharmony_ci	.bpc = 8,
37062306a36Sopenharmony_ci	.size = {
37162306a36Sopenharmony_ci		.width = 147,
37262306a36Sopenharmony_ci		.height = 196,
37362306a36Sopenharmony_ci	},
37462306a36Sopenharmony_ci	.flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
37562306a36Sopenharmony_ci		 MIPI_DSI_MODE_LPM,
37662306a36Sopenharmony_ci	.format = MIPI_DSI_FMT_RGB888,
37762306a36Sopenharmony_ci	.init_cmds = innolux_p097pfg_init_cmds,
37862306a36Sopenharmony_ci	.lanes = 4,
37962306a36Sopenharmony_ci	.supply_names = innolux_p097pfg_supply_names,
38062306a36Sopenharmony_ci	.num_supplies = ARRAY_SIZE(innolux_p097pfg_supply_names),
38162306a36Sopenharmony_ci	.sleep_mode_delay = 100, /* T15 */
38262306a36Sopenharmony_ci};
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_cistatic int innolux_panel_get_modes(struct drm_panel *panel,
38562306a36Sopenharmony_ci				   struct drm_connector *connector)
38662306a36Sopenharmony_ci{
38762306a36Sopenharmony_ci	struct innolux_panel *innolux = to_innolux_panel(panel);
38862306a36Sopenharmony_ci	const struct drm_display_mode *m = innolux->desc->mode;
38962306a36Sopenharmony_ci	struct drm_display_mode *mode;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	mode = drm_mode_duplicate(connector->dev, m);
39262306a36Sopenharmony_ci	if (!mode) {
39362306a36Sopenharmony_ci		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
39462306a36Sopenharmony_ci			m->hdisplay, m->vdisplay, drm_mode_vrefresh(m));
39562306a36Sopenharmony_ci		return -ENOMEM;
39662306a36Sopenharmony_ci	}
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	drm_mode_set_name(mode);
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	drm_mode_probed_add(connector, mode);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	connector->display_info.width_mm = innolux->desc->size.width;
40362306a36Sopenharmony_ci	connector->display_info.height_mm = innolux->desc->size.height;
40462306a36Sopenharmony_ci	connector->display_info.bpc = innolux->desc->bpc;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	return 1;
40762306a36Sopenharmony_ci}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_cistatic const struct drm_panel_funcs innolux_panel_funcs = {
41062306a36Sopenharmony_ci	.disable = innolux_panel_disable,
41162306a36Sopenharmony_ci	.unprepare = innolux_panel_unprepare,
41262306a36Sopenharmony_ci	.prepare = innolux_panel_prepare,
41362306a36Sopenharmony_ci	.enable = innolux_panel_enable,
41462306a36Sopenharmony_ci	.get_modes = innolux_panel_get_modes,
41562306a36Sopenharmony_ci};
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_cistatic const struct of_device_id innolux_of_match[] = {
41862306a36Sopenharmony_ci	{ .compatible = "innolux,p079zca",
41962306a36Sopenharmony_ci	  .data = &innolux_p079zca_panel_desc
42062306a36Sopenharmony_ci	},
42162306a36Sopenharmony_ci	{ .compatible = "innolux,p097pfg",
42262306a36Sopenharmony_ci	  .data = &innolux_p097pfg_panel_desc
42362306a36Sopenharmony_ci	},
42462306a36Sopenharmony_ci	{ }
42562306a36Sopenharmony_ci};
42662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, innolux_of_match);
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_cistatic int innolux_panel_add(struct mipi_dsi_device *dsi,
42962306a36Sopenharmony_ci			     const struct panel_desc *desc)
43062306a36Sopenharmony_ci{
43162306a36Sopenharmony_ci	struct innolux_panel *innolux;
43262306a36Sopenharmony_ci	struct device *dev = &dsi->dev;
43362306a36Sopenharmony_ci	int err, i;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL);
43662306a36Sopenharmony_ci	if (!innolux)
43762306a36Sopenharmony_ci		return -ENOMEM;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	innolux->desc = desc;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	innolux->supplies = devm_kcalloc(dev, desc->num_supplies,
44262306a36Sopenharmony_ci					 sizeof(*innolux->supplies),
44362306a36Sopenharmony_ci					 GFP_KERNEL);
44462306a36Sopenharmony_ci	if (!innolux->supplies)
44562306a36Sopenharmony_ci		return -ENOMEM;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	for (i = 0; i < desc->num_supplies; i++)
44862306a36Sopenharmony_ci		innolux->supplies[i].supply = desc->supply_names[i];
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	err = devm_regulator_bulk_get(dev, desc->num_supplies,
45162306a36Sopenharmony_ci				      innolux->supplies);
45262306a36Sopenharmony_ci	if (err < 0)
45362306a36Sopenharmony_ci		return err;
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable",
45662306a36Sopenharmony_ci						       GPIOD_OUT_HIGH);
45762306a36Sopenharmony_ci	if (IS_ERR(innolux->enable_gpio)) {
45862306a36Sopenharmony_ci		err = PTR_ERR(innolux->enable_gpio);
45962306a36Sopenharmony_ci		dev_dbg(dev, "failed to get enable gpio: %d\n", err);
46062306a36Sopenharmony_ci		innolux->enable_gpio = NULL;
46162306a36Sopenharmony_ci	}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	drm_panel_init(&innolux->base, dev, &innolux_panel_funcs,
46462306a36Sopenharmony_ci		       DRM_MODE_CONNECTOR_DSI);
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci	err = drm_panel_of_backlight(&innolux->base);
46762306a36Sopenharmony_ci	if (err)
46862306a36Sopenharmony_ci		return err;
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	drm_panel_add(&innolux->base);
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	mipi_dsi_set_drvdata(dsi, innolux);
47362306a36Sopenharmony_ci	innolux->link = dsi;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	return 0;
47662306a36Sopenharmony_ci}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_cistatic void innolux_panel_del(struct innolux_panel *innolux)
47962306a36Sopenharmony_ci{
48062306a36Sopenharmony_ci	drm_panel_remove(&innolux->base);
48162306a36Sopenharmony_ci}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_cistatic int innolux_panel_probe(struct mipi_dsi_device *dsi)
48462306a36Sopenharmony_ci{
48562306a36Sopenharmony_ci	const struct panel_desc *desc;
48662306a36Sopenharmony_ci	struct innolux_panel *innolux;
48762306a36Sopenharmony_ci	int err;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	desc = of_device_get_match_data(&dsi->dev);
49062306a36Sopenharmony_ci	dsi->mode_flags = desc->flags;
49162306a36Sopenharmony_ci	dsi->format = desc->format;
49262306a36Sopenharmony_ci	dsi->lanes = desc->lanes;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	err = innolux_panel_add(dsi, desc);
49562306a36Sopenharmony_ci	if (err < 0)
49662306a36Sopenharmony_ci		return err;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	err = mipi_dsi_attach(dsi);
49962306a36Sopenharmony_ci	if (err < 0) {
50062306a36Sopenharmony_ci		innolux = mipi_dsi_get_drvdata(dsi);
50162306a36Sopenharmony_ci		innolux_panel_del(innolux);
50262306a36Sopenharmony_ci		return err;
50362306a36Sopenharmony_ci	}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	return 0;
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_cistatic void innolux_panel_remove(struct mipi_dsi_device *dsi)
50962306a36Sopenharmony_ci{
51062306a36Sopenharmony_ci	struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
51162306a36Sopenharmony_ci	int err;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	err = drm_panel_unprepare(&innolux->base);
51462306a36Sopenharmony_ci	if (err < 0)
51562306a36Sopenharmony_ci		dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	err = drm_panel_disable(&innolux->base);
51862306a36Sopenharmony_ci	if (err < 0)
51962306a36Sopenharmony_ci		dev_err(&dsi->dev, "failed to disable panel: %d\n", err);
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	err = mipi_dsi_detach(dsi);
52262306a36Sopenharmony_ci	if (err < 0)
52362306a36Sopenharmony_ci		dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	innolux_panel_del(innolux);
52662306a36Sopenharmony_ci}
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_cistatic void innolux_panel_shutdown(struct mipi_dsi_device *dsi)
52962306a36Sopenharmony_ci{
53062306a36Sopenharmony_ci	struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	drm_panel_unprepare(&innolux->base);
53362306a36Sopenharmony_ci	drm_panel_disable(&innolux->base);
53462306a36Sopenharmony_ci}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_cistatic struct mipi_dsi_driver innolux_panel_driver = {
53762306a36Sopenharmony_ci	.driver = {
53862306a36Sopenharmony_ci		.name = "panel-innolux-p079zca",
53962306a36Sopenharmony_ci		.of_match_table = innolux_of_match,
54062306a36Sopenharmony_ci	},
54162306a36Sopenharmony_ci	.probe = innolux_panel_probe,
54262306a36Sopenharmony_ci	.remove = innolux_panel_remove,
54362306a36Sopenharmony_ci	.shutdown = innolux_panel_shutdown,
54462306a36Sopenharmony_ci};
54562306a36Sopenharmony_cimodule_mipi_dsi_driver(innolux_panel_driver);
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ciMODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
54862306a36Sopenharmony_ciMODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
54962306a36Sopenharmony_ciMODULE_DESCRIPTION("Innolux P079ZCA panel driver");
55062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
551