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