1 /*
2 * TFP410 DPI-to-DVI encoder driver
3 *
4 * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
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/consumer.h>
13 #include <linux/module.h>
14 #include <linux/platform_device.h>
15 #include <linux/slab.h>
16 #include <linux/of_gpio.h>
17
18 #include "../dss/omapdss.h"
19
20 struct panel_drv_data {
21 struct omap_dss_device dssdev;
22 struct omap_dss_device *in;
23
24 int pd_gpio;
25
26 struct videomode vm;
27 };
28
29 #define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
30
tfp410_connect(struct omap_dss_device * dssdev,struct omap_dss_device * dst)31 static int tfp410_connect(struct omap_dss_device *dssdev,
32 struct omap_dss_device *dst)
33 {
34 struct panel_drv_data *ddata = to_panel_data(dssdev);
35 struct omap_dss_device *in;
36 int r;
37
38 if (omapdss_device_is_connected(dssdev))
39 return -EBUSY;
40
41 in = omapdss_of_find_source_for_first_ep(dssdev->dev->of_node);
42 if (IS_ERR(in)) {
43 dev_err(dssdev->dev, "failed to find video source\n");
44 return PTR_ERR(in);
45 }
46
47 r = in->ops.dpi->connect(in, dssdev);
48 if (r) {
49 omap_dss_put_device(in);
50 return r;
51 }
52
53 dst->src = dssdev;
54 dssdev->dst = dst;
55
56 ddata->in = in;
57 return 0;
58 }
59
tfp410_disconnect(struct omap_dss_device * dssdev,struct omap_dss_device * dst)60 static void tfp410_disconnect(struct omap_dss_device *dssdev,
61 struct omap_dss_device *dst)
62 {
63 struct panel_drv_data *ddata = to_panel_data(dssdev);
64 struct omap_dss_device *in = ddata->in;
65
66 WARN_ON(!omapdss_device_is_connected(dssdev));
67 if (!omapdss_device_is_connected(dssdev))
68 return;
69
70 WARN_ON(dst != dssdev->dst);
71 if (dst != dssdev->dst)
72 return;
73
74 dst->src = NULL;
75 dssdev->dst = NULL;
76
77 in->ops.dpi->disconnect(in, &ddata->dssdev);
78
79 omap_dss_put_device(in);
80 ddata->in = NULL;
81 }
82
tfp410_enable(struct omap_dss_device * dssdev)83 static int tfp410_enable(struct omap_dss_device *dssdev)
84 {
85 struct panel_drv_data *ddata = to_panel_data(dssdev);
86 struct omap_dss_device *in = ddata->in;
87 int r;
88
89 if (!omapdss_device_is_connected(dssdev))
90 return -ENODEV;
91
92 if (omapdss_device_is_enabled(dssdev))
93 return 0;
94
95 in->ops.dpi->set_timings(in, &ddata->vm);
96
97 r = in->ops.dpi->enable(in);
98 if (r)
99 return r;
100
101 if (gpio_is_valid(ddata->pd_gpio))
102 gpio_set_value_cansleep(ddata->pd_gpio, 1);
103
104 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
105
106 return 0;
107 }
108
tfp410_disable(struct omap_dss_device * dssdev)109 static void tfp410_disable(struct omap_dss_device *dssdev)
110 {
111 struct panel_drv_data *ddata = to_panel_data(dssdev);
112 struct omap_dss_device *in = ddata->in;
113
114 if (!omapdss_device_is_enabled(dssdev))
115 return;
116
117 if (gpio_is_valid(ddata->pd_gpio))
118 gpio_set_value_cansleep(ddata->pd_gpio, 0);
119
120 in->ops.dpi->disable(in);
121
122 dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
123 }
124
tfp410_fix_timings(struct videomode * vm)125 static void tfp410_fix_timings(struct videomode *vm)
126 {
127 vm->flags |= DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE |
128 DISPLAY_FLAGS_SYNC_POSEDGE;
129 }
130
tfp410_set_timings(struct omap_dss_device * dssdev,struct videomode * vm)131 static void tfp410_set_timings(struct omap_dss_device *dssdev,
132 struct videomode *vm)
133 {
134 struct panel_drv_data *ddata = to_panel_data(dssdev);
135 struct omap_dss_device *in = ddata->in;
136
137 tfp410_fix_timings(vm);
138
139 ddata->vm = *vm;
140 dssdev->panel.vm = *vm;
141
142 in->ops.dpi->set_timings(in, vm);
143 }
144
tfp410_get_timings(struct omap_dss_device * dssdev,struct videomode * vm)145 static void tfp410_get_timings(struct omap_dss_device *dssdev,
146 struct videomode *vm)
147 {
148 struct panel_drv_data *ddata = to_panel_data(dssdev);
149
150 *vm = ddata->vm;
151 }
152
tfp410_check_timings(struct omap_dss_device * dssdev,struct videomode * vm)153 static int tfp410_check_timings(struct omap_dss_device *dssdev,
154 struct videomode *vm)
155 {
156 struct panel_drv_data *ddata = to_panel_data(dssdev);
157 struct omap_dss_device *in = ddata->in;
158
159 tfp410_fix_timings(vm);
160
161 return in->ops.dpi->check_timings(in, vm);
162 }
163
164 static const struct omapdss_dvi_ops tfp410_dvi_ops = {
165 .connect = tfp410_connect,
166 .disconnect = tfp410_disconnect,
167
168 .enable = tfp410_enable,
169 .disable = tfp410_disable,
170
171 .check_timings = tfp410_check_timings,
172 .set_timings = tfp410_set_timings,
173 .get_timings = tfp410_get_timings,
174 };
175
tfp410_probe_of(struct platform_device * pdev)176 static int tfp410_probe_of(struct platform_device *pdev)
177 {
178 struct panel_drv_data *ddata = platform_get_drvdata(pdev);
179 struct device_node *node = pdev->dev.of_node;
180 int gpio;
181
182 gpio = of_get_named_gpio(node, "powerdown-gpios", 0);
183
184 if (gpio_is_valid(gpio) || gpio == -ENOENT) {
185 ddata->pd_gpio = gpio;
186 } else {
187 if (gpio != -EPROBE_DEFER)
188 dev_err(&pdev->dev, "failed to parse PD gpio\n");
189 return gpio;
190 }
191
192 return 0;
193 }
194
tfp410_probe(struct platform_device * pdev)195 static int tfp410_probe(struct platform_device *pdev)
196 {
197 struct panel_drv_data *ddata;
198 struct omap_dss_device *dssdev;
199 int r;
200
201 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
202 if (!ddata)
203 return -ENOMEM;
204
205 platform_set_drvdata(pdev, ddata);
206
207 r = tfp410_probe_of(pdev);
208 if (r)
209 return r;
210
211 if (gpio_is_valid(ddata->pd_gpio)) {
212 r = devm_gpio_request_one(&pdev->dev, ddata->pd_gpio,
213 GPIOF_OUT_INIT_LOW, "tfp410 PD");
214 if (r) {
215 dev_err(&pdev->dev, "Failed to request PD GPIO %d\n",
216 ddata->pd_gpio);
217 return r;
218 }
219 }
220
221 dssdev = &ddata->dssdev;
222 dssdev->ops.dvi = &tfp410_dvi_ops;
223 dssdev->dev = &pdev->dev;
224 dssdev->type = OMAP_DISPLAY_TYPE_DPI;
225 dssdev->output_type = OMAP_DISPLAY_TYPE_DVI;
226 dssdev->owner = THIS_MODULE;
227 dssdev->port_num = 1;
228
229 r = omapdss_register_output(dssdev);
230 if (r) {
231 dev_err(&pdev->dev, "Failed to register output\n");
232 return r;
233 }
234
235 return 0;
236 }
237
tfp410_remove(struct platform_device * pdev)238 static int __exit tfp410_remove(struct platform_device *pdev)
239 {
240 struct panel_drv_data *ddata = platform_get_drvdata(pdev);
241 struct omap_dss_device *dssdev = &ddata->dssdev;
242
243 omapdss_unregister_output(&ddata->dssdev);
244
245 WARN_ON(omapdss_device_is_enabled(dssdev));
246 if (omapdss_device_is_enabled(dssdev))
247 tfp410_disable(dssdev);
248
249 WARN_ON(omapdss_device_is_connected(dssdev));
250 if (omapdss_device_is_connected(dssdev))
251 tfp410_disconnect(dssdev, dssdev->dst);
252
253 return 0;
254 }
255
256 static const struct of_device_id tfp410_of_match[] = {
257 { .compatible = "omapdss,ti,tfp410", },
258 {},
259 };
260
261 MODULE_DEVICE_TABLE(of, tfp410_of_match);
262
263 static struct platform_driver tfp410_driver = {
264 .probe = tfp410_probe,
265 .remove = __exit_p(tfp410_remove),
266 .driver = {
267 .name = "tfp410",
268 .of_match_table = tfp410_of_match,
269 .suppress_bind_attrs = true,
270 },
271 };
272
273 module_platform_driver(tfp410_driver);
274
275 MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
276 MODULE_DESCRIPTION("TFP410 DPI to DVI encoder driver");
277 MODULE_LICENSE("GPL");
278