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