1/* 2 * Generic MIPI DPI Panel Driver 3 * 4 * Copyright (C) 2013 Texas Instruments 5 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 as published by 9 * the Free Software Foundation. 10 */ 11 12#include <linux/gpio.h> 13#include <linux/module.h> 14#include <linux/platform_device.h> 15#include <linux/slab.h> 16#include <linux/of.h> 17#include <linux/of_gpio.h> 18 19#include <video/omapdss.h> 20#include <video/omap-panel-data.h> 21#include <video/of_display_timing.h> 22 23struct panel_drv_data { 24 struct omap_dss_device dssdev; 25 struct omap_dss_device *in; 26 27 int data_lines; 28 29 struct omap_video_timings videomode; 30 31 /* used for non-DT boot, to be removed */ 32 int backlight_gpio; 33 34 struct gpio_desc *enable_gpio; 35}; 36 37#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) 38 39static int panel_dpi_connect(struct omap_dss_device *dssdev) 40{ 41 struct panel_drv_data *ddata = to_panel_data(dssdev); 42 struct omap_dss_device *in = ddata->in; 43 int r; 44 45 if (omapdss_device_is_connected(dssdev)) 46 return 0; 47 48 r = in->ops.dpi->connect(in, dssdev); 49 if (r) 50 return r; 51 52 return 0; 53} 54 55static void panel_dpi_disconnect(struct omap_dss_device *dssdev) 56{ 57 struct panel_drv_data *ddata = to_panel_data(dssdev); 58 struct omap_dss_device *in = ddata->in; 59 60 if (!omapdss_device_is_connected(dssdev)) 61 return; 62 63 in->ops.dpi->disconnect(in, dssdev); 64} 65 66static int panel_dpi_enable(struct omap_dss_device *dssdev) 67{ 68 struct panel_drv_data *ddata = to_panel_data(dssdev); 69 struct omap_dss_device *in = ddata->in; 70 int r; 71 72 if (!omapdss_device_is_connected(dssdev)) 73 return -ENODEV; 74 75 if (omapdss_device_is_enabled(dssdev)) 76 return 0; 77 78 if (ddata->data_lines) 79 in->ops.dpi->set_data_lines(in, ddata->data_lines); 80 in->ops.dpi->set_timings(in, &ddata->videomode); 81 82 r = in->ops.dpi->enable(in); 83 if (r) 84 return r; 85 86 if (ddata->enable_gpio) 87 gpiod_set_value_cansleep(ddata->enable_gpio, 1); 88 89 if (gpio_is_valid(ddata->backlight_gpio)) 90 gpio_set_value_cansleep(ddata->backlight_gpio, 1); 91 92 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; 93 94 return 0; 95} 96 97static void panel_dpi_disable(struct omap_dss_device *dssdev) 98{ 99 struct panel_drv_data *ddata = to_panel_data(dssdev); 100 struct omap_dss_device *in = ddata->in; 101 102 if (!omapdss_device_is_enabled(dssdev)) 103 return; 104 105 if (ddata->enable_gpio) 106 gpiod_set_value_cansleep(ddata->enable_gpio, 0); 107 108 if (gpio_is_valid(ddata->backlight_gpio)) 109 gpio_set_value_cansleep(ddata->backlight_gpio, 0); 110 111 in->ops.dpi->disable(in); 112 113 dssdev->state = OMAP_DSS_DISPLAY_DISABLED; 114} 115 116static void panel_dpi_set_timings(struct omap_dss_device *dssdev, 117 struct omap_video_timings *timings) 118{ 119 struct panel_drv_data *ddata = to_panel_data(dssdev); 120 struct omap_dss_device *in = ddata->in; 121 122 ddata->videomode = *timings; 123 dssdev->panel.timings = *timings; 124 125 in->ops.dpi->set_timings(in, timings); 126} 127 128static void panel_dpi_get_timings(struct omap_dss_device *dssdev, 129 struct omap_video_timings *timings) 130{ 131 struct panel_drv_data *ddata = to_panel_data(dssdev); 132 133 *timings = ddata->videomode; 134} 135 136static int panel_dpi_check_timings(struct omap_dss_device *dssdev, 137 struct omap_video_timings *timings) 138{ 139 struct panel_drv_data *ddata = to_panel_data(dssdev); 140 struct omap_dss_device *in = ddata->in; 141 142 return in->ops.dpi->check_timings(in, timings); 143} 144 145static struct omap_dss_driver panel_dpi_ops = { 146 .connect = panel_dpi_connect, 147 .disconnect = panel_dpi_disconnect, 148 149 .enable = panel_dpi_enable, 150 .disable = panel_dpi_disable, 151 152 .set_timings = panel_dpi_set_timings, 153 .get_timings = panel_dpi_get_timings, 154 .check_timings = panel_dpi_check_timings, 155 156 .get_resolution = omapdss_default_get_resolution, 157}; 158 159static int panel_dpi_probe_pdata(struct platform_device *pdev) 160{ 161 const struct panel_dpi_platform_data *pdata; 162 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 163 struct omap_dss_device *dssdev, *in; 164 struct videomode vm; 165 int r; 166 167 pdata = dev_get_platdata(&pdev->dev); 168 169 in = omap_dss_find_output(pdata->source); 170 if (in == NULL) { 171 dev_err(&pdev->dev, "failed to find video source '%s'\n", 172 pdata->source); 173 return -EPROBE_DEFER; 174 } 175 176 ddata->in = in; 177 178 ddata->data_lines = pdata->data_lines; 179 180 videomode_from_timing(pdata->display_timing, &vm); 181 videomode_to_omap_video_timings(&vm, &ddata->videomode); 182 183 dssdev = &ddata->dssdev; 184 dssdev->name = pdata->name; 185 186 r = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio, 187 GPIOF_OUT_INIT_LOW, "panel enable"); 188 if (r) 189 goto err_gpio; 190 191 ddata->enable_gpio = gpio_to_desc(pdata->enable_gpio); 192 193 ddata->backlight_gpio = pdata->backlight_gpio; 194 195 return 0; 196 197err_gpio: 198 omap_dss_put_device(ddata->in); 199 return r; 200} 201 202static int panel_dpi_probe_of(struct platform_device *pdev) 203{ 204 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 205 struct device_node *node = pdev->dev.of_node; 206 struct omap_dss_device *in; 207 int r; 208 struct display_timing timing; 209 struct videomode vm; 210 struct gpio_desc *gpio; 211 212 gpio = devm_gpiod_get_optional(&pdev->dev, "enable", GPIOD_OUT_LOW); 213 if (IS_ERR(gpio)) 214 return PTR_ERR(gpio); 215 216 ddata->enable_gpio = gpio; 217 218 ddata->backlight_gpio = -ENOENT; 219 220 r = of_get_display_timing(node, "panel-timing", &timing); 221 if (r) { 222 dev_err(&pdev->dev, "failed to get video timing\n"); 223 return r; 224 } 225 226 videomode_from_timing(&timing, &vm); 227 videomode_to_omap_video_timings(&vm, &ddata->videomode); 228 229 in = omapdss_of_find_source_for_first_ep(node); 230 if (IS_ERR(in)) { 231 dev_err(&pdev->dev, "failed to find video source\n"); 232 return PTR_ERR(in); 233 } 234 235 ddata->in = in; 236 237 return 0; 238} 239 240static int panel_dpi_probe(struct platform_device *pdev) 241{ 242 struct panel_drv_data *ddata; 243 struct omap_dss_device *dssdev; 244 int r; 245 246 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 247 if (ddata == NULL) 248 return -ENOMEM; 249 250 platform_set_drvdata(pdev, ddata); 251 252 if (dev_get_platdata(&pdev->dev)) { 253 r = panel_dpi_probe_pdata(pdev); 254 if (r) 255 return r; 256 } else if (pdev->dev.of_node) { 257 r = panel_dpi_probe_of(pdev); 258 if (r) 259 return r; 260 } else { 261 return -ENODEV; 262 } 263 264 if (gpio_is_valid(ddata->backlight_gpio)) { 265 r = devm_gpio_request_one(&pdev->dev, ddata->backlight_gpio, 266 GPIOF_OUT_INIT_LOW, "panel backlight"); 267 if (r) 268 goto err_gpio; 269 } 270 271 dssdev = &ddata->dssdev; 272 dssdev->dev = &pdev->dev; 273 dssdev->driver = &panel_dpi_ops; 274 dssdev->type = OMAP_DISPLAY_TYPE_DPI; 275 dssdev->owner = THIS_MODULE; 276 dssdev->panel.timings = ddata->videomode; 277 dssdev->phy.dpi.data_lines = ddata->data_lines; 278 279 r = omapdss_register_display(dssdev); 280 if (r) { 281 dev_err(&pdev->dev, "Failed to register panel\n"); 282 goto err_reg; 283 } 284 285 return 0; 286 287err_reg: 288err_gpio: 289 omap_dss_put_device(ddata->in); 290 return r; 291} 292 293static int __exit panel_dpi_remove(struct platform_device *pdev) 294{ 295 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 296 struct omap_dss_device *dssdev = &ddata->dssdev; 297 struct omap_dss_device *in = ddata->in; 298 299 omapdss_unregister_display(dssdev); 300 301 panel_dpi_disable(dssdev); 302 panel_dpi_disconnect(dssdev); 303 304 omap_dss_put_device(in); 305 306 return 0; 307} 308 309static const struct of_device_id panel_dpi_of_match[] = { 310 { .compatible = "omapdss,panel-dpi", }, 311 {}, 312}; 313 314MODULE_DEVICE_TABLE(of, panel_dpi_of_match); 315 316static struct platform_driver panel_dpi_driver = { 317 .probe = panel_dpi_probe, 318 .remove = __exit_p(panel_dpi_remove), 319 .driver = { 320 .name = "panel-dpi", 321 .of_match_table = panel_dpi_of_match, 322 .suppress_bind_attrs = true, 323 }, 324}; 325 326module_platform_driver(panel_dpi_driver); 327 328MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 329MODULE_DESCRIPTION("Generic MIPI DPI Panel Driver"); 330MODULE_LICENSE("GPL"); 331