blob: bff306e041cabef157929f4078401122c06510d9 [file] [log] [blame]
Tomi Valkeinenba2eac92011-09-01 09:17:41 +03001/*
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +02002 * TFP410 DPI-to-DVI chip
Tomi Valkeinenba2eac92011-09-01 09:17:41 +03003 *
4 * Copyright (C) 2011 Texas Instruments Inc
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 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <linux/module.h>
21#include <linux/slab.h>
22#include <video/omapdss.h>
23#include <linux/i2c.h>
Tomi Valkeinen2da35192011-12-22 10:37:33 +020024#include <linux/gpio.h>
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030025#include <drm/drm_edid.h>
26
Tomi Valkeinendac8eb52011-12-22 11:12:13 +020027#include <video/omap-panel-tfp410.h>
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030028
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +020029static const struct omap_video_timings tfp410_default_timings = {
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030030 .x_res = 640,
31 .y_res = 480,
32
33 .pixel_clock = 23500,
34
35 .hfp = 48,
36 .hsw = 32,
37 .hbp = 80,
38
39 .vfp = 3,
40 .vsw = 4,
41 .vbp = 7,
42};
43
44struct panel_drv_data {
45 struct omap_dss_device *dssdev;
46
47 struct mutex lock;
Tomi Valkeinen2da35192011-12-22 10:37:33 +020048
49 int pd_gpio;
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030050
Tomi Valkeinen958f2712012-02-17 12:19:48 +020051 struct i2c_adapter *i2c_adapter;
52};
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030053
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +020054static int tfp410_power_on(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030055{
Tomi Valkeinen2da35192011-12-22 10:37:33 +020056 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030057 int r;
58
59 if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
60 return 0;
61
62 r = omapdss_dpi_display_enable(dssdev);
63 if (r)
64 goto err0;
65
Tomi Valkeinen2da35192011-12-22 10:37:33 +020066 if (gpio_is_valid(ddata->pd_gpio))
Russ Dillaf461d62012-05-09 15:08:08 -070067 gpio_set_value_cansleep(ddata->pd_gpio, 1);
Tomi Valkeinen2da35192011-12-22 10:37:33 +020068
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030069 return 0;
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030070err0:
71 return r;
72}
73
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +020074static void tfp410_power_off(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030075{
Tomi Valkeinen2da35192011-12-22 10:37:33 +020076 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030077
78 if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
79 return;
80
Tomi Valkeinen2da35192011-12-22 10:37:33 +020081 if (gpio_is_valid(ddata->pd_gpio))
Russ Dillaf461d62012-05-09 15:08:08 -070082 gpio_set_value_cansleep(ddata->pd_gpio, 0);
Tomi Valkeinen2da35192011-12-22 10:37:33 +020083
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030084 omapdss_dpi_display_disable(dssdev);
85}
86
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +020087static int tfp410_probe(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030088{
89 struct panel_drv_data *ddata;
Tomi Valkeinen2da35192011-12-22 10:37:33 +020090 int r;
Tomi Valkeinen958f2712012-02-17 12:19:48 +020091 int i2c_bus_num;
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030092
Tomi Valkeinen958f2712012-02-17 12:19:48 +020093 ddata = devm_kzalloc(&dssdev->dev, sizeof(*ddata), GFP_KERNEL);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030094 if (!ddata)
95 return -ENOMEM;
96
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +020097 dssdev->panel.timings = tfp410_default_timings;
Tomi Valkeinenba2eac92011-09-01 09:17:41 +030098 dssdev->panel.config = OMAP_DSS_LCD_TFT;
99
100 ddata->dssdev = dssdev;
101 mutex_init(&ddata->lock);
102
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200103 if (dssdev->data) {
104 struct tfp410_platform_data *pdata = dssdev->data;
105
Tomi Valkeinen2da35192011-12-22 10:37:33 +0200106 ddata->pd_gpio = pdata->power_down_gpio;
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200107 i2c_bus_num = pdata->i2c_bus_num;
108 } else {
Tomi Valkeinen2da35192011-12-22 10:37:33 +0200109 ddata->pd_gpio = -1;
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200110 i2c_bus_num = -1;
111 }
Tomi Valkeinen2da35192011-12-22 10:37:33 +0200112
113 if (gpio_is_valid(ddata->pd_gpio)) {
114 r = gpio_request_one(ddata->pd_gpio, GPIOF_OUT_INIT_LOW,
115 "tfp410 pd");
116 if (r) {
117 dev_err(&dssdev->dev, "Failed to request PD GPIO %d\n",
118 ddata->pd_gpio);
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200119 return r;
Tomi Valkeinen2da35192011-12-22 10:37:33 +0200120 }
121 }
122
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200123 if (i2c_bus_num != -1) {
124 struct i2c_adapter *adapter;
125
126 adapter = i2c_get_adapter(i2c_bus_num);
127 if (!adapter) {
128 dev_err(&dssdev->dev, "Failed to get I2C adapter, bus %d\n",
129 i2c_bus_num);
130 r = -EINVAL;
131 goto err_i2c;
132 }
133
134 ddata->i2c_adapter = adapter;
135 }
136
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300137 dev_set_drvdata(&dssdev->dev, ddata);
138
139 return 0;
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200140err_i2c:
141 if (gpio_is_valid(ddata->pd_gpio))
142 gpio_free(ddata->pd_gpio);
143 return r;
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300144}
145
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200146static void __exit tfp410_remove(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300147{
148 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
149
150 mutex_lock(&ddata->lock);
151
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200152 if (ddata->i2c_adapter)
153 i2c_put_adapter(ddata->i2c_adapter);
154
Tomi Valkeinen2da35192011-12-22 10:37:33 +0200155 if (gpio_is_valid(ddata->pd_gpio))
156 gpio_free(ddata->pd_gpio);
157
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300158 dev_set_drvdata(&dssdev->dev, NULL);
159
160 mutex_unlock(&ddata->lock);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300161}
162
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200163static int tfp410_enable(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300164{
165 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
166 int r;
167
168 mutex_lock(&ddata->lock);
169
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200170 r = tfp410_power_on(dssdev);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300171 if (r == 0)
172 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
173
174 mutex_unlock(&ddata->lock);
175
176 return r;
177}
178
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200179static void tfp410_disable(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300180{
181 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
182
183 mutex_lock(&ddata->lock);
184
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200185 tfp410_power_off(dssdev);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300186
187 dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
188
189 mutex_unlock(&ddata->lock);
190}
191
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200192static int tfp410_suspend(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300193{
194 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
195
196 mutex_lock(&ddata->lock);
197
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200198 tfp410_power_off(dssdev);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300199
200 dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED;
201
202 mutex_unlock(&ddata->lock);
203
204 return 0;
205}
206
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200207static int tfp410_resume(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300208{
209 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
210 int r;
211
212 mutex_lock(&ddata->lock);
213
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200214 r = tfp410_power_on(dssdev);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300215 if (r == 0)
216 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
217
218 mutex_unlock(&ddata->lock);
219
220 return r;
221}
222
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200223static void tfp410_set_timings(struct omap_dss_device *dssdev,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300224 struct omap_video_timings *timings)
225{
226 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
227
228 mutex_lock(&ddata->lock);
229 dpi_set_timings(dssdev, timings);
230 mutex_unlock(&ddata->lock);
231}
232
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200233static void tfp410_get_timings(struct omap_dss_device *dssdev,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300234 struct omap_video_timings *timings)
235{
236 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
237
238 mutex_lock(&ddata->lock);
239 *timings = dssdev->panel.timings;
240 mutex_unlock(&ddata->lock);
241}
242
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200243static int tfp410_check_timings(struct omap_dss_device *dssdev,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300244 struct omap_video_timings *timings)
245{
246 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
247 int r;
248
249 mutex_lock(&ddata->lock);
250 r = dpi_check_timings(dssdev, timings);
251 mutex_unlock(&ddata->lock);
252
253 return r;
254}
255
256
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200257static int tfp410_ddc_read(struct i2c_adapter *adapter,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300258 unsigned char *buf, u16 count, u8 offset)
259{
260 int r, retries;
261
262 for (retries = 3; retries > 0; retries--) {
263 struct i2c_msg msgs[] = {
264 {
265 .addr = DDC_ADDR,
266 .flags = 0,
267 .len = 1,
268 .buf = &offset,
269 }, {
270 .addr = DDC_ADDR,
271 .flags = I2C_M_RD,
272 .len = count,
273 .buf = buf,
274 }
275 };
276
277 r = i2c_transfer(adapter, msgs, 2);
278 if (r == 2)
279 return 0;
280
281 if (r != -EAGAIN)
282 break;
283 }
284
285 return r < 0 ? r : -EIO;
286}
287
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200288static int tfp410_read_edid(struct omap_dss_device *dssdev,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300289 u8 *edid, int len)
290{
291 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300292 int r, l, bytes_read;
293
294 mutex_lock(&ddata->lock);
295
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200296 if (!ddata->i2c_adapter) {
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300297 r = -ENODEV;
298 goto err;
299 }
300
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300301 l = min(EDID_LENGTH, len);
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200302 r = tfp410_ddc_read(ddata->i2c_adapter, edid, l, 0);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300303 if (r)
304 goto err;
305
306 bytes_read = l;
307
308 /* if there are extensions, read second block */
309 if (len > EDID_LENGTH && edid[0x7e] > 0) {
310 l = min(EDID_LENGTH, len - EDID_LENGTH);
311
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200312 r = tfp410_ddc_read(ddata->i2c_adapter, edid + EDID_LENGTH,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300313 l, EDID_LENGTH);
314 if (r)
315 goto err;
316
317 bytes_read += l;
318 }
319
320 mutex_unlock(&ddata->lock);
321
322 return bytes_read;
323
324err:
325 mutex_unlock(&ddata->lock);
326 return r;
327}
328
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200329static bool tfp410_detect(struct omap_dss_device *dssdev)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300330{
331 struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300332 unsigned char out;
333 int r;
334
335 mutex_lock(&ddata->lock);
336
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200337 if (!ddata->i2c_adapter)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300338 goto out;
339
Tomi Valkeinen958f2712012-02-17 12:19:48 +0200340 r = tfp410_ddc_read(ddata->i2c_adapter, &out, 1, 0);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300341
342 mutex_unlock(&ddata->lock);
343
344 return r == 0;
345
346out:
347 mutex_unlock(&ddata->lock);
348 return true;
349}
350
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200351static struct omap_dss_driver tfp410_driver = {
352 .probe = tfp410_probe,
353 .remove = __exit_p(tfp410_remove),
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300354
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200355 .enable = tfp410_enable,
356 .disable = tfp410_disable,
357 .suspend = tfp410_suspend,
358 .resume = tfp410_resume,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300359
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200360 .set_timings = tfp410_set_timings,
361 .get_timings = tfp410_get_timings,
362 .check_timings = tfp410_check_timings,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300363
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200364 .read_edid = tfp410_read_edid,
365 .detect = tfp410_detect,
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300366
367 .driver = {
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200368 .name = "tfp410",
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300369 .owner = THIS_MODULE,
370 },
371};
372
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200373static int __init tfp410_init(void)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300374{
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200375 return omap_dss_register_driver(&tfp410_driver);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300376}
377
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200378static void __exit tfp410_exit(void)
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300379{
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200380 omap_dss_unregister_driver(&tfp410_driver);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300381}
382
Tomi Valkeinen2e6f2ee2012-03-05 14:29:28 +0200383module_init(tfp410_init);
384module_exit(tfp410_exit);
Tomi Valkeinenba2eac92011-09-01 09:17:41 +0300385MODULE_LICENSE("GPL");