Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/panel: add Innolux P079ZCA panel driver

Support Innolux P079ZCA 7.85" 768x1024 TFT LCD panel, it is a MIPI DSI
panel.

Signed-off-by: Chris Zhong <zyw@rock-chips.com>
Reviewed-by: Sean Paul <seanpaul@chromium.org>
Tested-by: Brian Norris <briannorris@chromium.org>
Signed-off-by: Thierry Reding <treding@nvidia.com>
Link: http://patchwork.freedesktop.org/patch/msgid/1490316692-20506-2-git-send-email-zyw@rock-chips.com

authored by

Chris Zhong and committed by
Thierry Reding
14c8f2e9 ead9d5b1

+352
+11
drivers/gpu/drm/panel/Kconfig
··· 28 28 that it can be automatically turned off when the panel goes into a 29 29 low power state. 30 30 31 + config DRM_PANEL_INNOLUX_P079ZCA 32 + tristate "Innolux P079ZCA panel" 33 + depends on OF 34 + depends on DRM_MIPI_DSI 35 + depends on BACKLIGHT_CLASS_DEVICE 36 + help 37 + Say Y here if you want to enable support for Innolux P079ZCA 38 + TFT-LCD modules. The panel has a 1024x768 resolution and uses 39 + 24 bit RGB per pixel. It provides a MIPI DSI interface to 40 + the host and has a built-in LED backlight. 41 + 31 42 config DRM_PANEL_JDI_LT070ME05000 32 43 tristate "JDI LT070ME05000 WUXGA DSI panel" 33 44 depends on OF
+1
drivers/gpu/drm/panel/Makefile
··· 1 1 obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o 2 2 obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o 3 + obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o 3 4 obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o 4 5 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o 5 6 obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += panel-panasonic-vvx10f034n00.o
+340
drivers/gpu/drm/panel/panel-innolux-p079zca.c
··· 1 + /* 2 + * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd 3 + * 4 + * This program is free software; you can redistribute it and/or modify 5 + * it under the terms of the GNU General Public License as published by 6 + * the Free Software Foundation; either version 2 of the License, or 7 + * (at your option) any later version. 8 + */ 9 + 10 + #include <linux/backlight.h> 11 + #include <linux/gpio/consumer.h> 12 + #include <linux/module.h> 13 + #include <linux/of.h> 14 + #include <linux/regulator/consumer.h> 15 + 16 + #include <drm/drmP.h> 17 + #include <drm/drm_crtc.h> 18 + #include <drm/drm_mipi_dsi.h> 19 + #include <drm/drm_panel.h> 20 + 21 + #include <video/mipi_display.h> 22 + 23 + struct innolux_panel { 24 + struct drm_panel base; 25 + struct mipi_dsi_device *link; 26 + 27 + struct backlight_device *backlight; 28 + struct regulator *supply; 29 + struct gpio_desc *enable_gpio; 30 + 31 + bool prepared; 32 + bool enabled; 33 + }; 34 + 35 + static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel) 36 + { 37 + return container_of(panel, struct innolux_panel, base); 38 + } 39 + 40 + static int innolux_panel_disable(struct drm_panel *panel) 41 + { 42 + struct innolux_panel *innolux = to_innolux_panel(panel); 43 + int err; 44 + 45 + if (!innolux->enabled) 46 + return 0; 47 + 48 + innolux->backlight->props.power = FB_BLANK_POWERDOWN; 49 + backlight_update_status(innolux->backlight); 50 + 51 + err = mipi_dsi_dcs_set_display_off(innolux->link); 52 + if (err < 0) 53 + DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", 54 + err); 55 + 56 + innolux->enabled = false; 57 + 58 + return 0; 59 + } 60 + 61 + static int innolux_panel_unprepare(struct drm_panel *panel) 62 + { 63 + struct innolux_panel *innolux = to_innolux_panel(panel); 64 + int err; 65 + 66 + if (!innolux->prepared) 67 + return 0; 68 + 69 + err = mipi_dsi_dcs_enter_sleep_mode(innolux->link); 70 + if (err < 0) { 71 + DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n", 72 + err); 73 + return err; 74 + } 75 + 76 + gpiod_set_value_cansleep(innolux->enable_gpio, 0); 77 + 78 + /* T8: 80ms - 1000ms */ 79 + msleep(80); 80 + 81 + err = regulator_disable(innolux->supply); 82 + if (err < 0) 83 + return err; 84 + 85 + innolux->prepared = false; 86 + 87 + return 0; 88 + } 89 + 90 + static int innolux_panel_prepare(struct drm_panel *panel) 91 + { 92 + struct innolux_panel *innolux = to_innolux_panel(panel); 93 + int err, regulator_err; 94 + 95 + if (innolux->prepared) 96 + return 0; 97 + 98 + gpiod_set_value_cansleep(innolux->enable_gpio, 0); 99 + 100 + err = regulator_enable(innolux->supply); 101 + if (err < 0) 102 + return err; 103 + 104 + /* T2: 15ms - 1000ms */ 105 + usleep_range(15000, 16000); 106 + 107 + gpiod_set_value_cansleep(innolux->enable_gpio, 1); 108 + 109 + /* T4: 15ms - 1000ms */ 110 + usleep_range(15000, 16000); 111 + 112 + err = mipi_dsi_dcs_exit_sleep_mode(innolux->link); 113 + if (err < 0) { 114 + DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n", 115 + err); 116 + goto poweroff; 117 + } 118 + 119 + /* T6: 120ms - 1000ms*/ 120 + msleep(120); 121 + 122 + err = mipi_dsi_dcs_set_display_on(innolux->link); 123 + if (err < 0) { 124 + DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n", 125 + err); 126 + goto poweroff; 127 + } 128 + 129 + /* T7: 5ms */ 130 + usleep_range(5000, 6000); 131 + 132 + innolux->prepared = true; 133 + 134 + return 0; 135 + 136 + poweroff: 137 + regulator_err = regulator_disable(innolux->supply); 138 + if (regulator_err) 139 + DRM_DEV_ERROR(panel->dev, "failed to disable regulator: %d\n", 140 + regulator_err); 141 + 142 + gpiod_set_value_cansleep(innolux->enable_gpio, 0); 143 + return err; 144 + } 145 + 146 + static int innolux_panel_enable(struct drm_panel *panel) 147 + { 148 + struct innolux_panel *innolux = to_innolux_panel(panel); 149 + int ret; 150 + 151 + if (innolux->enabled) 152 + return 0; 153 + 154 + innolux->backlight->props.power = FB_BLANK_UNBLANK; 155 + ret = backlight_update_status(innolux->backlight); 156 + if (ret) { 157 + DRM_DEV_ERROR(panel->drm->dev, 158 + "Failed to enable backlight %d\n", ret); 159 + return ret; 160 + } 161 + 162 + innolux->enabled = true; 163 + 164 + return 0; 165 + } 166 + 167 + static const struct drm_display_mode default_mode = { 168 + .clock = 56900, 169 + .hdisplay = 768, 170 + .hsync_start = 768 + 40, 171 + .hsync_end = 768 + 40 + 40, 172 + .htotal = 768 + 40 + 40 + 40, 173 + .vdisplay = 1024, 174 + .vsync_start = 1024 + 20, 175 + .vsync_end = 1024 + 20 + 4, 176 + .vtotal = 1024 + 20 + 4 + 20, 177 + .vrefresh = 60, 178 + }; 179 + 180 + static int innolux_panel_get_modes(struct drm_panel *panel) 181 + { 182 + struct drm_display_mode *mode; 183 + 184 + mode = drm_mode_duplicate(panel->drm, &default_mode); 185 + if (!mode) { 186 + DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n", 187 + default_mode.hdisplay, default_mode.vdisplay, 188 + default_mode.vrefresh); 189 + return -ENOMEM; 190 + } 191 + 192 + drm_mode_set_name(mode); 193 + 194 + drm_mode_probed_add(panel->connector, mode); 195 + 196 + panel->connector->display_info.width_mm = 120; 197 + panel->connector->display_info.height_mm = 160; 198 + panel->connector->display_info.bpc = 8; 199 + 200 + return 1; 201 + } 202 + 203 + static const struct drm_panel_funcs innolux_panel_funcs = { 204 + .disable = innolux_panel_disable, 205 + .unprepare = innolux_panel_unprepare, 206 + .prepare = innolux_panel_prepare, 207 + .enable = innolux_panel_enable, 208 + .get_modes = innolux_panel_get_modes, 209 + }; 210 + 211 + static const struct of_device_id innolux_of_match[] = { 212 + { .compatible = "innolux,p079zca", }, 213 + { } 214 + }; 215 + MODULE_DEVICE_TABLE(of, innolux_of_match); 216 + 217 + static int innolux_panel_add(struct innolux_panel *innolux) 218 + { 219 + struct device *dev = &innolux->link->dev; 220 + struct device_node *np; 221 + int err; 222 + 223 + innolux->supply = devm_regulator_get(dev, "power"); 224 + if (IS_ERR(innolux->supply)) 225 + return PTR_ERR(innolux->supply); 226 + 227 + innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable", 228 + GPIOD_OUT_HIGH); 229 + if (IS_ERR(innolux->enable_gpio)) { 230 + err = PTR_ERR(innolux->enable_gpio); 231 + dev_dbg(dev, "failed to get enable gpio: %d\n", err); 232 + innolux->enable_gpio = NULL; 233 + } 234 + 235 + np = of_parse_phandle(dev->of_node, "backlight", 0); 236 + if (np) { 237 + innolux->backlight = of_find_backlight_by_node(np); 238 + of_node_put(np); 239 + 240 + if (!innolux->backlight) 241 + return -EPROBE_DEFER; 242 + } 243 + 244 + drm_panel_init(&innolux->base); 245 + innolux->base.funcs = &innolux_panel_funcs; 246 + innolux->base.dev = &innolux->link->dev; 247 + 248 + err = drm_panel_add(&innolux->base); 249 + if (err < 0) 250 + goto put_backlight; 251 + 252 + return 0; 253 + 254 + put_backlight: 255 + put_device(&innolux->backlight->dev); 256 + 257 + return err; 258 + } 259 + 260 + static void innolux_panel_del(struct innolux_panel *innolux) 261 + { 262 + if (innolux->base.dev) 263 + drm_panel_remove(&innolux->base); 264 + 265 + put_device(&innolux->backlight->dev); 266 + } 267 + 268 + static int innolux_panel_probe(struct mipi_dsi_device *dsi) 269 + { 270 + struct innolux_panel *innolux; 271 + int err; 272 + 273 + dsi->lanes = 4; 274 + dsi->format = MIPI_DSI_FMT_RGB888; 275 + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | 276 + MIPI_DSI_MODE_LPM; 277 + 278 + innolux = devm_kzalloc(&dsi->dev, sizeof(*innolux), GFP_KERNEL); 279 + if (!innolux) 280 + return -ENOMEM; 281 + 282 + mipi_dsi_set_drvdata(dsi, innolux); 283 + 284 + innolux->link = dsi; 285 + 286 + err = innolux_panel_add(innolux); 287 + if (err < 0) 288 + return err; 289 + 290 + err = mipi_dsi_attach(dsi); 291 + return err; 292 + } 293 + 294 + static int innolux_panel_remove(struct mipi_dsi_device *dsi) 295 + { 296 + struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); 297 + int err; 298 + 299 + err = innolux_panel_unprepare(&innolux->base); 300 + if (err < 0) 301 + DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n", 302 + err); 303 + 304 + err = innolux_panel_disable(&innolux->base); 305 + if (err < 0) 306 + DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err); 307 + 308 + err = mipi_dsi_detach(dsi); 309 + if (err < 0) 310 + DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n", 311 + err); 312 + 313 + drm_panel_detach(&innolux->base); 314 + innolux_panel_del(innolux); 315 + 316 + return 0; 317 + } 318 + 319 + static void innolux_panel_shutdown(struct mipi_dsi_device *dsi) 320 + { 321 + struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); 322 + 323 + innolux_panel_unprepare(&innolux->base); 324 + innolux_panel_disable(&innolux->base); 325 + } 326 + 327 + static struct mipi_dsi_driver innolux_panel_driver = { 328 + .driver = { 329 + .name = "panel-innolux-p079zca", 330 + .of_match_table = innolux_of_match, 331 + }, 332 + .probe = innolux_panel_probe, 333 + .remove = innolux_panel_remove, 334 + .shutdown = innolux_panel_shutdown, 335 + }; 336 + module_mipi_dsi_driver(innolux_panel_driver); 337 + 338 + MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); 339 + MODULE_DESCRIPTION("Innolux P079ZCA panel driver"); 340 + MODULE_LICENSE("GPL v2");