Commit | Line | Data |
---|---|---|
f76ee892 TV |
1 | /* |
2 | * TPD12S015 HDMI ESD protection & level shifter chip driver | |
3 | * | |
4 | * Copyright (C) 2013 Texas Instruments | |
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/completion.h> | |
13 | #include <linux/delay.h> | |
14 | #include <linux/module.h> | |
ac316725 | 15 | #include <linux/mod_devicetable.h> |
f76ee892 TV |
16 | #include <linux/slab.h> |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/gpio/consumer.h> | |
19 | ||
62d9e44e | 20 | #include <video/omapfb_dss.h> |
f76ee892 TV |
21 | |
22 | struct panel_drv_data { | |
23 | struct omap_dss_device dssdev; | |
24 | struct omap_dss_device *in; | |
25 | ||
26 | struct gpio_desc *ct_cp_hpd_gpio; | |
27 | struct gpio_desc *ls_oe_gpio; | |
28 | struct gpio_desc *hpd_gpio; | |
29 | ||
30 | struct omap_video_timings timings; | |
31 | }; | |
32 | ||
33 | #define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) | |
34 | ||
35 | static int tpd_connect(struct omap_dss_device *dssdev, | |
36 | struct omap_dss_device *dst) | |
37 | { | |
38 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
39 | struct omap_dss_device *in = ddata->in; | |
40 | int r; | |
41 | ||
42 | r = in->ops.hdmi->connect(in, dssdev); | |
43 | if (r) | |
44 | return r; | |
45 | ||
46 | dst->src = dssdev; | |
47 | dssdev->dst = dst; | |
48 | ||
49 | if (ddata->ct_cp_hpd_gpio) { | |
50 | gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1); | |
51 | /* DC-DC converter needs at max 300us to get to 90% of 5V */ | |
52 | udelay(300); | |
53 | } | |
54 | ||
55 | return 0; | |
56 | } | |
57 | ||
58 | static void tpd_disconnect(struct omap_dss_device *dssdev, | |
59 | struct omap_dss_device *dst) | |
60 | { | |
61 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
62 | struct omap_dss_device *in = ddata->in; | |
63 | ||
64 | WARN_ON(dst != dssdev->dst); | |
65 | ||
66 | if (dst != dssdev->dst) | |
67 | return; | |
68 | ||
69 | gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0); | |
70 | ||
71 | dst->src = NULL; | |
72 | dssdev->dst = NULL; | |
73 | ||
74 | in->ops.hdmi->disconnect(in, &ddata->dssdev); | |
75 | } | |
76 | ||
77 | static int tpd_enable(struct omap_dss_device *dssdev) | |
78 | { | |
79 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
80 | struct omap_dss_device *in = ddata->in; | |
81 | int r; | |
82 | ||
83 | if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) | |
84 | return 0; | |
85 | ||
86 | in->ops.hdmi->set_timings(in, &ddata->timings); | |
87 | ||
88 | r = in->ops.hdmi->enable(in); | |
89 | if (r) | |
90 | return r; | |
91 | ||
92 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; | |
93 | ||
94 | return r; | |
95 | } | |
96 | ||
97 | static void tpd_disable(struct omap_dss_device *dssdev) | |
98 | { | |
99 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
100 | struct omap_dss_device *in = ddata->in; | |
101 | ||
102 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) | |
103 | return; | |
104 | ||
105 | in->ops.hdmi->disable(in); | |
106 | ||
107 | dssdev->state = OMAP_DSS_DISPLAY_DISABLED; | |
108 | } | |
109 | ||
110 | static void tpd_set_timings(struct omap_dss_device *dssdev, | |
111 | struct omap_video_timings *timings) | |
112 | { | |
113 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
114 | struct omap_dss_device *in = ddata->in; | |
115 | ||
116 | ddata->timings = *timings; | |
117 | dssdev->panel.timings = *timings; | |
118 | ||
119 | in->ops.hdmi->set_timings(in, timings); | |
120 | } | |
121 | ||
122 | static void tpd_get_timings(struct omap_dss_device *dssdev, | |
123 | struct omap_video_timings *timings) | |
124 | { | |
125 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
126 | ||
127 | *timings = ddata->timings; | |
128 | } | |
129 | ||
130 | static int tpd_check_timings(struct omap_dss_device *dssdev, | |
131 | struct omap_video_timings *timings) | |
132 | { | |
133 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
134 | struct omap_dss_device *in = ddata->in; | |
135 | int r; | |
136 | ||
137 | r = in->ops.hdmi->check_timings(in, timings); | |
138 | ||
139 | return r; | |
140 | } | |
141 | ||
142 | static int tpd_read_edid(struct omap_dss_device *dssdev, | |
143 | u8 *edid, int len) | |
144 | { | |
145 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
146 | struct omap_dss_device *in = ddata->in; | |
147 | int r; | |
148 | ||
149 | if (!gpiod_get_value_cansleep(ddata->hpd_gpio)) | |
150 | return -ENODEV; | |
151 | ||
152 | gpiod_set_value_cansleep(ddata->ls_oe_gpio, 1); | |
153 | ||
154 | r = in->ops.hdmi->read_edid(in, edid, len); | |
155 | ||
156 | gpiod_set_value_cansleep(ddata->ls_oe_gpio, 0); | |
157 | ||
158 | return r; | |
159 | } | |
160 | ||
161 | static bool tpd_detect(struct omap_dss_device *dssdev) | |
162 | { | |
163 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
164 | ||
165 | return gpiod_get_value_cansleep(ddata->hpd_gpio); | |
166 | } | |
167 | ||
168 | static int tpd_set_infoframe(struct omap_dss_device *dssdev, | |
169 | const struct hdmi_avi_infoframe *avi) | |
170 | { | |
171 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
172 | struct omap_dss_device *in = ddata->in; | |
173 | ||
174 | return in->ops.hdmi->set_infoframe(in, avi); | |
175 | } | |
176 | ||
177 | static int tpd_set_hdmi_mode(struct omap_dss_device *dssdev, | |
178 | bool hdmi_mode) | |
179 | { | |
180 | struct panel_drv_data *ddata = to_panel_data(dssdev); | |
181 | struct omap_dss_device *in = ddata->in; | |
182 | ||
183 | return in->ops.hdmi->set_hdmi_mode(in, hdmi_mode); | |
184 | } | |
185 | ||
186 | static const struct omapdss_hdmi_ops tpd_hdmi_ops = { | |
187 | .connect = tpd_connect, | |
188 | .disconnect = tpd_disconnect, | |
189 | ||
190 | .enable = tpd_enable, | |
191 | .disable = tpd_disable, | |
192 | ||
193 | .check_timings = tpd_check_timings, | |
194 | .set_timings = tpd_set_timings, | |
195 | .get_timings = tpd_get_timings, | |
196 | ||
197 | .read_edid = tpd_read_edid, | |
198 | .detect = tpd_detect, | |
199 | .set_infoframe = tpd_set_infoframe, | |
200 | .set_hdmi_mode = tpd_set_hdmi_mode, | |
201 | }; | |
202 | ||
203 | static int tpd_probe_of(struct platform_device *pdev) | |
204 | { | |
205 | struct panel_drv_data *ddata = platform_get_drvdata(pdev); | |
206 | struct device_node *node = pdev->dev.of_node; | |
207 | struct omap_dss_device *in; | |
208 | ||
209 | in = omapdss_of_find_source_for_first_ep(node); | |
210 | if (IS_ERR(in)) { | |
211 | dev_err(&pdev->dev, "failed to find video source\n"); | |
212 | return PTR_ERR(in); | |
213 | } | |
214 | ||
215 | ddata->in = in; | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | static int tpd_probe(struct platform_device *pdev) | |
221 | { | |
222 | struct omap_dss_device *in, *dssdev; | |
223 | struct panel_drv_data *ddata; | |
224 | int r; | |
225 | struct gpio_desc *gpio; | |
226 | ||
227 | ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); | |
228 | if (!ddata) | |
229 | return -ENOMEM; | |
230 | ||
231 | platform_set_drvdata(pdev, ddata); | |
232 | ||
233 | if (pdev->dev.of_node) { | |
234 | r = tpd_probe_of(pdev); | |
235 | if (r) | |
236 | return r; | |
237 | } else { | |
238 | return -ENODEV; | |
239 | } | |
240 | ||
241 | ||
242 | gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0, | |
243 | GPIOD_OUT_LOW); | |
244 | if (IS_ERR(gpio)) | |
245 | goto err_gpio; | |
246 | ||
247 | ddata->ct_cp_hpd_gpio = gpio; | |
248 | ||
249 | gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1, | |
250 | GPIOD_OUT_LOW); | |
251 | if (IS_ERR(gpio)) | |
252 | goto err_gpio; | |
253 | ||
254 | ddata->ls_oe_gpio = gpio; | |
255 | ||
256 | gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2, | |
257 | GPIOD_IN); | |
258 | if (IS_ERR(gpio)) | |
259 | goto err_gpio; | |
260 | ||
261 | ddata->hpd_gpio = gpio; | |
262 | ||
263 | dssdev = &ddata->dssdev; | |
264 | dssdev->ops.hdmi = &tpd_hdmi_ops; | |
265 | dssdev->dev = &pdev->dev; | |
266 | dssdev->type = OMAP_DISPLAY_TYPE_HDMI; | |
267 | dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI; | |
268 | dssdev->owner = THIS_MODULE; | |
269 | dssdev->port_num = 1; | |
270 | ||
271 | in = ddata->in; | |
272 | ||
273 | r = omapdss_register_output(dssdev); | |
274 | if (r) { | |
275 | dev_err(&pdev->dev, "Failed to register output\n"); | |
276 | goto err_reg; | |
277 | } | |
278 | ||
279 | return 0; | |
280 | err_reg: | |
281 | err_gpio: | |
282 | omap_dss_put_device(ddata->in); | |
283 | return r; | |
284 | } | |
285 | ||
286 | static int __exit tpd_remove(struct platform_device *pdev) | |
287 | { | |
288 | struct panel_drv_data *ddata = platform_get_drvdata(pdev); | |
289 | struct omap_dss_device *dssdev = &ddata->dssdev; | |
290 | struct omap_dss_device *in = ddata->in; | |
291 | ||
292 | omapdss_unregister_output(&ddata->dssdev); | |
293 | ||
294 | WARN_ON(omapdss_device_is_enabled(dssdev)); | |
295 | if (omapdss_device_is_enabled(dssdev)) | |
296 | tpd_disable(dssdev); | |
297 | ||
298 | WARN_ON(omapdss_device_is_connected(dssdev)); | |
299 | if (omapdss_device_is_connected(dssdev)) | |
300 | tpd_disconnect(dssdev, dssdev->dst); | |
301 | ||
302 | omap_dss_put_device(in); | |
303 | ||
304 | return 0; | |
305 | } | |
306 | ||
307 | static const struct of_device_id tpd_of_match[] = { | |
308 | { .compatible = "omapdss,ti,tpd12s015", }, | |
309 | {}, | |
310 | }; | |
311 | ||
312 | MODULE_DEVICE_TABLE(of, tpd_of_match); | |
313 | ||
314 | static struct platform_driver tpd_driver = { | |
315 | .probe = tpd_probe, | |
316 | .remove = __exit_p(tpd_remove), | |
317 | .driver = { | |
318 | .name = "tpd12s015", | |
319 | .of_match_table = tpd_of_match, | |
320 | .suppress_bind_attrs = true, | |
321 | }, | |
322 | }; | |
323 | ||
324 | module_platform_driver(tpd_driver); | |
325 | ||
326 | MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); | |
327 | MODULE_DESCRIPTION("TPD12S015 driver"); | |
328 | MODULE_LICENSE("GPL"); |