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> 1114c8f2e9SChris Zhong #include <linux/gpio/consumer.h> 1214c8f2e9SChris Zhong #include <linux/module.h> 1314c8f2e9SChris Zhong #include <linux/of.h> 1414c8f2e9SChris Zhong #include <linux/regulator/consumer.h> 1514c8f2e9SChris Zhong 1614c8f2e9SChris Zhong #include <drm/drmP.h> 1714c8f2e9SChris Zhong #include <drm/drm_crtc.h> 1814c8f2e9SChris Zhong #include <drm/drm_mipi_dsi.h> 1914c8f2e9SChris Zhong #include <drm/drm_panel.h> 2014c8f2e9SChris Zhong 2114c8f2e9SChris Zhong #include <video/mipi_display.h> 2214c8f2e9SChris Zhong 2314c8f2e9SChris Zhong struct innolux_panel { 2414c8f2e9SChris Zhong struct drm_panel base; 2514c8f2e9SChris Zhong struct mipi_dsi_device *link; 2614c8f2e9SChris Zhong 2714c8f2e9SChris Zhong struct backlight_device *backlight; 2814c8f2e9SChris Zhong struct regulator *supply; 2914c8f2e9SChris Zhong struct gpio_desc *enable_gpio; 3014c8f2e9SChris Zhong 3114c8f2e9SChris Zhong bool prepared; 3214c8f2e9SChris Zhong bool enabled; 3314c8f2e9SChris Zhong }; 3414c8f2e9SChris Zhong 3514c8f2e9SChris Zhong static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel) 3614c8f2e9SChris Zhong { 3714c8f2e9SChris Zhong return container_of(panel, struct innolux_panel, base); 3814c8f2e9SChris Zhong } 3914c8f2e9SChris Zhong 4014c8f2e9SChris Zhong static int innolux_panel_disable(struct drm_panel *panel) 4114c8f2e9SChris Zhong { 4214c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 4314c8f2e9SChris Zhong int err; 4414c8f2e9SChris Zhong 4514c8f2e9SChris Zhong if (!innolux->enabled) 4614c8f2e9SChris Zhong return 0; 4714c8f2e9SChris Zhong 48*d593bfdbSMeghana Madhyastha backlight_disable(innolux->backlight); 4914c8f2e9SChris Zhong 5014c8f2e9SChris Zhong err = mipi_dsi_dcs_set_display_off(innolux->link); 5114c8f2e9SChris Zhong if (err < 0) 5214c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", 5314c8f2e9SChris Zhong err); 5414c8f2e9SChris Zhong 5514c8f2e9SChris Zhong innolux->enabled = false; 5614c8f2e9SChris Zhong 5714c8f2e9SChris Zhong return 0; 5814c8f2e9SChris Zhong } 5914c8f2e9SChris Zhong 6014c8f2e9SChris Zhong static int innolux_panel_unprepare(struct drm_panel *panel) 6114c8f2e9SChris Zhong { 6214c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 6314c8f2e9SChris Zhong int err; 6414c8f2e9SChris Zhong 6514c8f2e9SChris Zhong if (!innolux->prepared) 6614c8f2e9SChris Zhong return 0; 6714c8f2e9SChris Zhong 6814c8f2e9SChris Zhong err = mipi_dsi_dcs_enter_sleep_mode(innolux->link); 6914c8f2e9SChris Zhong if (err < 0) { 7014c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n", 7114c8f2e9SChris Zhong err); 7214c8f2e9SChris Zhong return err; 7314c8f2e9SChris Zhong } 7414c8f2e9SChris Zhong 7514c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 7614c8f2e9SChris Zhong 7714c8f2e9SChris Zhong /* T8: 80ms - 1000ms */ 7814c8f2e9SChris Zhong msleep(80); 7914c8f2e9SChris Zhong 8014c8f2e9SChris Zhong err = regulator_disable(innolux->supply); 8114c8f2e9SChris Zhong if (err < 0) 8214c8f2e9SChris Zhong return err; 8314c8f2e9SChris Zhong 8414c8f2e9SChris Zhong innolux->prepared = false; 8514c8f2e9SChris Zhong 8614c8f2e9SChris Zhong return 0; 8714c8f2e9SChris Zhong } 8814c8f2e9SChris Zhong 8914c8f2e9SChris Zhong static int innolux_panel_prepare(struct drm_panel *panel) 9014c8f2e9SChris Zhong { 9114c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 9214c8f2e9SChris Zhong int err, regulator_err; 9314c8f2e9SChris Zhong 9414c8f2e9SChris Zhong if (innolux->prepared) 9514c8f2e9SChris Zhong return 0; 9614c8f2e9SChris Zhong 9714c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 9814c8f2e9SChris Zhong 9914c8f2e9SChris Zhong err = regulator_enable(innolux->supply); 10014c8f2e9SChris Zhong if (err < 0) 10114c8f2e9SChris Zhong return err; 10214c8f2e9SChris Zhong 10314c8f2e9SChris Zhong /* T2: 15ms - 1000ms */ 10414c8f2e9SChris Zhong usleep_range(15000, 16000); 10514c8f2e9SChris Zhong 10614c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 1); 10714c8f2e9SChris Zhong 10814c8f2e9SChris Zhong /* T4: 15ms - 1000ms */ 10914c8f2e9SChris Zhong usleep_range(15000, 16000); 11014c8f2e9SChris Zhong 11114c8f2e9SChris Zhong err = mipi_dsi_dcs_exit_sleep_mode(innolux->link); 11214c8f2e9SChris Zhong if (err < 0) { 11314c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n", 11414c8f2e9SChris Zhong err); 11514c8f2e9SChris Zhong goto poweroff; 11614c8f2e9SChris Zhong } 11714c8f2e9SChris Zhong 11814c8f2e9SChris Zhong /* T6: 120ms - 1000ms*/ 11914c8f2e9SChris Zhong msleep(120); 12014c8f2e9SChris Zhong 12114c8f2e9SChris Zhong err = mipi_dsi_dcs_set_display_on(innolux->link); 12214c8f2e9SChris Zhong if (err < 0) { 12314c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n", 12414c8f2e9SChris Zhong err); 12514c8f2e9SChris Zhong goto poweroff; 12614c8f2e9SChris Zhong } 12714c8f2e9SChris Zhong 12814c8f2e9SChris Zhong /* T7: 5ms */ 12914c8f2e9SChris Zhong usleep_range(5000, 6000); 13014c8f2e9SChris Zhong 13114c8f2e9SChris Zhong innolux->prepared = true; 13214c8f2e9SChris Zhong 13314c8f2e9SChris Zhong return 0; 13414c8f2e9SChris Zhong 13514c8f2e9SChris Zhong poweroff: 13614c8f2e9SChris Zhong regulator_err = regulator_disable(innolux->supply); 13714c8f2e9SChris Zhong if (regulator_err) 13814c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to disable regulator: %d\n", 13914c8f2e9SChris Zhong regulator_err); 14014c8f2e9SChris Zhong 14114c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 14214c8f2e9SChris Zhong return err; 14314c8f2e9SChris Zhong } 14414c8f2e9SChris Zhong 14514c8f2e9SChris Zhong static int innolux_panel_enable(struct drm_panel *panel) 14614c8f2e9SChris Zhong { 14714c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 14814c8f2e9SChris Zhong int ret; 14914c8f2e9SChris Zhong 15014c8f2e9SChris Zhong if (innolux->enabled) 15114c8f2e9SChris Zhong return 0; 15214c8f2e9SChris Zhong 153*d593bfdbSMeghana Madhyastha ret = backlight_enable(innolux->backlight); 15414c8f2e9SChris Zhong if (ret) { 15514c8f2e9SChris Zhong DRM_DEV_ERROR(panel->drm->dev, 15614c8f2e9SChris Zhong "Failed to enable backlight %d\n", ret); 15714c8f2e9SChris Zhong return ret; 15814c8f2e9SChris Zhong } 15914c8f2e9SChris Zhong 16014c8f2e9SChris Zhong innolux->enabled = true; 16114c8f2e9SChris Zhong 16214c8f2e9SChris Zhong return 0; 16314c8f2e9SChris Zhong } 16414c8f2e9SChris Zhong 16514c8f2e9SChris Zhong static const struct drm_display_mode default_mode = { 16614c8f2e9SChris Zhong .clock = 56900, 16714c8f2e9SChris Zhong .hdisplay = 768, 16814c8f2e9SChris Zhong .hsync_start = 768 + 40, 16914c8f2e9SChris Zhong .hsync_end = 768 + 40 + 40, 17014c8f2e9SChris Zhong .htotal = 768 + 40 + 40 + 40, 17114c8f2e9SChris Zhong .vdisplay = 1024, 17214c8f2e9SChris Zhong .vsync_start = 1024 + 20, 17314c8f2e9SChris Zhong .vsync_end = 1024 + 20 + 4, 17414c8f2e9SChris Zhong .vtotal = 1024 + 20 + 4 + 20, 17514c8f2e9SChris Zhong .vrefresh = 60, 17614c8f2e9SChris Zhong }; 17714c8f2e9SChris Zhong 17814c8f2e9SChris Zhong static int innolux_panel_get_modes(struct drm_panel *panel) 17914c8f2e9SChris Zhong { 18014c8f2e9SChris Zhong struct drm_display_mode *mode; 18114c8f2e9SChris Zhong 18214c8f2e9SChris Zhong mode = drm_mode_duplicate(panel->drm, &default_mode); 18314c8f2e9SChris Zhong if (!mode) { 18414c8f2e9SChris Zhong DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n", 18514c8f2e9SChris Zhong default_mode.hdisplay, default_mode.vdisplay, 18614c8f2e9SChris Zhong default_mode.vrefresh); 18714c8f2e9SChris Zhong return -ENOMEM; 18814c8f2e9SChris Zhong } 18914c8f2e9SChris Zhong 19014c8f2e9SChris Zhong drm_mode_set_name(mode); 19114c8f2e9SChris Zhong 19214c8f2e9SChris Zhong drm_mode_probed_add(panel->connector, mode); 19314c8f2e9SChris Zhong 19414c8f2e9SChris Zhong panel->connector->display_info.width_mm = 120; 19514c8f2e9SChris Zhong panel->connector->display_info.height_mm = 160; 19614c8f2e9SChris Zhong panel->connector->display_info.bpc = 8; 19714c8f2e9SChris Zhong 19814c8f2e9SChris Zhong return 1; 19914c8f2e9SChris Zhong } 20014c8f2e9SChris Zhong 20114c8f2e9SChris Zhong static const struct drm_panel_funcs innolux_panel_funcs = { 20214c8f2e9SChris Zhong .disable = innolux_panel_disable, 20314c8f2e9SChris Zhong .unprepare = innolux_panel_unprepare, 20414c8f2e9SChris Zhong .prepare = innolux_panel_prepare, 20514c8f2e9SChris Zhong .enable = innolux_panel_enable, 20614c8f2e9SChris Zhong .get_modes = innolux_panel_get_modes, 20714c8f2e9SChris Zhong }; 20814c8f2e9SChris Zhong 20914c8f2e9SChris Zhong static const struct of_device_id innolux_of_match[] = { 21014c8f2e9SChris Zhong { .compatible = "innolux,p079zca", }, 21114c8f2e9SChris Zhong { } 21214c8f2e9SChris Zhong }; 21314c8f2e9SChris Zhong MODULE_DEVICE_TABLE(of, innolux_of_match); 21414c8f2e9SChris Zhong 21514c8f2e9SChris Zhong static int innolux_panel_add(struct innolux_panel *innolux) 21614c8f2e9SChris Zhong { 21714c8f2e9SChris Zhong struct device *dev = &innolux->link->dev; 21814c8f2e9SChris Zhong struct device_node *np; 21914c8f2e9SChris Zhong int err; 22014c8f2e9SChris Zhong 22114c8f2e9SChris Zhong innolux->supply = devm_regulator_get(dev, "power"); 22214c8f2e9SChris Zhong if (IS_ERR(innolux->supply)) 22314c8f2e9SChris Zhong return PTR_ERR(innolux->supply); 22414c8f2e9SChris Zhong 22514c8f2e9SChris Zhong innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable", 22614c8f2e9SChris Zhong GPIOD_OUT_HIGH); 22714c8f2e9SChris Zhong if (IS_ERR(innolux->enable_gpio)) { 22814c8f2e9SChris Zhong err = PTR_ERR(innolux->enable_gpio); 22914c8f2e9SChris Zhong dev_dbg(dev, "failed to get enable gpio: %d\n", err); 23014c8f2e9SChris Zhong innolux->enable_gpio = NULL; 23114c8f2e9SChris Zhong } 23214c8f2e9SChris Zhong 23314c8f2e9SChris Zhong np = of_parse_phandle(dev->of_node, "backlight", 0); 23414c8f2e9SChris Zhong if (np) { 23514c8f2e9SChris Zhong innolux->backlight = of_find_backlight_by_node(np); 23614c8f2e9SChris Zhong of_node_put(np); 23714c8f2e9SChris Zhong 23814c8f2e9SChris Zhong if (!innolux->backlight) 23914c8f2e9SChris Zhong return -EPROBE_DEFER; 24014c8f2e9SChris Zhong } 24114c8f2e9SChris Zhong 24214c8f2e9SChris Zhong drm_panel_init(&innolux->base); 24314c8f2e9SChris Zhong innolux->base.funcs = &innolux_panel_funcs; 24414c8f2e9SChris Zhong innolux->base.dev = &innolux->link->dev; 24514c8f2e9SChris Zhong 24614c8f2e9SChris Zhong err = drm_panel_add(&innolux->base); 24714c8f2e9SChris Zhong if (err < 0) 24814c8f2e9SChris Zhong goto put_backlight; 24914c8f2e9SChris Zhong 25014c8f2e9SChris Zhong return 0; 25114c8f2e9SChris Zhong 25214c8f2e9SChris Zhong put_backlight: 25314c8f2e9SChris Zhong put_device(&innolux->backlight->dev); 25414c8f2e9SChris Zhong 25514c8f2e9SChris Zhong return err; 25614c8f2e9SChris Zhong } 25714c8f2e9SChris Zhong 25814c8f2e9SChris Zhong static void innolux_panel_del(struct innolux_panel *innolux) 25914c8f2e9SChris Zhong { 26014c8f2e9SChris Zhong if (innolux->base.dev) 26114c8f2e9SChris Zhong drm_panel_remove(&innolux->base); 26214c8f2e9SChris Zhong 26314c8f2e9SChris Zhong put_device(&innolux->backlight->dev); 26414c8f2e9SChris Zhong } 26514c8f2e9SChris Zhong 26614c8f2e9SChris Zhong static int innolux_panel_probe(struct mipi_dsi_device *dsi) 26714c8f2e9SChris Zhong { 26814c8f2e9SChris Zhong struct innolux_panel *innolux; 26914c8f2e9SChris Zhong int err; 27014c8f2e9SChris Zhong 27114c8f2e9SChris Zhong dsi->lanes = 4; 27214c8f2e9SChris Zhong dsi->format = MIPI_DSI_FMT_RGB888; 27314c8f2e9SChris Zhong dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | 27414c8f2e9SChris Zhong MIPI_DSI_MODE_LPM; 27514c8f2e9SChris Zhong 27614c8f2e9SChris Zhong innolux = devm_kzalloc(&dsi->dev, sizeof(*innolux), GFP_KERNEL); 27714c8f2e9SChris Zhong if (!innolux) 27814c8f2e9SChris Zhong return -ENOMEM; 27914c8f2e9SChris Zhong 28014c8f2e9SChris Zhong mipi_dsi_set_drvdata(dsi, innolux); 28114c8f2e9SChris Zhong 28214c8f2e9SChris Zhong innolux->link = dsi; 28314c8f2e9SChris Zhong 28414c8f2e9SChris Zhong err = innolux_panel_add(innolux); 28514c8f2e9SChris Zhong if (err < 0) 28614c8f2e9SChris Zhong return err; 28714c8f2e9SChris Zhong 28814c8f2e9SChris Zhong err = mipi_dsi_attach(dsi); 28914c8f2e9SChris Zhong return err; 29014c8f2e9SChris Zhong } 29114c8f2e9SChris Zhong 29214c8f2e9SChris Zhong static int innolux_panel_remove(struct mipi_dsi_device *dsi) 29314c8f2e9SChris Zhong { 29414c8f2e9SChris Zhong struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); 29514c8f2e9SChris Zhong int err; 29614c8f2e9SChris Zhong 29714c8f2e9SChris Zhong err = innolux_panel_unprepare(&innolux->base); 29814c8f2e9SChris Zhong if (err < 0) 29914c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n", 30014c8f2e9SChris Zhong err); 30114c8f2e9SChris Zhong 30214c8f2e9SChris Zhong err = innolux_panel_disable(&innolux->base); 30314c8f2e9SChris Zhong if (err < 0) 30414c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err); 30514c8f2e9SChris Zhong 30614c8f2e9SChris Zhong err = mipi_dsi_detach(dsi); 30714c8f2e9SChris Zhong if (err < 0) 30814c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n", 30914c8f2e9SChris Zhong err); 31014c8f2e9SChris Zhong 31114c8f2e9SChris Zhong drm_panel_detach(&innolux->base); 31214c8f2e9SChris Zhong innolux_panel_del(innolux); 31314c8f2e9SChris Zhong 31414c8f2e9SChris Zhong return 0; 31514c8f2e9SChris Zhong } 31614c8f2e9SChris Zhong 31714c8f2e9SChris Zhong static void innolux_panel_shutdown(struct mipi_dsi_device *dsi) 31814c8f2e9SChris Zhong { 31914c8f2e9SChris Zhong struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); 32014c8f2e9SChris Zhong 32114c8f2e9SChris Zhong innolux_panel_unprepare(&innolux->base); 32214c8f2e9SChris Zhong innolux_panel_disable(&innolux->base); 32314c8f2e9SChris Zhong } 32414c8f2e9SChris Zhong 32514c8f2e9SChris Zhong static struct mipi_dsi_driver innolux_panel_driver = { 32614c8f2e9SChris Zhong .driver = { 32714c8f2e9SChris Zhong .name = "panel-innolux-p079zca", 32814c8f2e9SChris Zhong .of_match_table = innolux_of_match, 32914c8f2e9SChris Zhong }, 33014c8f2e9SChris Zhong .probe = innolux_panel_probe, 33114c8f2e9SChris Zhong .remove = innolux_panel_remove, 33214c8f2e9SChris Zhong .shutdown = innolux_panel_shutdown, 33314c8f2e9SChris Zhong }; 33414c8f2e9SChris Zhong module_mipi_dsi_driver(innolux_panel_driver); 33514c8f2e9SChris Zhong 33614c8f2e9SChris Zhong MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); 33714c8f2e9SChris Zhong MODULE_DESCRIPTION("Innolux P079ZCA panel driver"); 33814c8f2e9SChris Zhong MODULE_LICENSE("GPL v2"); 339