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

drm/panel: Add Panasonic VVX10F034N00 MIPI DSI panel

This adds support for the Panasonic panel found in some Xperia Z2
tablets.

Signed-off-by: Werner Johansson <werner.johansson@sonymobile.com>
Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>

authored by

Werner Johansson and committed by
Thierry Reding
086ceb6b f74807a9

+345
+10
drivers/gpu/drm/panel/Kconfig
··· 31 31 Say Y here if you want to enable support for LG4573 RGB panel. 32 32 To compile this driver as a module, choose M here. 33 33 34 + config DRM_PANEL_PANASONIC_VVX10F034N00 35 + tristate "Panasonic VVX10F034N00 1920x1200 video mode panel" 36 + depends on OF 37 + depends on DRM_MIPI_DSI 38 + depends on BACKLIGHT_CLASS_DEVICE 39 + help 40 + Say Y here if you want to enable support for Panasonic VVX10F034N00 41 + WUXGA (1920x1200) Novatek NT1397-based DSI panel as found in some 42 + Xperia Z2 tablets 43 + 34 44 config DRM_PANEL_SAMSUNG_S6E8AA0 35 45 tristate "Samsung S6E8AA0 DSI video mode panel" 36 46 depends on OF
+1
drivers/gpu/drm/panel/Makefile
··· 1 1 obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o 2 2 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o 3 + obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += panel-panasonic-vvx10f034n00.o 3 4 obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o 4 5 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o 5 6 obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o
+334
drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c
··· 1 + /* 2 + * Copyright (C) 2015 Red Hat 3 + * Copyright (C) 2015 Sony Mobile Communications Inc. 4 + * Author: Werner Johansson <werner.johansson@sonymobile.com> 5 + * 6 + * Based on AUO panel driver by Rob Clark <robdclark@gmail.com> 7 + * 8 + * This program is free software; you can redistribute it and/or modify it 9 + * under the terms of the GNU General Public License version 2 as published by 10 + * the Free Software Foundation. 11 + * 12 + * This program is distributed in the hope that it will be useful, but WITHOUT 13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 + * more details. 16 + * 17 + * You should have received a copy of the GNU General Public License along with 18 + * this program. If not, see <http://www.gnu.org/licenses/>. 19 + */ 20 + 21 + #include <linux/backlight.h> 22 + #include <linux/module.h> 23 + #include <linux/of.h> 24 + #include <linux/regulator/consumer.h> 25 + 26 + #include <drm/drmP.h> 27 + #include <drm/drm_crtc.h> 28 + #include <drm/drm_mipi_dsi.h> 29 + #include <drm/drm_panel.h> 30 + 31 + #include <video/mipi_display.h> 32 + 33 + /* 34 + * When power is turned off to this panel a minimum off time of 500ms has to be 35 + * observed before powering back on as there's no external reset pin. Keep 36 + * track of earliest wakeup time and delay subsequent prepare call accordingly 37 + */ 38 + #define MIN_POFF_MS (500) 39 + 40 + struct wuxga_nt_panel { 41 + struct drm_panel base; 42 + struct mipi_dsi_device *dsi; 43 + 44 + struct backlight_device *backlight; 45 + struct regulator *supply; 46 + 47 + bool prepared; 48 + bool enabled; 49 + 50 + ktime_t earliest_wake; 51 + 52 + const struct drm_display_mode *mode; 53 + }; 54 + 55 + static inline struct wuxga_nt_panel *to_wuxga_nt_panel(struct drm_panel *panel) 56 + { 57 + return container_of(panel, struct wuxga_nt_panel, base); 58 + } 59 + 60 + static int wuxga_nt_panel_on(struct wuxga_nt_panel *wuxga_nt) 61 + { 62 + struct mipi_dsi_device *dsi = wuxga_nt->dsi; 63 + int ret; 64 + 65 + ret = mipi_dsi_turn_on_peripheral(dsi); 66 + if (ret < 0) 67 + return ret; 68 + 69 + return 0; 70 + } 71 + 72 + static int wuxga_nt_panel_disable(struct drm_panel *panel) 73 + { 74 + struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); 75 + 76 + if (!wuxga_nt->enabled) 77 + return 0; 78 + 79 + mipi_dsi_shutdown_peripheral(wuxga_nt->dsi); 80 + 81 + if (wuxga_nt->backlight) { 82 + wuxga_nt->backlight->props.power = FB_BLANK_POWERDOWN; 83 + wuxga_nt->backlight->props.state |= BL_CORE_FBBLANK; 84 + backlight_update_status(wuxga_nt->backlight); 85 + } 86 + 87 + wuxga_nt->enabled = false; 88 + 89 + return 0; 90 + } 91 + 92 + static int wuxga_nt_panel_unprepare(struct drm_panel *panel) 93 + { 94 + struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); 95 + 96 + if (!wuxga_nt->prepared) 97 + return 0; 98 + 99 + regulator_disable(wuxga_nt->supply); 100 + wuxga_nt->earliest_wake = ktime_add_ms(ktime_get_real(), MIN_POFF_MS); 101 + wuxga_nt->prepared = false; 102 + 103 + return 0; 104 + } 105 + 106 + static int wuxga_nt_panel_prepare(struct drm_panel *panel) 107 + { 108 + struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); 109 + int ret; 110 + s64 enablewait; 111 + 112 + if (wuxga_nt->prepared) 113 + return 0; 114 + 115 + /* 116 + * If the user re-enabled the panel before the required off-time then 117 + * we need to wait the remaining period before re-enabling regulator 118 + */ 119 + enablewait = ktime_ms_delta(wuxga_nt->earliest_wake, ktime_get_real()); 120 + 121 + /* Sanity check, this should never happen */ 122 + if (enablewait > MIN_POFF_MS) 123 + enablewait = MIN_POFF_MS; 124 + 125 + if (enablewait > 0) 126 + msleep(enablewait); 127 + 128 + ret = regulator_enable(wuxga_nt->supply); 129 + if (ret < 0) 130 + return ret; 131 + 132 + /* 133 + * A minimum delay of 250ms is required after power-up until commands 134 + * can be sent 135 + */ 136 + msleep(250); 137 + 138 + ret = wuxga_nt_panel_on(wuxga_nt); 139 + if (ret < 0) { 140 + dev_err(panel->dev, "failed to set panel on: %d\n", ret); 141 + goto poweroff; 142 + } 143 + 144 + wuxga_nt->prepared = true; 145 + 146 + return 0; 147 + 148 + poweroff: 149 + regulator_disable(wuxga_nt->supply); 150 + 151 + return ret; 152 + } 153 + 154 + static int wuxga_nt_panel_enable(struct drm_panel *panel) 155 + { 156 + struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); 157 + 158 + if (wuxga_nt->enabled) 159 + return 0; 160 + 161 + if (wuxga_nt->backlight) { 162 + wuxga_nt->backlight->props.power = FB_BLANK_UNBLANK; 163 + wuxga_nt->backlight->props.state &= ~BL_CORE_FBBLANK; 164 + backlight_update_status(wuxga_nt->backlight); 165 + } 166 + 167 + wuxga_nt->enabled = true; 168 + 169 + return 0; 170 + } 171 + 172 + static const struct drm_display_mode default_mode = { 173 + .clock = 164402, 174 + .hdisplay = 1920, 175 + .hsync_start = 1920 + 152, 176 + .hsync_end = 1920 + 152 + 52, 177 + .htotal = 1920 + 152 + 52 + 20, 178 + .vdisplay = 1200, 179 + .vsync_start = 1200 + 24, 180 + .vsync_end = 1200 + 24 + 6, 181 + .vtotal = 1200 + 24 + 6 + 48, 182 + .vrefresh = 60, 183 + }; 184 + 185 + static int wuxga_nt_panel_get_modes(struct drm_panel *panel) 186 + { 187 + struct drm_display_mode *mode; 188 + 189 + mode = drm_mode_duplicate(panel->drm, &default_mode); 190 + if (!mode) { 191 + dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n", 192 + default_mode.hdisplay, default_mode.vdisplay, 193 + default_mode.vrefresh); 194 + return -ENOMEM; 195 + } 196 + 197 + drm_mode_set_name(mode); 198 + 199 + drm_mode_probed_add(panel->connector, mode); 200 + 201 + panel->connector->display_info.width_mm = 217; 202 + panel->connector->display_info.height_mm = 136; 203 + 204 + return 1; 205 + } 206 + 207 + static const struct drm_panel_funcs wuxga_nt_panel_funcs = { 208 + .disable = wuxga_nt_panel_disable, 209 + .unprepare = wuxga_nt_panel_unprepare, 210 + .prepare = wuxga_nt_panel_prepare, 211 + .enable = wuxga_nt_panel_enable, 212 + .get_modes = wuxga_nt_panel_get_modes, 213 + }; 214 + 215 + static const struct of_device_id wuxga_nt_of_match[] = { 216 + { .compatible = "panasonic,vvx10f034n00", }, 217 + { } 218 + }; 219 + MODULE_DEVICE_TABLE(of, wuxga_nt_of_match); 220 + 221 + static int wuxga_nt_panel_add(struct wuxga_nt_panel *wuxga_nt) 222 + { 223 + struct device *dev = &wuxga_nt->dsi->dev; 224 + struct device_node *np; 225 + int ret; 226 + 227 + wuxga_nt->mode = &default_mode; 228 + 229 + wuxga_nt->supply = devm_regulator_get(dev, "power"); 230 + if (IS_ERR(wuxga_nt->supply)) 231 + return PTR_ERR(wuxga_nt->supply); 232 + 233 + np = of_parse_phandle(dev->of_node, "backlight", 0); 234 + if (np) { 235 + wuxga_nt->backlight = of_find_backlight_by_node(np); 236 + of_node_put(np); 237 + 238 + if (!wuxga_nt->backlight) 239 + return -EPROBE_DEFER; 240 + } 241 + 242 + drm_panel_init(&wuxga_nt->base); 243 + wuxga_nt->base.funcs = &wuxga_nt_panel_funcs; 244 + wuxga_nt->base.dev = &wuxga_nt->dsi->dev; 245 + 246 + ret = drm_panel_add(&wuxga_nt->base); 247 + if (ret < 0) 248 + goto put_backlight; 249 + 250 + return 0; 251 + 252 + put_backlight: 253 + if (wuxga_nt->backlight) 254 + put_device(&wuxga_nt->backlight->dev); 255 + 256 + return ret; 257 + } 258 + 259 + static void wuxga_nt_panel_del(struct wuxga_nt_panel *wuxga_nt) 260 + { 261 + if (wuxga_nt->base.dev) 262 + drm_panel_remove(&wuxga_nt->base); 263 + 264 + if (wuxga_nt->backlight) 265 + put_device(&wuxga_nt->backlight->dev); 266 + } 267 + 268 + static int wuxga_nt_panel_probe(struct mipi_dsi_device *dsi) 269 + { 270 + struct wuxga_nt_panel *wuxga_nt; 271 + int ret; 272 + 273 + dsi->lanes = 4; 274 + dsi->format = MIPI_DSI_FMT_RGB888; 275 + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | 276 + MIPI_DSI_MODE_VIDEO_HSE | 277 + MIPI_DSI_CLOCK_NON_CONTINUOUS | 278 + MIPI_DSI_MODE_LPM; 279 + 280 + wuxga_nt = devm_kzalloc(&dsi->dev, sizeof(*wuxga_nt), GFP_KERNEL); 281 + if (!wuxga_nt) 282 + return -ENOMEM; 283 + 284 + mipi_dsi_set_drvdata(dsi, wuxga_nt); 285 + 286 + wuxga_nt->dsi = dsi; 287 + 288 + ret = wuxga_nt_panel_add(wuxga_nt); 289 + if (ret < 0) 290 + return ret; 291 + 292 + return mipi_dsi_attach(dsi); 293 + } 294 + 295 + static int wuxga_nt_panel_remove(struct mipi_dsi_device *dsi) 296 + { 297 + struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi); 298 + int ret; 299 + 300 + ret = wuxga_nt_panel_disable(&wuxga_nt->base); 301 + if (ret < 0) 302 + dev_err(&dsi->dev, "failed to disable panel: %d\n", ret); 303 + 304 + ret = mipi_dsi_detach(dsi); 305 + if (ret < 0) 306 + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); 307 + 308 + drm_panel_detach(&wuxga_nt->base); 309 + wuxga_nt_panel_del(wuxga_nt); 310 + 311 + return 0; 312 + } 313 + 314 + static void wuxga_nt_panel_shutdown(struct mipi_dsi_device *dsi) 315 + { 316 + struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi); 317 + 318 + wuxga_nt_panel_disable(&wuxga_nt->base); 319 + } 320 + 321 + static struct mipi_dsi_driver wuxga_nt_panel_driver = { 322 + .driver = { 323 + .name = "panel-panasonic-vvx10f034n00", 324 + .of_match_table = wuxga_nt_of_match, 325 + }, 326 + .probe = wuxga_nt_panel_probe, 327 + .remove = wuxga_nt_panel_remove, 328 + .shutdown = wuxga_nt_panel_shutdown, 329 + }; 330 + module_mipi_dsi_driver(wuxga_nt_panel_driver); 331 + 332 + MODULE_AUTHOR("Werner Johansson <werner.johansson@sonymobile.com>"); 333 + MODULE_DESCRIPTION("Panasonic VVX10F034N00 Novatek NT1397-based WUXGA (1920x1200) video mode panel driver"); 334 + MODULE_LICENSE("GPL v2");