114c8f2e9SChris Zhong /* 214c8f2e9SChris Zhong * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd 314c8f2e9SChris Zhong * 414c8f2e9SChris Zhong * This program is free software; you can redistribute it and/or modify 514c8f2e9SChris Zhong * it under the terms of the GNU General Public License as published by 614c8f2e9SChris Zhong * the Free Software Foundation; either version 2 of the License, or 714c8f2e9SChris Zhong * (at your option) any later version. 814c8f2e9SChris Zhong */ 914c8f2e9SChris Zhong 1014c8f2e9SChris Zhong #include <linux/backlight.h> 11*cb23eae3SSam Ravnborg #include <linux/delay.h> 1214c8f2e9SChris Zhong #include <linux/gpio/consumer.h> 1314c8f2e9SChris Zhong #include <linux/module.h> 1414c8f2e9SChris Zhong #include <linux/of.h> 15b6d83fccSThierry Reding #include <linux/of_device.h> 1614c8f2e9SChris Zhong #include <linux/regulator/consumer.h> 1714c8f2e9SChris Zhong 1814c8f2e9SChris Zhong #include <video/mipi_display.h> 1914c8f2e9SChris Zhong 20*cb23eae3SSam Ravnborg #include <drm/drm_crtc.h> 21*cb23eae3SSam Ravnborg #include <drm/drm_device.h> 22*cb23eae3SSam Ravnborg #include <drm/drm_mipi_dsi.h> 23*cb23eae3SSam Ravnborg #include <drm/drm_modes.h> 24*cb23eae3SSam Ravnborg #include <drm/drm_panel.h> 25*cb23eae3SSam Ravnborg #include <drm/drm_print.h> 26*cb23eae3SSam Ravnborg 27de04a462SLin Huang struct panel_init_cmd { 28de04a462SLin Huang size_t len; 29de04a462SLin Huang const char *data; 30de04a462SLin Huang }; 31de04a462SLin Huang 32de04a462SLin Huang #define _INIT_CMD(...) { \ 33de04a462SLin Huang .len = sizeof((char[]){__VA_ARGS__}), \ 34de04a462SLin Huang .data = (char[]){__VA_ARGS__} } 35de04a462SLin Huang 367ad4e463SLin Huang struct panel_desc { 377ad4e463SLin Huang const struct drm_display_mode *mode; 387ad4e463SLin Huang unsigned int bpc; 397ad4e463SLin Huang struct { 407ad4e463SLin Huang unsigned int width; 417ad4e463SLin Huang unsigned int height; 427ad4e463SLin Huang } size; 437ad4e463SLin Huang 447ad4e463SLin Huang unsigned long flags; 457ad4e463SLin Huang enum mipi_dsi_pixel_format format; 46de04a462SLin Huang const struct panel_init_cmd *init_cmds; 477ad4e463SLin Huang unsigned int lanes; 487ad4e463SLin Huang const char * const *supply_names; 497ad4e463SLin Huang unsigned int num_supplies; 5048bd379aSLin Huang unsigned int sleep_mode_delay; 5148bd379aSLin Huang unsigned int power_down_delay; 527ad4e463SLin Huang }; 537ad4e463SLin Huang 5414c8f2e9SChris Zhong struct innolux_panel { 5514c8f2e9SChris Zhong struct drm_panel base; 5614c8f2e9SChris Zhong struct mipi_dsi_device *link; 577ad4e463SLin Huang const struct panel_desc *desc; 5814c8f2e9SChris Zhong 5914c8f2e9SChris Zhong struct backlight_device *backlight; 607ad4e463SLin Huang struct regulator_bulk_data *supplies; 6114c8f2e9SChris Zhong struct gpio_desc *enable_gpio; 6214c8f2e9SChris Zhong 6314c8f2e9SChris Zhong bool prepared; 6414c8f2e9SChris Zhong bool enabled; 6514c8f2e9SChris Zhong }; 6614c8f2e9SChris Zhong 6714c8f2e9SChris Zhong static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel) 6814c8f2e9SChris Zhong { 6914c8f2e9SChris Zhong return container_of(panel, struct innolux_panel, base); 7014c8f2e9SChris Zhong } 7114c8f2e9SChris Zhong 7214c8f2e9SChris Zhong static int innolux_panel_disable(struct drm_panel *panel) 7314c8f2e9SChris Zhong { 7414c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 7514c8f2e9SChris Zhong 7614c8f2e9SChris Zhong if (!innolux->enabled) 7714c8f2e9SChris Zhong return 0; 7814c8f2e9SChris Zhong 79d593bfdbSMeghana Madhyastha backlight_disable(innolux->backlight); 8014c8f2e9SChris Zhong 8114c8f2e9SChris Zhong innolux->enabled = false; 8214c8f2e9SChris Zhong 8314c8f2e9SChris Zhong return 0; 8414c8f2e9SChris Zhong } 8514c8f2e9SChris Zhong 8614c8f2e9SChris Zhong static int innolux_panel_unprepare(struct drm_panel *panel) 8714c8f2e9SChris Zhong { 8814c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 8914c8f2e9SChris Zhong int err; 9014c8f2e9SChris Zhong 9114c8f2e9SChris Zhong if (!innolux->prepared) 9214c8f2e9SChris Zhong return 0; 9314c8f2e9SChris Zhong 9446f3ceafSHsin-Yi, Wang err = mipi_dsi_dcs_set_display_off(innolux->link); 9546f3ceafSHsin-Yi, Wang if (err < 0) 9646f3ceafSHsin-Yi, Wang DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", 9746f3ceafSHsin-Yi, Wang err); 9846f3ceafSHsin-Yi, Wang 9914c8f2e9SChris Zhong err = mipi_dsi_dcs_enter_sleep_mode(innolux->link); 10014c8f2e9SChris Zhong if (err < 0) { 10114c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n", 10214c8f2e9SChris Zhong err); 10314c8f2e9SChris Zhong return err; 10414c8f2e9SChris Zhong } 10514c8f2e9SChris Zhong 10648bd379aSLin Huang if (innolux->desc->sleep_mode_delay) 10748bd379aSLin Huang msleep(innolux->desc->sleep_mode_delay); 10848bd379aSLin Huang 10914c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 11014c8f2e9SChris Zhong 11148bd379aSLin Huang if (innolux->desc->power_down_delay) 11248bd379aSLin Huang msleep(innolux->desc->power_down_delay); 11314c8f2e9SChris Zhong 1147ad4e463SLin Huang err = regulator_bulk_disable(innolux->desc->num_supplies, 1157ad4e463SLin Huang innolux->supplies); 11614c8f2e9SChris Zhong if (err < 0) 11714c8f2e9SChris Zhong return err; 11814c8f2e9SChris Zhong 11914c8f2e9SChris Zhong innolux->prepared = false; 12014c8f2e9SChris Zhong 12114c8f2e9SChris Zhong return 0; 12214c8f2e9SChris Zhong } 12314c8f2e9SChris Zhong 12414c8f2e9SChris Zhong static int innolux_panel_prepare(struct drm_panel *panel) 12514c8f2e9SChris Zhong { 12614c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 1277ad4e463SLin Huang int err; 12814c8f2e9SChris Zhong 12914c8f2e9SChris Zhong if (innolux->prepared) 13014c8f2e9SChris Zhong return 0; 13114c8f2e9SChris Zhong 13214c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 13314c8f2e9SChris Zhong 1347ad4e463SLin Huang err = regulator_bulk_enable(innolux->desc->num_supplies, 1357ad4e463SLin Huang innolux->supplies); 13614c8f2e9SChris Zhong if (err < 0) 13714c8f2e9SChris Zhong return err; 13814c8f2e9SChris Zhong 139de04a462SLin Huang /* p079zca: t2 (20ms), p097pfg: t4 (15ms) */ 140de04a462SLin Huang usleep_range(20000, 21000); 14114c8f2e9SChris Zhong 14214c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 1); 14314c8f2e9SChris Zhong 144de04a462SLin Huang /* p079zca: t4, p097pfg: t5 */ 145de04a462SLin Huang usleep_range(20000, 21000); 146de04a462SLin Huang 147de04a462SLin Huang if (innolux->desc->init_cmds) { 148de04a462SLin Huang const struct panel_init_cmd *cmds = 149de04a462SLin Huang innolux->desc->init_cmds; 150de04a462SLin Huang unsigned int i; 151de04a462SLin Huang 152de04a462SLin Huang for (i = 0; cmds[i].len != 0; i++) { 153de04a462SLin Huang const struct panel_init_cmd *cmd = &cmds[i]; 154de04a462SLin Huang 155de04a462SLin Huang err = mipi_dsi_generic_write(innolux->link, cmd->data, 156de04a462SLin Huang cmd->len); 157de04a462SLin Huang if (err < 0) { 158de04a462SLin Huang dev_err(panel->dev, 159de04a462SLin Huang "failed to write command %u\n", i); 160de04a462SLin Huang goto poweroff; 161de04a462SLin Huang } 162de04a462SLin Huang 163de04a462SLin Huang /* 164de04a462SLin Huang * Included by random guessing, because without this 165de04a462SLin Huang * (or at least, some delay), the panel sometimes 166de04a462SLin Huang * didn't appear to pick up the command sequence. 167de04a462SLin Huang */ 168de04a462SLin Huang err = mipi_dsi_dcs_nop(innolux->link); 169de04a462SLin Huang if (err < 0) { 170de04a462SLin Huang dev_err(panel->dev, 171de04a462SLin Huang "failed to send DCS nop: %d\n", err); 172de04a462SLin Huang goto poweroff; 173de04a462SLin Huang } 174de04a462SLin Huang } 175de04a462SLin Huang } 17614c8f2e9SChris Zhong 17714c8f2e9SChris Zhong err = mipi_dsi_dcs_exit_sleep_mode(innolux->link); 17814c8f2e9SChris Zhong if (err < 0) { 17914c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n", 18014c8f2e9SChris Zhong err); 18114c8f2e9SChris Zhong goto poweroff; 18214c8f2e9SChris Zhong } 18314c8f2e9SChris Zhong 18414c8f2e9SChris Zhong /* T6: 120ms - 1000ms*/ 18514c8f2e9SChris Zhong msleep(120); 18614c8f2e9SChris Zhong 18714c8f2e9SChris Zhong err = mipi_dsi_dcs_set_display_on(innolux->link); 18814c8f2e9SChris Zhong if (err < 0) { 18914c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n", 19014c8f2e9SChris Zhong err); 19114c8f2e9SChris Zhong goto poweroff; 19214c8f2e9SChris Zhong } 19314c8f2e9SChris Zhong 19414c8f2e9SChris Zhong /* T7: 5ms */ 19514c8f2e9SChris Zhong usleep_range(5000, 6000); 19614c8f2e9SChris Zhong 19714c8f2e9SChris Zhong innolux->prepared = true; 19814c8f2e9SChris Zhong 19914c8f2e9SChris Zhong return 0; 20014c8f2e9SChris Zhong 20114c8f2e9SChris Zhong poweroff: 20214c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 2037ad4e463SLin Huang regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies); 2047ad4e463SLin Huang 20514c8f2e9SChris Zhong return err; 20614c8f2e9SChris Zhong } 20714c8f2e9SChris Zhong 20814c8f2e9SChris Zhong static int innolux_panel_enable(struct drm_panel *panel) 20914c8f2e9SChris Zhong { 21014c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 21114c8f2e9SChris Zhong int ret; 21214c8f2e9SChris Zhong 21314c8f2e9SChris Zhong if (innolux->enabled) 21414c8f2e9SChris Zhong return 0; 21514c8f2e9SChris Zhong 216d593bfdbSMeghana Madhyastha ret = backlight_enable(innolux->backlight); 21714c8f2e9SChris Zhong if (ret) { 21814c8f2e9SChris Zhong DRM_DEV_ERROR(panel->drm->dev, 21914c8f2e9SChris Zhong "Failed to enable backlight %d\n", ret); 22014c8f2e9SChris Zhong return ret; 22114c8f2e9SChris Zhong } 22214c8f2e9SChris Zhong 22314c8f2e9SChris Zhong innolux->enabled = true; 22414c8f2e9SChris Zhong 22514c8f2e9SChris Zhong return 0; 22614c8f2e9SChris Zhong } 22714c8f2e9SChris Zhong 2287ad4e463SLin Huang static const char * const innolux_p079zca_supply_names[] = { 2297ad4e463SLin Huang "power", 2307ad4e463SLin Huang }; 2317ad4e463SLin Huang 2327ad4e463SLin Huang static const struct drm_display_mode innolux_p079zca_mode = { 23314c8f2e9SChris Zhong .clock = 56900, 23414c8f2e9SChris Zhong .hdisplay = 768, 23514c8f2e9SChris Zhong .hsync_start = 768 + 40, 23614c8f2e9SChris Zhong .hsync_end = 768 + 40 + 40, 23714c8f2e9SChris Zhong .htotal = 768 + 40 + 40 + 40, 23814c8f2e9SChris Zhong .vdisplay = 1024, 23914c8f2e9SChris Zhong .vsync_start = 1024 + 20, 24014c8f2e9SChris Zhong .vsync_end = 1024 + 20 + 4, 24114c8f2e9SChris Zhong .vtotal = 1024 + 20 + 4 + 20, 24214c8f2e9SChris Zhong .vrefresh = 60, 24314c8f2e9SChris Zhong }; 24414c8f2e9SChris Zhong 2457ad4e463SLin Huang static const struct panel_desc innolux_p079zca_panel_desc = { 2467ad4e463SLin Huang .mode = &innolux_p079zca_mode, 2477ad4e463SLin Huang .bpc = 8, 2487ad4e463SLin Huang .size = { 2497ad4e463SLin Huang .width = 120, 2507ad4e463SLin Huang .height = 160, 2517ad4e463SLin Huang }, 2527ad4e463SLin Huang .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | 2537ad4e463SLin Huang MIPI_DSI_MODE_LPM, 2547ad4e463SLin Huang .format = MIPI_DSI_FMT_RGB888, 2557ad4e463SLin Huang .lanes = 4, 2567ad4e463SLin Huang .supply_names = innolux_p079zca_supply_names, 2577ad4e463SLin Huang .num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names), 25848bd379aSLin Huang .power_down_delay = 80, /* T8: 80ms - 1000ms */ 2597ad4e463SLin Huang }; 2607ad4e463SLin Huang 261de04a462SLin Huang static const char * const innolux_p097pfg_supply_names[] = { 262de04a462SLin Huang "avdd", 263de04a462SLin Huang "avee", 264de04a462SLin Huang }; 265de04a462SLin Huang 266de04a462SLin Huang static const struct drm_display_mode innolux_p097pfg_mode = { 267de04a462SLin Huang .clock = 229000, 268de04a462SLin Huang .hdisplay = 1536, 269de04a462SLin Huang .hsync_start = 1536 + 100, 270de04a462SLin Huang .hsync_end = 1536 + 100 + 24, 271de04a462SLin Huang .htotal = 1536 + 100 + 24 + 100, 272de04a462SLin Huang .vdisplay = 2048, 273de04a462SLin Huang .vsync_start = 2048 + 100, 274de04a462SLin Huang .vsync_end = 2048 + 100 + 2, 275de04a462SLin Huang .vtotal = 2048 + 100 + 2 + 18, 276de04a462SLin Huang .vrefresh = 60, 277de04a462SLin Huang }; 278de04a462SLin Huang 279de04a462SLin Huang /* 280de04a462SLin Huang * Display manufacturer failed to provide init sequencing according to 281de04a462SLin Huang * https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/892065/ 282de04a462SLin Huang * so the init sequence stems from a register dump of a working panel. 283de04a462SLin Huang */ 284de04a462SLin Huang static const struct panel_init_cmd innolux_p097pfg_init_cmds[] = { 285de04a462SLin Huang /* page 0 */ 286de04a462SLin Huang _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00), 287de04a462SLin Huang _INIT_CMD(0xB1, 0xE8, 0x11), 288de04a462SLin Huang _INIT_CMD(0xB2, 0x25, 0x02), 289de04a462SLin Huang _INIT_CMD(0xB5, 0x08, 0x00), 290de04a462SLin Huang _INIT_CMD(0xBC, 0x0F, 0x00), 291de04a462SLin Huang _INIT_CMD(0xB8, 0x03, 0x06, 0x00, 0x00), 292de04a462SLin Huang _INIT_CMD(0xBD, 0x01, 0x90, 0x14, 0x14), 293de04a462SLin Huang _INIT_CMD(0x6F, 0x01), 294de04a462SLin Huang _INIT_CMD(0xC0, 0x03), 295de04a462SLin Huang _INIT_CMD(0x6F, 0x02), 296de04a462SLin Huang _INIT_CMD(0xC1, 0x0D), 297de04a462SLin Huang _INIT_CMD(0xD9, 0x01, 0x09, 0x70), 298de04a462SLin Huang _INIT_CMD(0xC5, 0x12, 0x21, 0x00), 299de04a462SLin Huang _INIT_CMD(0xBB, 0x93, 0x93), 300de04a462SLin Huang 301de04a462SLin Huang /* page 1 */ 302de04a462SLin Huang _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01), 303de04a462SLin Huang _INIT_CMD(0xB3, 0x3C, 0x3C), 304de04a462SLin Huang _INIT_CMD(0xB4, 0x0F, 0x0F), 305de04a462SLin Huang _INIT_CMD(0xB9, 0x45, 0x45), 306de04a462SLin Huang _INIT_CMD(0xBA, 0x14, 0x14), 307de04a462SLin Huang _INIT_CMD(0xCA, 0x02), 308de04a462SLin Huang _INIT_CMD(0xCE, 0x04), 309de04a462SLin Huang _INIT_CMD(0xC3, 0x9B, 0x9B), 310de04a462SLin Huang _INIT_CMD(0xD8, 0xC0, 0x03), 311de04a462SLin Huang _INIT_CMD(0xBC, 0x82, 0x01), 312de04a462SLin Huang _INIT_CMD(0xBD, 0x9E, 0x01), 313de04a462SLin Huang 314de04a462SLin Huang /* page 2 */ 315de04a462SLin Huang _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x02), 316de04a462SLin Huang _INIT_CMD(0xB0, 0x82), 317de04a462SLin Huang _INIT_CMD(0xD1, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x82, 0x00, 0xA5, 318de04a462SLin Huang 0x00, 0xC1, 0x00, 0xEA, 0x01, 0x0D, 0x01, 0x40), 319de04a462SLin Huang _INIT_CMD(0xD2, 0x01, 0x6A, 0x01, 0xA8, 0x01, 0xDC, 0x02, 0x29, 320de04a462SLin Huang 0x02, 0x67, 0x02, 0x68, 0x02, 0xA8, 0x02, 0xF0), 321de04a462SLin Huang _INIT_CMD(0xD3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8C, 322de04a462SLin Huang 0x03, 0xA6, 0x03, 0xC7, 0x03, 0xDE, 0x03, 0xEC), 323de04a462SLin Huang _INIT_CMD(0xD4, 0x03, 0xFF, 0x03, 0xFF), 324de04a462SLin Huang _INIT_CMD(0xE0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xC5, 0x00, 0xE5, 325de04a462SLin Huang 0x00, 0xFF, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75), 326de04a462SLin Huang _INIT_CMD(0xE1, 0x01, 0x9C, 0x01, 0xD5, 0x02, 0x05, 0x02, 0x4D, 327de04a462SLin Huang 0x02, 0x86, 0x02, 0x87, 0x02, 0xC3, 0x03, 0x03), 328de04a462SLin Huang _INIT_CMD(0xE2, 0x03, 0x2A, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94, 329de04a462SLin Huang 0x03, 0xAC, 0x03, 0xCB, 0x03, 0xE0, 0x03, 0xED), 330de04a462SLin Huang _INIT_CMD(0xE3, 0x03, 0xFF, 0x03, 0xFF), 331de04a462SLin Huang 332de04a462SLin Huang /* page 3 */ 333de04a462SLin Huang _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03), 334de04a462SLin Huang _INIT_CMD(0xB0, 0x00, 0x00, 0x00, 0x00), 335de04a462SLin Huang _INIT_CMD(0xB1, 0x00, 0x00, 0x00, 0x00), 336de04a462SLin Huang _INIT_CMD(0xB2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85), 337de04a462SLin Huang _INIT_CMD(0xB3, 0x10, 0x07, 0xFC, 0x04, 0x01, 0x40, 0x80), 338de04a462SLin Huang _INIT_CMD(0xB6, 0xF0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 339de04a462SLin Huang 0x40, 0x80), 340de04a462SLin Huang _INIT_CMD(0xBA, 0xC5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8C), 341de04a462SLin Huang _INIT_CMD(0xBB, 0xC5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8C), 342de04a462SLin Huang _INIT_CMD(0xC0, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80), 343de04a462SLin Huang _INIT_CMD(0xC1, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80), 344de04a462SLin Huang _INIT_CMD(0xC4, 0x00, 0x00), 345de04a462SLin Huang _INIT_CMD(0xEF, 0x41), 346de04a462SLin Huang 347de04a462SLin Huang /* page 4 */ 348de04a462SLin Huang _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04), 349de04a462SLin Huang _INIT_CMD(0xEC, 0x4C), 350de04a462SLin Huang 351de04a462SLin Huang /* page 5 */ 352de04a462SLin Huang _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x05), 353de04a462SLin Huang _INIT_CMD(0xB0, 0x13, 0x03, 0x03, 0x01), 354de04a462SLin Huang _INIT_CMD(0xB1, 0x30, 0x00), 355de04a462SLin Huang _INIT_CMD(0xB2, 0x02, 0x02, 0x00), 356de04a462SLin Huang _INIT_CMD(0xB3, 0x82, 0x23, 0x82, 0x9D), 357de04a462SLin Huang _INIT_CMD(0xB4, 0xC5, 0x75, 0x24, 0x57), 358de04a462SLin Huang _INIT_CMD(0xB5, 0x00, 0xD4, 0x72, 0x11, 0x11, 0xAB, 0x0A), 359de04a462SLin Huang _INIT_CMD(0xB6, 0x00, 0x00, 0xD5, 0x72, 0x24, 0x56), 360de04a462SLin Huang _INIT_CMD(0xB7, 0x5C, 0xDC, 0x5C, 0x5C), 361de04a462SLin Huang _INIT_CMD(0xB9, 0x0C, 0x00, 0x00, 0x01, 0x00), 362de04a462SLin Huang _INIT_CMD(0xC0, 0x75, 0x11, 0x11, 0x54, 0x05), 363de04a462SLin Huang _INIT_CMD(0xC6, 0x00, 0x00, 0x00, 0x00), 364de04a462SLin Huang _INIT_CMD(0xD0, 0x00, 0x48, 0x08, 0x00, 0x00), 365de04a462SLin Huang _INIT_CMD(0xD1, 0x00, 0x48, 0x09, 0x00, 0x00), 366de04a462SLin Huang 367de04a462SLin Huang /* page 6 */ 368de04a462SLin Huang _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x06), 369de04a462SLin Huang _INIT_CMD(0xB0, 0x02, 0x32, 0x32, 0x08, 0x2F), 370de04a462SLin Huang _INIT_CMD(0xB1, 0x2E, 0x15, 0x14, 0x13, 0x12), 371de04a462SLin Huang _INIT_CMD(0xB2, 0x11, 0x10, 0x00, 0x3D, 0x3D), 372de04a462SLin Huang _INIT_CMD(0xB3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D), 373de04a462SLin Huang _INIT_CMD(0xB4, 0x3D, 0x32), 374de04a462SLin Huang _INIT_CMD(0xB5, 0x03, 0x32, 0x32, 0x09, 0x2F), 375de04a462SLin Huang _INIT_CMD(0xB6, 0x2E, 0x1B, 0x1A, 0x19, 0x18), 376de04a462SLin Huang _INIT_CMD(0xB7, 0x17, 0x16, 0x01, 0x3D, 0x3D), 377de04a462SLin Huang _INIT_CMD(0xB8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D), 378de04a462SLin Huang _INIT_CMD(0xB9, 0x3D, 0x32), 379de04a462SLin Huang _INIT_CMD(0xC0, 0x01, 0x32, 0x32, 0x09, 0x2F), 380de04a462SLin Huang _INIT_CMD(0xC1, 0x2E, 0x1A, 0x1B, 0x16, 0x17), 381de04a462SLin Huang _INIT_CMD(0xC2, 0x18, 0x19, 0x03, 0x3D, 0x3D), 382de04a462SLin Huang _INIT_CMD(0xC3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D), 383de04a462SLin Huang _INIT_CMD(0xC4, 0x3D, 0x32), 384de04a462SLin Huang _INIT_CMD(0xC5, 0x00, 0x32, 0x32, 0x08, 0x2F), 385de04a462SLin Huang _INIT_CMD(0xC6, 0x2E, 0x14, 0x15, 0x10, 0x11), 386de04a462SLin Huang _INIT_CMD(0xC7, 0x12, 0x13, 0x02, 0x3D, 0x3D), 387de04a462SLin Huang _INIT_CMD(0xC8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D), 388de04a462SLin Huang _INIT_CMD(0xC9, 0x3D, 0x32), 389de04a462SLin Huang 390de04a462SLin Huang {}, 391de04a462SLin Huang }; 392de04a462SLin Huang 393de04a462SLin Huang static const struct panel_desc innolux_p097pfg_panel_desc = { 394de04a462SLin Huang .mode = &innolux_p097pfg_mode, 395de04a462SLin Huang .bpc = 8, 396de04a462SLin Huang .size = { 397de04a462SLin Huang .width = 147, 398de04a462SLin Huang .height = 196, 399de04a462SLin Huang }, 400de04a462SLin Huang .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | 401de04a462SLin Huang MIPI_DSI_MODE_LPM, 402de04a462SLin Huang .format = MIPI_DSI_FMT_RGB888, 403de04a462SLin Huang .init_cmds = innolux_p097pfg_init_cmds, 404de04a462SLin Huang .lanes = 4, 405de04a462SLin Huang .supply_names = innolux_p097pfg_supply_names, 406de04a462SLin Huang .num_supplies = ARRAY_SIZE(innolux_p097pfg_supply_names), 407de04a462SLin Huang .sleep_mode_delay = 100, /* T15 */ 408de04a462SLin Huang }; 409de04a462SLin Huang 41014c8f2e9SChris Zhong static int innolux_panel_get_modes(struct drm_panel *panel) 41114c8f2e9SChris Zhong { 4127ad4e463SLin Huang struct innolux_panel *innolux = to_innolux_panel(panel); 4137ad4e463SLin Huang const struct drm_display_mode *m = innolux->desc->mode; 41414c8f2e9SChris Zhong struct drm_display_mode *mode; 41514c8f2e9SChris Zhong 4167ad4e463SLin Huang mode = drm_mode_duplicate(panel->drm, m); 41714c8f2e9SChris Zhong if (!mode) { 41814c8f2e9SChris Zhong DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n", 4197ad4e463SLin Huang m->hdisplay, m->vdisplay, m->vrefresh); 42014c8f2e9SChris Zhong return -ENOMEM; 42114c8f2e9SChris Zhong } 42214c8f2e9SChris Zhong 42314c8f2e9SChris Zhong drm_mode_set_name(mode); 42414c8f2e9SChris Zhong 42514c8f2e9SChris Zhong drm_mode_probed_add(panel->connector, mode); 42614c8f2e9SChris Zhong 4277ad4e463SLin Huang panel->connector->display_info.width_mm = 4287ad4e463SLin Huang innolux->desc->size.width; 4297ad4e463SLin Huang panel->connector->display_info.height_mm = 4307ad4e463SLin Huang innolux->desc->size.height; 4317ad4e463SLin Huang panel->connector->display_info.bpc = innolux->desc->bpc; 43214c8f2e9SChris Zhong 43314c8f2e9SChris Zhong return 1; 43414c8f2e9SChris Zhong } 43514c8f2e9SChris Zhong 43614c8f2e9SChris Zhong static const struct drm_panel_funcs innolux_panel_funcs = { 43714c8f2e9SChris Zhong .disable = innolux_panel_disable, 43814c8f2e9SChris Zhong .unprepare = innolux_panel_unprepare, 43914c8f2e9SChris Zhong .prepare = innolux_panel_prepare, 44014c8f2e9SChris Zhong .enable = innolux_panel_enable, 44114c8f2e9SChris Zhong .get_modes = innolux_panel_get_modes, 44214c8f2e9SChris Zhong }; 44314c8f2e9SChris Zhong 44414c8f2e9SChris Zhong static const struct of_device_id innolux_of_match[] = { 4457ad4e463SLin Huang { .compatible = "innolux,p079zca", 4467ad4e463SLin Huang .data = &innolux_p079zca_panel_desc 4477ad4e463SLin Huang }, 448de04a462SLin Huang { .compatible = "innolux,p097pfg", 449de04a462SLin Huang .data = &innolux_p097pfg_panel_desc 450de04a462SLin Huang }, 45114c8f2e9SChris Zhong { } 45214c8f2e9SChris Zhong }; 45314c8f2e9SChris Zhong MODULE_DEVICE_TABLE(of, innolux_of_match); 45414c8f2e9SChris Zhong 4557ad4e463SLin Huang static int innolux_panel_add(struct mipi_dsi_device *dsi, 4567ad4e463SLin Huang const struct panel_desc *desc) 45714c8f2e9SChris Zhong { 4587ad4e463SLin Huang struct innolux_panel *innolux; 4597ad4e463SLin Huang struct device *dev = &dsi->dev; 4607ad4e463SLin Huang int err, i; 46114c8f2e9SChris Zhong 4627ad4e463SLin Huang innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL); 4637ad4e463SLin Huang if (!innolux) 4647ad4e463SLin Huang return -ENOMEM; 4657ad4e463SLin Huang 4667ad4e463SLin Huang innolux->desc = desc; 4677ad4e463SLin Huang 4687ad4e463SLin Huang innolux->supplies = devm_kcalloc(dev, desc->num_supplies, 4697ad4e463SLin Huang sizeof(*innolux->supplies), 4707ad4e463SLin Huang GFP_KERNEL); 4717ad4e463SLin Huang if (!innolux->supplies) 4727ad4e463SLin Huang return -ENOMEM; 4737ad4e463SLin Huang 4747ad4e463SLin Huang for (i = 0; i < desc->num_supplies; i++) 4757ad4e463SLin Huang innolux->supplies[i].supply = desc->supply_names[i]; 4767ad4e463SLin Huang 4777ad4e463SLin Huang err = devm_regulator_bulk_get(dev, desc->num_supplies, 4787ad4e463SLin Huang innolux->supplies); 4797ad4e463SLin Huang if (err < 0) 4807ad4e463SLin Huang return err; 48114c8f2e9SChris Zhong 48214c8f2e9SChris Zhong innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable", 48314c8f2e9SChris Zhong GPIOD_OUT_HIGH); 48414c8f2e9SChris Zhong if (IS_ERR(innolux->enable_gpio)) { 48514c8f2e9SChris Zhong err = PTR_ERR(innolux->enable_gpio); 48614c8f2e9SChris Zhong dev_dbg(dev, "failed to get enable gpio: %d\n", err); 48714c8f2e9SChris Zhong innolux->enable_gpio = NULL; 48814c8f2e9SChris Zhong } 48914c8f2e9SChris Zhong 490c69f9457SMeghana Madhyastha innolux->backlight = devm_of_find_backlight(dev); 491c69f9457SMeghana Madhyastha if (IS_ERR(innolux->backlight)) 492c69f9457SMeghana Madhyastha return PTR_ERR(innolux->backlight); 49314c8f2e9SChris Zhong 49414c8f2e9SChris Zhong drm_panel_init(&innolux->base); 49514c8f2e9SChris Zhong innolux->base.funcs = &innolux_panel_funcs; 4967ad4e463SLin Huang innolux->base.dev = dev; 49714c8f2e9SChris Zhong 4987ad4e463SLin Huang err = drm_panel_add(&innolux->base); 4997ad4e463SLin Huang if (err < 0) 5007ad4e463SLin Huang return err; 5017ad4e463SLin Huang 5027ad4e463SLin Huang mipi_dsi_set_drvdata(dsi, innolux); 5037ad4e463SLin Huang innolux->link = dsi; 5047ad4e463SLin Huang 5057ad4e463SLin Huang return 0; 50614c8f2e9SChris Zhong } 50714c8f2e9SChris Zhong 50814c8f2e9SChris Zhong static void innolux_panel_del(struct innolux_panel *innolux) 50914c8f2e9SChris Zhong { 51014c8f2e9SChris Zhong drm_panel_remove(&innolux->base); 51114c8f2e9SChris Zhong } 51214c8f2e9SChris Zhong 51314c8f2e9SChris Zhong static int innolux_panel_probe(struct mipi_dsi_device *dsi) 51414c8f2e9SChris Zhong { 5157ad4e463SLin Huang const struct panel_desc *desc; 51614c8f2e9SChris Zhong int err; 51714c8f2e9SChris Zhong 518b6d83fccSThierry Reding desc = of_device_get_match_data(&dsi->dev); 5197ad4e463SLin Huang dsi->mode_flags = desc->flags; 5207ad4e463SLin Huang dsi->format = desc->format; 5217ad4e463SLin Huang dsi->lanes = desc->lanes; 52214c8f2e9SChris Zhong 5237ad4e463SLin Huang err = innolux_panel_add(dsi, desc); 52414c8f2e9SChris Zhong if (err < 0) 52514c8f2e9SChris Zhong return err; 52614c8f2e9SChris Zhong 5277ad4e463SLin Huang return mipi_dsi_attach(dsi); 52814c8f2e9SChris Zhong } 52914c8f2e9SChris Zhong 53014c8f2e9SChris Zhong static int innolux_panel_remove(struct mipi_dsi_device *dsi) 53114c8f2e9SChris Zhong { 53214c8f2e9SChris Zhong struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); 53314c8f2e9SChris Zhong int err; 53414c8f2e9SChris Zhong 53514c8f2e9SChris Zhong err = innolux_panel_unprepare(&innolux->base); 53614c8f2e9SChris Zhong if (err < 0) 53714c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n", 53814c8f2e9SChris Zhong err); 53914c8f2e9SChris Zhong 54014c8f2e9SChris Zhong err = innolux_panel_disable(&innolux->base); 54114c8f2e9SChris Zhong if (err < 0) 54214c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err); 54314c8f2e9SChris Zhong 54414c8f2e9SChris Zhong err = mipi_dsi_detach(dsi); 54514c8f2e9SChris Zhong if (err < 0) 54614c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n", 54714c8f2e9SChris Zhong err); 54814c8f2e9SChris Zhong 54914c8f2e9SChris Zhong innolux_panel_del(innolux); 55014c8f2e9SChris Zhong 55114c8f2e9SChris Zhong return 0; 55214c8f2e9SChris Zhong } 55314c8f2e9SChris Zhong 55414c8f2e9SChris Zhong static void innolux_panel_shutdown(struct mipi_dsi_device *dsi) 55514c8f2e9SChris Zhong { 55614c8f2e9SChris Zhong struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); 55714c8f2e9SChris Zhong 55814c8f2e9SChris Zhong innolux_panel_unprepare(&innolux->base); 55914c8f2e9SChris Zhong innolux_panel_disable(&innolux->base); 56014c8f2e9SChris Zhong } 56114c8f2e9SChris Zhong 56214c8f2e9SChris Zhong static struct mipi_dsi_driver innolux_panel_driver = { 56314c8f2e9SChris Zhong .driver = { 56414c8f2e9SChris Zhong .name = "panel-innolux-p079zca", 56514c8f2e9SChris Zhong .of_match_table = innolux_of_match, 56614c8f2e9SChris Zhong }, 56714c8f2e9SChris Zhong .probe = innolux_panel_probe, 56814c8f2e9SChris Zhong .remove = innolux_panel_remove, 56914c8f2e9SChris Zhong .shutdown = innolux_panel_shutdown, 57014c8f2e9SChris Zhong }; 57114c8f2e9SChris Zhong module_mipi_dsi_driver(innolux_panel_driver); 57214c8f2e9SChris Zhong 57314c8f2e9SChris Zhong MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); 5747ad4e463SLin Huang MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>"); 57514c8f2e9SChris Zhong MODULE_DESCRIPTION("Innolux P079ZCA panel driver"); 57614c8f2e9SChris Zhong MODULE_LICENSE("GPL v2"); 577