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