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 23*7ad4e463SLin Huang struct panel_desc { 24*7ad4e463SLin Huang const struct drm_display_mode *mode; 25*7ad4e463SLin Huang unsigned int bpc; 26*7ad4e463SLin Huang struct { 27*7ad4e463SLin Huang unsigned int width; 28*7ad4e463SLin Huang unsigned int height; 29*7ad4e463SLin Huang } size; 30*7ad4e463SLin Huang 31*7ad4e463SLin Huang unsigned long flags; 32*7ad4e463SLin Huang enum mipi_dsi_pixel_format format; 33*7ad4e463SLin Huang unsigned int lanes; 34*7ad4e463SLin Huang const char * const *supply_names; 35*7ad4e463SLin Huang unsigned int num_supplies; 36*7ad4e463SLin Huang }; 37*7ad4e463SLin Huang 3814c8f2e9SChris Zhong struct innolux_panel { 3914c8f2e9SChris Zhong struct drm_panel base; 4014c8f2e9SChris Zhong struct mipi_dsi_device *link; 41*7ad4e463SLin Huang const struct panel_desc *desc; 4214c8f2e9SChris Zhong 4314c8f2e9SChris Zhong struct backlight_device *backlight; 44*7ad4e463SLin Huang struct regulator_bulk_data *supplies; 45*7ad4e463SLin Huang unsigned int num_supplies; 4614c8f2e9SChris Zhong struct gpio_desc *enable_gpio; 4714c8f2e9SChris Zhong 4814c8f2e9SChris Zhong bool prepared; 4914c8f2e9SChris Zhong bool enabled; 5014c8f2e9SChris Zhong }; 5114c8f2e9SChris Zhong 5214c8f2e9SChris Zhong static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel) 5314c8f2e9SChris Zhong { 5414c8f2e9SChris Zhong return container_of(panel, struct innolux_panel, base); 5514c8f2e9SChris Zhong } 5614c8f2e9SChris Zhong 5714c8f2e9SChris Zhong static int innolux_panel_disable(struct drm_panel *panel) 5814c8f2e9SChris Zhong { 5914c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 6014c8f2e9SChris Zhong int err; 6114c8f2e9SChris Zhong 6214c8f2e9SChris Zhong if (!innolux->enabled) 6314c8f2e9SChris Zhong return 0; 6414c8f2e9SChris Zhong 65d593bfdbSMeghana Madhyastha backlight_disable(innolux->backlight); 6614c8f2e9SChris Zhong 6714c8f2e9SChris Zhong err = mipi_dsi_dcs_set_display_off(innolux->link); 6814c8f2e9SChris Zhong if (err < 0) 6914c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", 7014c8f2e9SChris Zhong err); 7114c8f2e9SChris Zhong 7214c8f2e9SChris Zhong innolux->enabled = false; 7314c8f2e9SChris Zhong 7414c8f2e9SChris Zhong return 0; 7514c8f2e9SChris Zhong } 7614c8f2e9SChris Zhong 7714c8f2e9SChris Zhong static int innolux_panel_unprepare(struct drm_panel *panel) 7814c8f2e9SChris Zhong { 7914c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 8014c8f2e9SChris Zhong int err; 8114c8f2e9SChris Zhong 8214c8f2e9SChris Zhong if (!innolux->prepared) 8314c8f2e9SChris Zhong return 0; 8414c8f2e9SChris Zhong 8514c8f2e9SChris Zhong err = mipi_dsi_dcs_enter_sleep_mode(innolux->link); 8614c8f2e9SChris Zhong if (err < 0) { 8714c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n", 8814c8f2e9SChris Zhong err); 8914c8f2e9SChris Zhong return err; 9014c8f2e9SChris Zhong } 9114c8f2e9SChris Zhong 9214c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 9314c8f2e9SChris Zhong 9414c8f2e9SChris Zhong /* T8: 80ms - 1000ms */ 9514c8f2e9SChris Zhong msleep(80); 9614c8f2e9SChris Zhong 97*7ad4e463SLin Huang err = regulator_bulk_disable(innolux->desc->num_supplies, 98*7ad4e463SLin Huang innolux->supplies); 9914c8f2e9SChris Zhong if (err < 0) 10014c8f2e9SChris Zhong return err; 10114c8f2e9SChris Zhong 10214c8f2e9SChris Zhong innolux->prepared = false; 10314c8f2e9SChris Zhong 10414c8f2e9SChris Zhong return 0; 10514c8f2e9SChris Zhong } 10614c8f2e9SChris Zhong 10714c8f2e9SChris Zhong static int innolux_panel_prepare(struct drm_panel *panel) 10814c8f2e9SChris Zhong { 10914c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 110*7ad4e463SLin Huang int err; 11114c8f2e9SChris Zhong 11214c8f2e9SChris Zhong if (innolux->prepared) 11314c8f2e9SChris Zhong return 0; 11414c8f2e9SChris Zhong 11514c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 11614c8f2e9SChris Zhong 117*7ad4e463SLin Huang err = regulator_bulk_enable(innolux->desc->num_supplies, 118*7ad4e463SLin Huang innolux->supplies); 11914c8f2e9SChris Zhong if (err < 0) 12014c8f2e9SChris Zhong return err; 12114c8f2e9SChris Zhong 12214c8f2e9SChris Zhong /* T2: 15ms - 1000ms */ 12314c8f2e9SChris Zhong usleep_range(15000, 16000); 12414c8f2e9SChris Zhong 12514c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 1); 12614c8f2e9SChris Zhong 12714c8f2e9SChris Zhong /* T4: 15ms - 1000ms */ 12814c8f2e9SChris Zhong usleep_range(15000, 16000); 12914c8f2e9SChris Zhong 13014c8f2e9SChris Zhong err = mipi_dsi_dcs_exit_sleep_mode(innolux->link); 13114c8f2e9SChris Zhong if (err < 0) { 13214c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n", 13314c8f2e9SChris Zhong err); 13414c8f2e9SChris Zhong goto poweroff; 13514c8f2e9SChris Zhong } 13614c8f2e9SChris Zhong 13714c8f2e9SChris Zhong /* T6: 120ms - 1000ms*/ 13814c8f2e9SChris Zhong msleep(120); 13914c8f2e9SChris Zhong 14014c8f2e9SChris Zhong err = mipi_dsi_dcs_set_display_on(innolux->link); 14114c8f2e9SChris Zhong if (err < 0) { 14214c8f2e9SChris Zhong DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n", 14314c8f2e9SChris Zhong err); 14414c8f2e9SChris Zhong goto poweroff; 14514c8f2e9SChris Zhong } 14614c8f2e9SChris Zhong 14714c8f2e9SChris Zhong /* T7: 5ms */ 14814c8f2e9SChris Zhong usleep_range(5000, 6000); 14914c8f2e9SChris Zhong 15014c8f2e9SChris Zhong innolux->prepared = true; 15114c8f2e9SChris Zhong 15214c8f2e9SChris Zhong return 0; 15314c8f2e9SChris Zhong 15414c8f2e9SChris Zhong poweroff: 15514c8f2e9SChris Zhong gpiod_set_value_cansleep(innolux->enable_gpio, 0); 156*7ad4e463SLin Huang regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies); 157*7ad4e463SLin Huang 15814c8f2e9SChris Zhong return err; 15914c8f2e9SChris Zhong } 16014c8f2e9SChris Zhong 16114c8f2e9SChris Zhong static int innolux_panel_enable(struct drm_panel *panel) 16214c8f2e9SChris Zhong { 16314c8f2e9SChris Zhong struct innolux_panel *innolux = to_innolux_panel(panel); 16414c8f2e9SChris Zhong int ret; 16514c8f2e9SChris Zhong 16614c8f2e9SChris Zhong if (innolux->enabled) 16714c8f2e9SChris Zhong return 0; 16814c8f2e9SChris Zhong 169d593bfdbSMeghana Madhyastha ret = backlight_enable(innolux->backlight); 17014c8f2e9SChris Zhong if (ret) { 17114c8f2e9SChris Zhong DRM_DEV_ERROR(panel->drm->dev, 17214c8f2e9SChris Zhong "Failed to enable backlight %d\n", ret); 17314c8f2e9SChris Zhong return ret; 17414c8f2e9SChris Zhong } 17514c8f2e9SChris Zhong 17614c8f2e9SChris Zhong innolux->enabled = true; 17714c8f2e9SChris Zhong 17814c8f2e9SChris Zhong return 0; 17914c8f2e9SChris Zhong } 18014c8f2e9SChris Zhong 181*7ad4e463SLin Huang static const char * const innolux_p079zca_supply_names[] = { 182*7ad4e463SLin Huang "power", 183*7ad4e463SLin Huang }; 184*7ad4e463SLin Huang 185*7ad4e463SLin Huang static const struct drm_display_mode innolux_p079zca_mode = { 18614c8f2e9SChris Zhong .clock = 56900, 18714c8f2e9SChris Zhong .hdisplay = 768, 18814c8f2e9SChris Zhong .hsync_start = 768 + 40, 18914c8f2e9SChris Zhong .hsync_end = 768 + 40 + 40, 19014c8f2e9SChris Zhong .htotal = 768 + 40 + 40 + 40, 19114c8f2e9SChris Zhong .vdisplay = 1024, 19214c8f2e9SChris Zhong .vsync_start = 1024 + 20, 19314c8f2e9SChris Zhong .vsync_end = 1024 + 20 + 4, 19414c8f2e9SChris Zhong .vtotal = 1024 + 20 + 4 + 20, 19514c8f2e9SChris Zhong .vrefresh = 60, 19614c8f2e9SChris Zhong }; 19714c8f2e9SChris Zhong 198*7ad4e463SLin Huang static const struct panel_desc innolux_p079zca_panel_desc = { 199*7ad4e463SLin Huang .mode = &innolux_p079zca_mode, 200*7ad4e463SLin Huang .bpc = 8, 201*7ad4e463SLin Huang .size = { 202*7ad4e463SLin Huang .width = 120, 203*7ad4e463SLin Huang .height = 160, 204*7ad4e463SLin Huang }, 205*7ad4e463SLin Huang .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | 206*7ad4e463SLin Huang MIPI_DSI_MODE_LPM, 207*7ad4e463SLin Huang .format = MIPI_DSI_FMT_RGB888, 208*7ad4e463SLin Huang .lanes = 4, 209*7ad4e463SLin Huang .supply_names = innolux_p079zca_supply_names, 210*7ad4e463SLin Huang .num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names), 211*7ad4e463SLin Huang }; 212*7ad4e463SLin Huang 21314c8f2e9SChris Zhong static int innolux_panel_get_modes(struct drm_panel *panel) 21414c8f2e9SChris Zhong { 215*7ad4e463SLin Huang struct innolux_panel *innolux = to_innolux_panel(panel); 216*7ad4e463SLin Huang const struct drm_display_mode *m = innolux->desc->mode; 21714c8f2e9SChris Zhong struct drm_display_mode *mode; 21814c8f2e9SChris Zhong 219*7ad4e463SLin Huang mode = drm_mode_duplicate(panel->drm, m); 22014c8f2e9SChris Zhong if (!mode) { 22114c8f2e9SChris Zhong DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n", 222*7ad4e463SLin Huang m->hdisplay, m->vdisplay, m->vrefresh); 22314c8f2e9SChris Zhong return -ENOMEM; 22414c8f2e9SChris Zhong } 22514c8f2e9SChris Zhong 22614c8f2e9SChris Zhong drm_mode_set_name(mode); 22714c8f2e9SChris Zhong 22814c8f2e9SChris Zhong drm_mode_probed_add(panel->connector, mode); 22914c8f2e9SChris Zhong 230*7ad4e463SLin Huang panel->connector->display_info.width_mm = 231*7ad4e463SLin Huang innolux->desc->size.width; 232*7ad4e463SLin Huang panel->connector->display_info.height_mm = 233*7ad4e463SLin Huang innolux->desc->size.height; 234*7ad4e463SLin Huang panel->connector->display_info.bpc = innolux->desc->bpc; 23514c8f2e9SChris Zhong 23614c8f2e9SChris Zhong return 1; 23714c8f2e9SChris Zhong } 23814c8f2e9SChris Zhong 23914c8f2e9SChris Zhong static const struct drm_panel_funcs innolux_panel_funcs = { 24014c8f2e9SChris Zhong .disable = innolux_panel_disable, 24114c8f2e9SChris Zhong .unprepare = innolux_panel_unprepare, 24214c8f2e9SChris Zhong .prepare = innolux_panel_prepare, 24314c8f2e9SChris Zhong .enable = innolux_panel_enable, 24414c8f2e9SChris Zhong .get_modes = innolux_panel_get_modes, 24514c8f2e9SChris Zhong }; 24614c8f2e9SChris Zhong 24714c8f2e9SChris Zhong static const struct of_device_id innolux_of_match[] = { 248*7ad4e463SLin Huang { .compatible = "innolux,p079zca", 249*7ad4e463SLin Huang .data = &innolux_p079zca_panel_desc 250*7ad4e463SLin Huang }, 25114c8f2e9SChris Zhong { } 25214c8f2e9SChris Zhong }; 25314c8f2e9SChris Zhong MODULE_DEVICE_TABLE(of, innolux_of_match); 25414c8f2e9SChris Zhong 255*7ad4e463SLin Huang static int innolux_panel_add(struct mipi_dsi_device *dsi, 256*7ad4e463SLin Huang const struct panel_desc *desc) 25714c8f2e9SChris Zhong { 258*7ad4e463SLin Huang struct innolux_panel *innolux; 259*7ad4e463SLin Huang struct device *dev = &dsi->dev; 260*7ad4e463SLin Huang int err, i; 26114c8f2e9SChris Zhong 262*7ad4e463SLin Huang innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL); 263*7ad4e463SLin Huang if (!innolux) 264*7ad4e463SLin Huang return -ENOMEM; 265*7ad4e463SLin Huang 266*7ad4e463SLin Huang innolux->desc = desc; 267*7ad4e463SLin Huang 268*7ad4e463SLin Huang innolux->supplies = devm_kcalloc(dev, desc->num_supplies, 269*7ad4e463SLin Huang sizeof(*innolux->supplies), 270*7ad4e463SLin Huang GFP_KERNEL); 271*7ad4e463SLin Huang if (!innolux->supplies) 272*7ad4e463SLin Huang return -ENOMEM; 273*7ad4e463SLin Huang 274*7ad4e463SLin Huang for (i = 0; i < desc->num_supplies; i++) 275*7ad4e463SLin Huang innolux->supplies[i].supply = desc->supply_names[i]; 276*7ad4e463SLin Huang 277*7ad4e463SLin Huang err = devm_regulator_bulk_get(dev, desc->num_supplies, 278*7ad4e463SLin Huang innolux->supplies); 279*7ad4e463SLin Huang if (err < 0) 280*7ad4e463SLin Huang return err; 28114c8f2e9SChris Zhong 28214c8f2e9SChris Zhong innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable", 28314c8f2e9SChris Zhong GPIOD_OUT_HIGH); 28414c8f2e9SChris Zhong if (IS_ERR(innolux->enable_gpio)) { 28514c8f2e9SChris Zhong err = PTR_ERR(innolux->enable_gpio); 28614c8f2e9SChris Zhong dev_dbg(dev, "failed to get enable gpio: %d\n", err); 28714c8f2e9SChris Zhong innolux->enable_gpio = NULL; 28814c8f2e9SChris Zhong } 28914c8f2e9SChris Zhong 290c69f9457SMeghana Madhyastha innolux->backlight = devm_of_find_backlight(dev); 291c69f9457SMeghana Madhyastha if (IS_ERR(innolux->backlight)) 292c69f9457SMeghana Madhyastha return PTR_ERR(innolux->backlight); 29314c8f2e9SChris Zhong 29414c8f2e9SChris Zhong drm_panel_init(&innolux->base); 29514c8f2e9SChris Zhong innolux->base.funcs = &innolux_panel_funcs; 296*7ad4e463SLin Huang innolux->base.dev = dev; 29714c8f2e9SChris Zhong 298*7ad4e463SLin Huang err = drm_panel_add(&innolux->base); 299*7ad4e463SLin Huang if (err < 0) 300*7ad4e463SLin Huang return err; 301*7ad4e463SLin Huang 302*7ad4e463SLin Huang mipi_dsi_set_drvdata(dsi, innolux); 303*7ad4e463SLin Huang innolux->link = dsi; 304*7ad4e463SLin Huang 305*7ad4e463SLin Huang return 0; 30614c8f2e9SChris Zhong } 30714c8f2e9SChris Zhong 30814c8f2e9SChris Zhong static void innolux_panel_del(struct innolux_panel *innolux) 30914c8f2e9SChris Zhong { 31014c8f2e9SChris Zhong if (innolux->base.dev) 31114c8f2e9SChris Zhong drm_panel_remove(&innolux->base); 31214c8f2e9SChris Zhong } 31314c8f2e9SChris Zhong 31414c8f2e9SChris Zhong static int innolux_panel_probe(struct mipi_dsi_device *dsi) 31514c8f2e9SChris Zhong { 316*7ad4e463SLin Huang const struct panel_desc *desc; 317*7ad4e463SLin Huang const struct of_device_id *id; 31814c8f2e9SChris Zhong int err; 31914c8f2e9SChris Zhong 320*7ad4e463SLin Huang id = of_match_node(innolux_of_match, dsi->dev.of_node); 321*7ad4e463SLin Huang if (!id) 322*7ad4e463SLin Huang return -ENODEV; 32314c8f2e9SChris Zhong 324*7ad4e463SLin Huang desc = id->data; 325*7ad4e463SLin Huang dsi->mode_flags = desc->flags; 326*7ad4e463SLin Huang dsi->format = desc->format; 327*7ad4e463SLin Huang dsi->lanes = desc->lanes; 32814c8f2e9SChris Zhong 329*7ad4e463SLin Huang err = innolux_panel_add(dsi, desc); 33014c8f2e9SChris Zhong if (err < 0) 33114c8f2e9SChris Zhong return err; 33214c8f2e9SChris Zhong 333*7ad4e463SLin Huang return mipi_dsi_attach(dsi); 33414c8f2e9SChris Zhong } 33514c8f2e9SChris Zhong 33614c8f2e9SChris Zhong static int innolux_panel_remove(struct mipi_dsi_device *dsi) 33714c8f2e9SChris Zhong { 33814c8f2e9SChris Zhong struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); 33914c8f2e9SChris Zhong int err; 34014c8f2e9SChris Zhong 34114c8f2e9SChris Zhong err = innolux_panel_unprepare(&innolux->base); 34214c8f2e9SChris Zhong if (err < 0) 34314c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n", 34414c8f2e9SChris Zhong err); 34514c8f2e9SChris Zhong 34614c8f2e9SChris Zhong err = innolux_panel_disable(&innolux->base); 34714c8f2e9SChris Zhong if (err < 0) 34814c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err); 34914c8f2e9SChris Zhong 35014c8f2e9SChris Zhong err = mipi_dsi_detach(dsi); 35114c8f2e9SChris Zhong if (err < 0) 35214c8f2e9SChris Zhong DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n", 35314c8f2e9SChris Zhong err); 35414c8f2e9SChris Zhong 35514c8f2e9SChris Zhong innolux_panel_del(innolux); 35614c8f2e9SChris Zhong 35714c8f2e9SChris Zhong return 0; 35814c8f2e9SChris Zhong } 35914c8f2e9SChris Zhong 36014c8f2e9SChris Zhong static void innolux_panel_shutdown(struct mipi_dsi_device *dsi) 36114c8f2e9SChris Zhong { 36214c8f2e9SChris Zhong struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); 36314c8f2e9SChris Zhong 36414c8f2e9SChris Zhong innolux_panel_unprepare(&innolux->base); 36514c8f2e9SChris Zhong innolux_panel_disable(&innolux->base); 36614c8f2e9SChris Zhong } 36714c8f2e9SChris Zhong 36814c8f2e9SChris Zhong static struct mipi_dsi_driver innolux_panel_driver = { 36914c8f2e9SChris Zhong .driver = { 37014c8f2e9SChris Zhong .name = "panel-innolux-p079zca", 37114c8f2e9SChris Zhong .of_match_table = innolux_of_match, 37214c8f2e9SChris Zhong }, 37314c8f2e9SChris Zhong .probe = innolux_panel_probe, 37414c8f2e9SChris Zhong .remove = innolux_panel_remove, 37514c8f2e9SChris Zhong .shutdown = innolux_panel_shutdown, 37614c8f2e9SChris Zhong }; 37714c8f2e9SChris Zhong module_mipi_dsi_driver(innolux_panel_driver); 37814c8f2e9SChris Zhong 37914c8f2e9SChris Zhong MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); 380*7ad4e463SLin Huang MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>"); 38114c8f2e9SChris Zhong MODULE_DESCRIPTION("Innolux P079ZCA panel driver"); 38214c8f2e9SChris Zhong MODULE_LICENSE("GPL v2"); 383