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