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