blob: e3d98d78fc401167cb5afeefc190a1c871e27e79 [file] [log] [blame]
Tomi Valkeinena0ee5772013-05-24 14:20:14 +03001/*
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>
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030016#include <linux/platform_device.h>
Manisha Agrawal460543b2015-11-03 15:22:49 -060017#include <linux/gpio/consumer.h>
Peter Ujfalusi232ce602017-06-02 15:26:37 +030018#include <linux/mutex.h>
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030019
Peter Ujfalusi32043da2016-05-27 14:40:49 +030020#include "../dss/omapdss.h"
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030021
22struct panel_drv_data {
23 struct omap_dss_device dssdev;
24 struct omap_dss_device *in;
Peter Ujfalusi232ce602017-06-02 15:26:37 +030025 void (*hpd_cb)(void *cb_data, enum drm_connector_status status);
26 void *hpd_cb_data;
27 bool hpd_enabled;
28 struct mutex hpd_lock;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030029
Manisha Agrawal460543b2015-11-03 15:22:49 -060030 struct gpio_desc *ct_cp_hpd_gpio;
31 struct gpio_desc *ls_oe_gpio;
32 struct gpio_desc *hpd_gpio;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030033
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +030034 struct videomode vm;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030035};
36
37#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
38
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030039static int tpd_connect(struct omap_dss_device *dssdev,
40 struct omap_dss_device *dst)
41{
42 struct panel_drv_data *ddata = to_panel_data(dssdev);
43 struct omap_dss_device *in = ddata->in;
44 int r;
45
46 r = in->ops.hdmi->connect(in, dssdev);
47 if (r)
48 return r;
49
Tomi Valkeinena73fdc62013-07-24 13:01:34 +030050 dst->src = dssdev;
Tomi Valkeinen9560dc102013-07-24 13:06:54 +030051 dssdev->dst = dst;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030052
Manisha Agrawal460543b2015-11-03 15:22:49 -060053 gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1);
Hans Verkuil3b86b9e2017-08-02 10:54:00 +020054 gpiod_set_value_cansleep(ddata->ls_oe_gpio, 1);
55
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030056 /* DC-DC converter needs at max 300us to get to 90% of 5V */
57 udelay(300);
58
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030059 return 0;
60}
61
62static void tpd_disconnect(struct omap_dss_device *dssdev,
63 struct omap_dss_device *dst)
64{
65 struct panel_drv_data *ddata = to_panel_data(dssdev);
66 struct omap_dss_device *in = ddata->in;
67
Tomi Valkeinen9560dc102013-07-24 13:06:54 +030068 WARN_ON(dst != dssdev->dst);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030069
Tomi Valkeinen9560dc102013-07-24 13:06:54 +030070 if (dst != dssdev->dst)
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030071 return;
72
Manisha Agrawal460543b2015-11-03 15:22:49 -060073 gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0);
Hans Verkuil3b86b9e2017-08-02 10:54:00 +020074 gpiod_set_value_cansleep(ddata->ls_oe_gpio, 0);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030075
Tomi Valkeinena73fdc62013-07-24 13:01:34 +030076 dst->src = NULL;
Tomi Valkeinen9560dc102013-07-24 13:06:54 +030077 dssdev->dst = NULL;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030078
79 in->ops.hdmi->disconnect(in, &ddata->dssdev);
80}
81
82static int tpd_enable(struct omap_dss_device *dssdev)
83{
84 struct panel_drv_data *ddata = to_panel_data(dssdev);
85 struct omap_dss_device *in = ddata->in;
86 int r;
87
88 if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
89 return 0;
90
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +030091 in->ops.hdmi->set_timings(in, &ddata->vm);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +030092
93 r = in->ops.hdmi->enable(in);
94 if (r)
95 return r;
96
97 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
98
99 return r;
100}
101
102static void tpd_disable(struct omap_dss_device *dssdev)
103{
104 struct panel_drv_data *ddata = to_panel_data(dssdev);
105 struct omap_dss_device *in = ddata->in;
106
107 if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
108 return;
109
110 in->ops.hdmi->disable(in);
111
112 dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
113}
114
115static void tpd_set_timings(struct omap_dss_device *dssdev,
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +0300116 struct videomode *vm)
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300117{
118 struct panel_drv_data *ddata = to_panel_data(dssdev);
119 struct omap_dss_device *in = ddata->in;
120
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +0300121 ddata->vm = *vm;
122 dssdev->panel.vm = *vm;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300123
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +0300124 in->ops.hdmi->set_timings(in, vm);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300125}
126
127static void tpd_get_timings(struct omap_dss_device *dssdev,
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +0300128 struct videomode *vm)
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300129{
130 struct panel_drv_data *ddata = to_panel_data(dssdev);
131
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +0300132 *vm = ddata->vm;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300133}
134
135static int tpd_check_timings(struct omap_dss_device *dssdev,
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +0300136 struct videomode *vm)
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300137{
138 struct panel_drv_data *ddata = to_panel_data(dssdev);
139 struct omap_dss_device *in = ddata->in;
140 int r;
141
Peter Ujfalusida11bbbb2016-09-22 14:07:04 +0300142 r = in->ops.hdmi->check_timings(in, vm);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300143
144 return r;
145}
146
147static int tpd_read_edid(struct omap_dss_device *dssdev,
148 u8 *edid, int len)
149{
150 struct panel_drv_data *ddata = to_panel_data(dssdev);
151 struct omap_dss_device *in = ddata->in;
152
Manisha Agrawal460543b2015-11-03 15:22:49 -0600153 if (!gpiod_get_value_cansleep(ddata->hpd_gpio))
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300154 return -ENODEV;
155
Hans Verkuil3b86b9e2017-08-02 10:54:00 +0200156 return in->ops.hdmi->read_edid(in, edid, len);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300157}
158
159static bool tpd_detect(struct omap_dss_device *dssdev)
160{
161 struct panel_drv_data *ddata = to_panel_data(dssdev);
Hans Verkuil019114e2017-08-17 15:19:57 +0200162 struct omap_dss_device *in = ddata->in;
163 bool connected = gpiod_get_value_cansleep(ddata->hpd_gpio);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300164
Hans Verkuil019114e2017-08-17 15:19:57 +0200165 if (!connected && in->ops.hdmi->lost_hotplug)
166 in->ops.hdmi->lost_hotplug(in);
167 return connected;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300168}
169
Peter Ujfalusi232ce602017-06-02 15:26:37 +0300170static int tpd_register_hpd_cb(struct omap_dss_device *dssdev,
171 void (*cb)(void *cb_data,
172 enum drm_connector_status status),
173 void *cb_data)
174{
175 struct panel_drv_data *ddata = to_panel_data(dssdev);
176
177 mutex_lock(&ddata->hpd_lock);
178 ddata->hpd_cb = cb;
179 ddata->hpd_cb_data = cb_data;
180 mutex_unlock(&ddata->hpd_lock);
181
182 return 0;
183}
184
185static void tpd_unregister_hpd_cb(struct omap_dss_device *dssdev)
186{
187 struct panel_drv_data *ddata = to_panel_data(dssdev);
188
189 mutex_lock(&ddata->hpd_lock);
190 ddata->hpd_cb = NULL;
191 ddata->hpd_cb_data = NULL;
192 mutex_unlock(&ddata->hpd_lock);
193}
194
195static void tpd_enable_hpd(struct omap_dss_device *dssdev)
196{
197 struct panel_drv_data *ddata = to_panel_data(dssdev);
198
199 mutex_lock(&ddata->hpd_lock);
200 ddata->hpd_enabled = true;
201 mutex_unlock(&ddata->hpd_lock);
202}
203
204static void tpd_disable_hpd(struct omap_dss_device *dssdev)
205{
206 struct panel_drv_data *ddata = to_panel_data(dssdev);
207
208 mutex_lock(&ddata->hpd_lock);
209 ddata->hpd_enabled = false;
210 mutex_unlock(&ddata->hpd_lock);
211}
212
Tomi Valkeinen9fb17372014-06-18 12:58:02 +0300213static int tpd_set_infoframe(struct omap_dss_device *dssdev,
214 const struct hdmi_avi_infoframe *avi)
215{
216 struct panel_drv_data *ddata = to_panel_data(dssdev);
217 struct omap_dss_device *in = ddata->in;
218
219 return in->ops.hdmi->set_infoframe(in, avi);
220}
221
222static int tpd_set_hdmi_mode(struct omap_dss_device *dssdev,
223 bool hdmi_mode)
224{
225 struct panel_drv_data *ddata = to_panel_data(dssdev);
226 struct omap_dss_device *in = ddata->in;
227
228 return in->ops.hdmi->set_hdmi_mode(in, hdmi_mode);
229}
230
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300231static const struct omapdss_hdmi_ops tpd_hdmi_ops = {
232 .connect = tpd_connect,
233 .disconnect = tpd_disconnect,
234
235 .enable = tpd_enable,
236 .disable = tpd_disable,
237
238 .check_timings = tpd_check_timings,
239 .set_timings = tpd_set_timings,
240 .get_timings = tpd_get_timings,
241
242 .read_edid = tpd_read_edid,
243 .detect = tpd_detect,
Peter Ujfalusi232ce602017-06-02 15:26:37 +0300244 .register_hpd_cb = tpd_register_hpd_cb,
245 .unregister_hpd_cb = tpd_unregister_hpd_cb,
246 .enable_hpd = tpd_enable_hpd,
247 .disable_hpd = tpd_disable_hpd,
Tomi Valkeinen9fb17372014-06-18 12:58:02 +0300248 .set_infoframe = tpd_set_infoframe,
249 .set_hdmi_mode = tpd_set_hdmi_mode,
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300250};
251
Peter Ujfalusi232ce602017-06-02 15:26:37 +0300252static irqreturn_t tpd_hpd_isr(int irq, void *data)
253{
254 struct panel_drv_data *ddata = data;
255
256 mutex_lock(&ddata->hpd_lock);
257 if (ddata->hpd_enabled && ddata->hpd_cb) {
258 enum drm_connector_status status;
259
260 if (tpd_detect(&ddata->dssdev))
261 status = connector_status_connected;
262 else
263 status = connector_status_disconnected;
264
265 ddata->hpd_cb(ddata->hpd_cb_data, status);
266 }
267 mutex_unlock(&ddata->hpd_lock);
268
269 return IRQ_HANDLED;
270}
271
Tomi Valkeinen5e4c89c2013-07-30 10:37:17 +0300272static int tpd_probe_of(struct platform_device *pdev)
273{
274 struct panel_drv_data *ddata = platform_get_drvdata(pdev);
275 struct device_node *node = pdev->dev.of_node;
276 struct omap_dss_device *in;
Tomi Valkeinen5e4c89c2013-07-30 10:37:17 +0300277
278 in = omapdss_of_find_source_for_first_ep(node);
279 if (IS_ERR(in)) {
280 dev_err(&pdev->dev, "failed to find video source\n");
281 return PTR_ERR(in);
282 }
283
284 ddata->in = in;
285
286 return 0;
287}
288
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300289static int tpd_probe(struct platform_device *pdev)
290{
291 struct omap_dss_device *in, *dssdev;
292 struct panel_drv_data *ddata;
293 int r;
Manisha Agrawal460543b2015-11-03 15:22:49 -0600294 struct gpio_desc *gpio;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300295
296 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
297 if (!ddata)
298 return -ENOMEM;
299
300 platform_set_drvdata(pdev, ddata);
301
Manisha Agrawal45dd63c2015-11-03 15:22:48 -0600302 if (!pdev->dev.of_node)
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300303 return -ENODEV;
Manisha Agrawal45dd63c2015-11-03 15:22:48 -0600304
305 r = tpd_probe_of(pdev);
306 if (r)
307 return r;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300308
Manisha Agrawald8e31632015-11-03 15:22:50 -0600309 gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
Manisha Agrawal460543b2015-11-03 15:22:49 -0600310 GPIOD_OUT_LOW);
Tomi Valkeinen5c2a3922016-11-22 10:11:07 +0200311 if (IS_ERR(gpio)) {
312 r = PTR_ERR(gpio);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300313 goto err_gpio;
Tomi Valkeinen5c2a3922016-11-22 10:11:07 +0200314 }
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300315
Manisha Agrawal460543b2015-11-03 15:22:49 -0600316 ddata->ct_cp_hpd_gpio = gpio;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300317
Manisha Agrawal460543b2015-11-03 15:22:49 -0600318 gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1,
319 GPIOD_OUT_LOW);
Tomi Valkeinen5c2a3922016-11-22 10:11:07 +0200320 if (IS_ERR(gpio)) {
321 r = PTR_ERR(gpio);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300322 goto err_gpio;
Tomi Valkeinen5c2a3922016-11-22 10:11:07 +0200323 }
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300324
Manisha Agrawal460543b2015-11-03 15:22:49 -0600325 ddata->ls_oe_gpio = gpio;
326
327 gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2,
328 GPIOD_IN);
Tomi Valkeinen5c2a3922016-11-22 10:11:07 +0200329 if (IS_ERR(gpio)) {
330 r = PTR_ERR(gpio);
Manisha Agrawal460543b2015-11-03 15:22:49 -0600331 goto err_gpio;
Tomi Valkeinen5c2a3922016-11-22 10:11:07 +0200332 }
Manisha Agrawal460543b2015-11-03 15:22:49 -0600333
334 ddata->hpd_gpio = gpio;
335
Peter Ujfalusi232ce602017-06-02 15:26:37 +0300336 mutex_init(&ddata->hpd_lock);
337
338 r = devm_request_threaded_irq(&pdev->dev, gpiod_to_irq(ddata->hpd_gpio),
339 NULL, tpd_hpd_isr,
340 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
341 "tpd12s015 hpd", ddata);
342 if (r)
343 goto err_gpio;
344
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300345 dssdev = &ddata->dssdev;
346 dssdev->ops.hdmi = &tpd_hdmi_ops;
347 dssdev->dev = &pdev->dev;
348 dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
349 dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI;
350 dssdev->owner = THIS_MODULE;
Archit Tanejaef691ff2014-04-22 17:43:48 +0530351 dssdev->port_num = 1;
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300352
353 in = ddata->in;
354
355 r = omapdss_register_output(dssdev);
356 if (r) {
357 dev_err(&pdev->dev, "Failed to register output\n");
358 goto err_reg;
359 }
360
361 return 0;
362err_reg:
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300363err_gpio:
364 omap_dss_put_device(ddata->in);
365 return r;
366}
367
368static int __exit tpd_remove(struct platform_device *pdev)
369{
370 struct panel_drv_data *ddata = platform_get_drvdata(pdev);
371 struct omap_dss_device *dssdev = &ddata->dssdev;
372 struct omap_dss_device *in = ddata->in;
373
374 omapdss_unregister_output(&ddata->dssdev);
375
376 WARN_ON(omapdss_device_is_enabled(dssdev));
377 if (omapdss_device_is_enabled(dssdev))
378 tpd_disable(dssdev);
379
380 WARN_ON(omapdss_device_is_connected(dssdev));
381 if (omapdss_device_is_connected(dssdev))
Tomi Valkeinen9560dc102013-07-24 13:06:54 +0300382 tpd_disconnect(dssdev, dssdev->dst);
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300383
384 omap_dss_put_device(in);
385
386 return 0;
387}
388
Tomi Valkeinen5e4c89c2013-07-30 10:37:17 +0300389static const struct of_device_id tpd_of_match[] = {
390 { .compatible = "omapdss,ti,tpd12s015", },
391 {},
392};
393
394MODULE_DEVICE_TABLE(of, tpd_of_match);
395
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300396static struct platform_driver tpd_driver = {
397 .probe = tpd_probe,
398 .remove = __exit_p(tpd_remove),
399 .driver = {
400 .name = "tpd12s015",
Tomi Valkeinen5e4c89c2013-07-30 10:37:17 +0300401 .of_match_table = tpd_of_match,
Tomi Valkeinen422ccbd2014-10-16 09:54:25 +0300402 .suppress_bind_attrs = true,
Tomi Valkeinena0ee5772013-05-24 14:20:14 +0300403 },
404};
405
406module_platform_driver(tpd_driver);
407
408MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
409MODULE_DESCRIPTION("TPD12S015 driver");
410MODULE_LICENSE("GPL");