1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * OPA362 analog video amplifier with output/power control
4 *
5 * Copyright (C) 2014 Golden Delicious Computers
6 * Author: H. Nikolaus Schaller <hns@goldelico.com>
7 *
8 * based on encoder-tfp410
9 *
10 * Copyright (C) 2013 Texas Instruments
11 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
12 */
13
14 #include <linux/gpio/consumer.h>
15 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/slab.h>
18
19 #include <video/omapfb_dss.h>
20
21 struct panel_drv_data {
22 struct omap_dss_device dssdev;
23 struct omap_dss_device *in;
24
25 struct gpio_desc *enable_gpio;
26
27 struct omap_video_timings timings;
28 };
29
30 #define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
31
opa362_connect(struct omap_dss_device * dssdev,struct omap_dss_device * dst)32 static int opa362_connect(struct omap_dss_device *dssdev,
33 struct omap_dss_device *dst)
34 {
35 struct panel_drv_data *ddata = to_panel_data(dssdev);
36 struct omap_dss_device *in = ddata->in;
37 int r;
38
39 dev_dbg(dssdev->dev, "connect\n");
40
41 if (omapdss_device_is_connected(dssdev))
42 return -EBUSY;
43
44 r = in->ops.atv->connect(in, dssdev);
45 if (r)
46 return r;
47
48 dst->src = dssdev;
49 dssdev->dst = dst;
50
51 return 0;
52 }
53
opa362_disconnect(struct omap_dss_device * dssdev,struct omap_dss_device * dst)54 static void opa362_disconnect(struct omap_dss_device *dssdev,
55 struct omap_dss_device *dst)
56 {
57 struct panel_drv_data *ddata = to_panel_data(dssdev);
58 struct omap_dss_device *in = ddata->in;
59
60 dev_dbg(dssdev->dev, "disconnect\n");
61
62 WARN_ON(!omapdss_device_is_connected(dssdev));
63 if (!omapdss_device_is_connected(dssdev))
64 return;
65
66 WARN_ON(dst != dssdev->dst);
67 if (dst != dssdev->dst)
68 return;
69
70 dst->src = NULL;
71 dssdev->dst = NULL;
72
73 in->ops.atv->disconnect(in, &ddata->dssdev);
74 }
75
opa362_enable(struct omap_dss_device * dssdev)76 static int opa362_enable(struct omap_dss_device *dssdev)
77 {
78 struct panel_drv_data *ddata = to_panel_data(dssdev);
79 struct omap_dss_device *in = ddata->in;
80 int r;
81
82 dev_dbg(dssdev->dev, "enable\n");
83
84 if (!omapdss_device_is_connected(dssdev))
85 return -ENODEV;
86
87 if (omapdss_device_is_enabled(dssdev))
88 return 0;
89
90 in->ops.atv->set_timings(in, &ddata->timings);
91
92 r = in->ops.atv->enable(in);
93 if (r)
94 return r;
95
96 if (ddata->enable_gpio)
97 gpiod_set_value_cansleep(ddata->enable_gpio, 1);
98
99 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
100
101 return 0;
102 }
103
opa362_disable(struct omap_dss_device * dssdev)104 static void opa362_disable(struct omap_dss_device *dssdev)
105 {
106 struct panel_drv_data *ddata = to_panel_data(dssdev);
107 struct omap_dss_device *in = ddata->in;
108
109 dev_dbg(dssdev->dev, "disable\n");
110
111 if (!omapdss_device_is_enabled(dssdev))
112 return;
113
114 if (ddata->enable_gpio)
115 gpiod_set_value_cansleep(ddata->enable_gpio, 0);
116
117 in->ops.atv->disable(in);
118
119 dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
120 }
121
opa362_set_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)122 static void opa362_set_timings(struct omap_dss_device *dssdev,
123 struct omap_video_timings *timings)
124 {
125 struct panel_drv_data *ddata = to_panel_data(dssdev);
126 struct omap_dss_device *in = ddata->in;
127
128 dev_dbg(dssdev->dev, "set_timings\n");
129
130 ddata->timings = *timings;
131 dssdev->panel.timings = *timings;
132
133 in->ops.atv->set_timings(in, timings);
134 }
135
opa362_get_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)136 static void opa362_get_timings(struct omap_dss_device *dssdev,
137 struct omap_video_timings *timings)
138 {
139 struct panel_drv_data *ddata = to_panel_data(dssdev);
140
141 dev_dbg(dssdev->dev, "get_timings\n");
142
143 *timings = ddata->timings;
144 }
145
opa362_check_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)146 static int opa362_check_timings(struct omap_dss_device *dssdev,
147 struct omap_video_timings *timings)
148 {
149 struct panel_drv_data *ddata = to_panel_data(dssdev);
150 struct omap_dss_device *in = ddata->in;
151
152 dev_dbg(dssdev->dev, "check_timings\n");
153
154 return in->ops.atv->check_timings(in, timings);
155 }
156
opa362_set_type(struct omap_dss_device * dssdev,enum omap_dss_venc_type type)157 static void opa362_set_type(struct omap_dss_device *dssdev,
158 enum omap_dss_venc_type type)
159 {
160 /* we can only drive a COMPOSITE output */
161 WARN_ON(type != OMAP_DSS_VENC_TYPE_COMPOSITE);
162
163 }
164
165 static const struct omapdss_atv_ops opa362_atv_ops = {
166 .connect = opa362_connect,
167 .disconnect = opa362_disconnect,
168
169 .enable = opa362_enable,
170 .disable = opa362_disable,
171
172 .check_timings = opa362_check_timings,
173 .set_timings = opa362_set_timings,
174 .get_timings = opa362_get_timings,
175
176 .set_type = opa362_set_type,
177 };
178
opa362_probe(struct platform_device * pdev)179 static int opa362_probe(struct platform_device *pdev)
180 {
181 struct device_node *node = pdev->dev.of_node;
182 struct panel_drv_data *ddata;
183 struct omap_dss_device *dssdev, *in;
184 struct gpio_desc *gpio;
185 int r;
186
187 dev_dbg(&pdev->dev, "probe\n");
188
189 if (node == NULL) {
190 dev_err(&pdev->dev, "Unable to find device tree\n");
191 return -EINVAL;
192 }
193
194 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
195 if (!ddata)
196 return -ENOMEM;
197
198 platform_set_drvdata(pdev, ddata);
199
200 gpio = devm_gpiod_get_optional(&pdev->dev, "enable", GPIOD_OUT_LOW);
201 if (IS_ERR(gpio))
202 return PTR_ERR(gpio);
203
204 ddata->enable_gpio = gpio;
205
206 in = omapdss_of_find_source_for_first_ep(node);
207 if (IS_ERR(in)) {
208 dev_err(&pdev->dev, "failed to find video source\n");
209 return PTR_ERR(in);
210 }
211
212 ddata->in = in;
213
214 dssdev = &ddata->dssdev;
215 dssdev->ops.atv = &opa362_atv_ops;
216 dssdev->dev = &pdev->dev;
217 dssdev->type = OMAP_DISPLAY_TYPE_VENC;
218 dssdev->output_type = OMAP_DISPLAY_TYPE_VENC;
219 dssdev->owner = THIS_MODULE;
220
221 r = omapdss_register_output(dssdev);
222 if (r) {
223 dev_err(&pdev->dev, "Failed to register output\n");
224 goto err_reg;
225 }
226
227 return 0;
228 err_reg:
229 omap_dss_put_device(ddata->in);
230 return r;
231 }
232
opa362_remove(struct platform_device * pdev)233 static void opa362_remove(struct platform_device *pdev)
234 {
235 struct panel_drv_data *ddata = platform_get_drvdata(pdev);
236 struct omap_dss_device *dssdev = &ddata->dssdev;
237 struct omap_dss_device *in = ddata->in;
238
239 omapdss_unregister_output(&ddata->dssdev);
240
241 WARN_ON(omapdss_device_is_enabled(dssdev));
242 if (omapdss_device_is_enabled(dssdev))
243 opa362_disable(dssdev);
244
245 WARN_ON(omapdss_device_is_connected(dssdev));
246 if (omapdss_device_is_connected(dssdev))
247 opa362_disconnect(dssdev, dssdev->dst);
248
249 omap_dss_put_device(in);
250 }
251
252 static const struct of_device_id opa362_of_match[] = {
253 { .compatible = "omapdss,ti,opa362", },
254 {},
255 };
256 MODULE_DEVICE_TABLE(of, opa362_of_match);
257
258 static struct platform_driver opa362_driver = {
259 .probe = opa362_probe,
260 .remove = opa362_remove,
261 .driver = {
262 .name = "amplifier-opa362",
263 .of_match_table = opa362_of_match,
264 },
265 };
266
267 module_platform_driver(opa362_driver);
268
269 MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
270 MODULE_DESCRIPTION("OPA362 analog video amplifier with output/power control");
271 MODULE_LICENSE("GPL v2");
272