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
panel_dpi_connect(struct omap_dss_device * dssdev)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
panel_dpi_disconnect(struct omap_dss_device * dssdev)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
panel_dpi_enable(struct omap_dss_device * dssdev)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
panel_dpi_disable(struct omap_dss_device * dssdev)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
panel_dpi_set_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)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
panel_dpi_get_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)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
panel_dpi_check_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)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
panel_dpi_probe_pdata(struct platform_device * pdev)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
panel_dpi_probe_of(struct platform_device * pdev)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
panel_dpi_probe(struct platform_device * pdev)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
panel_dpi_remove(struct platform_device * pdev)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