Commit | Line | Data |
---|---|---|
f133a9d7 TV |
1 | /* |
2 | * Taal DSI command mode panel | |
3 | * | |
4 | * Copyright (C) 2009 Nokia Corporation | |
5 | * Author: Tomi Valkeinen <tomi.valkeinen@nokia.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 | /*#define DEBUG*/ | |
21 | ||
22 | #include <linux/module.h> | |
23 | #include <linux/delay.h> | |
24 | #include <linux/err.h> | |
25 | #include <linux/jiffies.h> | |
26 | #include <linux/sched.h> | |
27 | #include <linux/backlight.h> | |
28 | #include <linux/fb.h> | |
29 | #include <linux/interrupt.h> | |
30 | #include <linux/gpio.h> | |
f133a9d7 | 31 | #include <linux/workqueue.h> |
5a0e3ad6 | 32 | #include <linux/slab.h> |
a3201a0e | 33 | #include <linux/mutex.h> |
f133a9d7 | 34 | |
a0b38cc4 | 35 | #include <video/omapdss.h> |
4e9f99d7 | 36 | #include <video/omap-panel-nokia-dsi.h> |
7a7c48f9 | 37 | #include <video/mipi_display.h> |
f133a9d7 TV |
38 | |
39 | /* DSI Virtual channel. Hardcoded for now. */ | |
40 | #define TCH 0 | |
41 | ||
42 | #define DCS_READ_NUM_ERRORS 0x05 | |
f133a9d7 TV |
43 | #define DCS_BRIGHTNESS 0x51 |
44 | #define DCS_CTRL_DISPLAY 0x53 | |
45 | #define DCS_WRITE_CABC 0x55 | |
46 | #define DCS_READ_CABC 0x56 | |
47 | #define DCS_GET_ID1 0xda | |
48 | #define DCS_GET_ID2 0xdb | |
49 | #define DCS_GET_ID3 0xdc | |
50 | ||
7ae2fb11 JN |
51 | static irqreturn_t taal_te_isr(int irq, void *data); |
52 | static void taal_te_timeout_work_callback(struct work_struct *work); | |
21df20fc TV |
53 | static int _taal_enable_te(struct omap_dss_device *dssdev, bool enable); |
54 | ||
1abf7814 TV |
55 | static int taal_panel_reset(struct omap_dss_device *dssdev); |
56 | ||
f133a9d7 | 57 | struct taal_data { |
a3201a0e TV |
58 | struct mutex lock; |
59 | ||
f133a9d7 TV |
60 | struct backlight_device *bldev; |
61 | ||
62 | unsigned long hw_guard_end; /* next value of jiffies when we can | |
63 | * issue the next sleep in/out command | |
64 | */ | |
65 | unsigned long hw_guard_wait; /* max guard time in jiffies */ | |
66 | ||
67 | struct omap_dss_device *dssdev; | |
68 | ||
f075a594 TV |
69 | /* panel HW configuration from DT or platform data */ |
70 | int reset_gpio; | |
71 | int ext_te_gpio; | |
72 | ||
73 | bool use_dsi_backlight; | |
74 | ||
75 | struct omap_dsi_pin_config pin_config; | |
76 | ||
77 | /* runtime variables */ | |
f133a9d7 | 78 | bool enabled; |
f133a9d7 TV |
79 | |
80 | bool te_enabled; | |
7ae2fb11 JN |
81 | |
82 | atomic_t do_update; | |
bc6d4b1d AT |
83 | int channel; |
84 | ||
7ae2fb11 | 85 | struct delayed_work te_timeout_work; |
f133a9d7 | 86 | |
f133a9d7 TV |
87 | bool cabc_broken; |
88 | unsigned cabc_mode; | |
89 | ||
90 | bool intro_printed; | |
91 | ||
883b9ac9 TV |
92 | struct workqueue_struct *workqueue; |
93 | ||
f133a9d7 | 94 | struct delayed_work esd_work; |
33a410be | 95 | unsigned esd_interval; |
e7f6c3f2 | 96 | |
1abf7814 TV |
97 | bool ulps_enabled; |
98 | unsigned ulps_timeout; | |
99 | struct delayed_work ulps_work; | |
f133a9d7 TV |
100 | }; |
101 | ||
102 | static void taal_esd_work(struct work_struct *work); | |
1abf7814 | 103 | static void taal_ulps_work(struct work_struct *work); |
f133a9d7 TV |
104 | |
105 | static void hw_guard_start(struct taal_data *td, int guard_msec) | |
106 | { | |
107 | td->hw_guard_wait = msecs_to_jiffies(guard_msec); | |
108 | td->hw_guard_end = jiffies + td->hw_guard_wait; | |
109 | } | |
110 | ||
111 | static void hw_guard_wait(struct taal_data *td) | |
112 | { | |
113 | unsigned long wait = td->hw_guard_end - jiffies; | |
114 | ||
115 | if ((long)wait > 0 && wait <= td->hw_guard_wait) { | |
116 | set_current_state(TASK_UNINTERRUPTIBLE); | |
117 | schedule_timeout(wait); | |
118 | } | |
119 | } | |
120 | ||
bc6d4b1d | 121 | static int taal_dcs_read_1(struct taal_data *td, u8 dcs_cmd, u8 *data) |
f133a9d7 TV |
122 | { |
123 | int r; | |
124 | u8 buf[1]; | |
125 | ||
1ffefe75 | 126 | r = dsi_vc_dcs_read(td->dssdev, td->channel, dcs_cmd, buf, 1); |
f133a9d7 TV |
127 | |
128 | if (r < 0) | |
129 | return r; | |
130 | ||
131 | *data = buf[0]; | |
132 | ||
133 | return 0; | |
134 | } | |
135 | ||
bc6d4b1d | 136 | static int taal_dcs_write_0(struct taal_data *td, u8 dcs_cmd) |
f133a9d7 | 137 | { |
1ffefe75 | 138 | return dsi_vc_dcs_write(td->dssdev, td->channel, &dcs_cmd, 1); |
f133a9d7 TV |
139 | } |
140 | ||
bc6d4b1d | 141 | static int taal_dcs_write_1(struct taal_data *td, u8 dcs_cmd, u8 param) |
f133a9d7 TV |
142 | { |
143 | u8 buf[2]; | |
144 | buf[0] = dcs_cmd; | |
145 | buf[1] = param; | |
1ffefe75 | 146 | return dsi_vc_dcs_write(td->dssdev, td->channel, buf, 2); |
f133a9d7 TV |
147 | } |
148 | ||
149 | static int taal_sleep_in(struct taal_data *td) | |
150 | ||
151 | { | |
152 | u8 cmd; | |
153 | int r; | |
154 | ||
155 | hw_guard_wait(td); | |
156 | ||
7a7c48f9 | 157 | cmd = MIPI_DCS_ENTER_SLEEP_MODE; |
1ffefe75 | 158 | r = dsi_vc_dcs_write_nosync(td->dssdev, td->channel, &cmd, 1); |
f133a9d7 TV |
159 | if (r) |
160 | return r; | |
161 | ||
162 | hw_guard_start(td, 120); | |
163 | ||
e087cc21 | 164 | msleep(5); |
f133a9d7 TV |
165 | |
166 | return 0; | |
167 | } | |
168 | ||
169 | static int taal_sleep_out(struct taal_data *td) | |
170 | { | |
171 | int r; | |
172 | ||
173 | hw_guard_wait(td); | |
174 | ||
7a7c48f9 | 175 | r = taal_dcs_write_0(td, MIPI_DCS_EXIT_SLEEP_MODE); |
f133a9d7 TV |
176 | if (r) |
177 | return r; | |
178 | ||
179 | hw_guard_start(td, 120); | |
180 | ||
e087cc21 | 181 | msleep(5); |
f133a9d7 TV |
182 | |
183 | return 0; | |
184 | } | |
185 | ||
bc6d4b1d | 186 | static int taal_get_id(struct taal_data *td, u8 *id1, u8 *id2, u8 *id3) |
f133a9d7 TV |
187 | { |
188 | int r; | |
189 | ||
bc6d4b1d | 190 | r = taal_dcs_read_1(td, DCS_GET_ID1, id1); |
f133a9d7 TV |
191 | if (r) |
192 | return r; | |
bc6d4b1d | 193 | r = taal_dcs_read_1(td, DCS_GET_ID2, id2); |
f133a9d7 TV |
194 | if (r) |
195 | return r; | |
bc6d4b1d | 196 | r = taal_dcs_read_1(td, DCS_GET_ID3, id3); |
f133a9d7 TV |
197 | if (r) |
198 | return r; | |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
bc6d4b1d AT |
203 | static int taal_set_update_window(struct taal_data *td, |
204 | u16 x, u16 y, u16 w, u16 h) | |
f133a9d7 TV |
205 | { |
206 | int r; | |
207 | u16 x1 = x; | |
208 | u16 x2 = x + w - 1; | |
209 | u16 y1 = y; | |
210 | u16 y2 = y + h - 1; | |
211 | ||
212 | u8 buf[5]; | |
7a7c48f9 | 213 | buf[0] = MIPI_DCS_SET_COLUMN_ADDRESS; |
f133a9d7 TV |
214 | buf[1] = (x1 >> 8) & 0xff; |
215 | buf[2] = (x1 >> 0) & 0xff; | |
216 | buf[3] = (x2 >> 8) & 0xff; | |
217 | buf[4] = (x2 >> 0) & 0xff; | |
218 | ||
1ffefe75 | 219 | r = dsi_vc_dcs_write_nosync(td->dssdev, td->channel, buf, sizeof(buf)); |
f133a9d7 TV |
220 | if (r) |
221 | return r; | |
222 | ||
7a7c48f9 | 223 | buf[0] = MIPI_DCS_SET_PAGE_ADDRESS; |
f133a9d7 TV |
224 | buf[1] = (y1 >> 8) & 0xff; |
225 | buf[2] = (y1 >> 0) & 0xff; | |
226 | buf[3] = (y2 >> 8) & 0xff; | |
227 | buf[4] = (y2 >> 0) & 0xff; | |
228 | ||
1ffefe75 | 229 | r = dsi_vc_dcs_write_nosync(td->dssdev, td->channel, buf, sizeof(buf)); |
f133a9d7 TV |
230 | if (r) |
231 | return r; | |
232 | ||
1ffefe75 | 233 | dsi_vc_send_bta_sync(td->dssdev, td->channel); |
f133a9d7 TV |
234 | |
235 | return r; | |
236 | } | |
237 | ||
1663d2f7 TV |
238 | static void taal_queue_esd_work(struct omap_dss_device *dssdev) |
239 | { | |
240 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
241 | ||
242 | if (td->esd_interval > 0) | |
883b9ac9 | 243 | queue_delayed_work(td->workqueue, &td->esd_work, |
1663d2f7 TV |
244 | msecs_to_jiffies(td->esd_interval)); |
245 | } | |
246 | ||
247 | static void taal_cancel_esd_work(struct omap_dss_device *dssdev) | |
248 | { | |
249 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
250 | ||
251 | cancel_delayed_work(&td->esd_work); | |
252 | } | |
253 | ||
1abf7814 TV |
254 | static void taal_queue_ulps_work(struct omap_dss_device *dssdev) |
255 | { | |
256 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
257 | ||
258 | if (td->ulps_timeout > 0) | |
259 | queue_delayed_work(td->workqueue, &td->ulps_work, | |
260 | msecs_to_jiffies(td->ulps_timeout)); | |
261 | } | |
262 | ||
263 | static void taal_cancel_ulps_work(struct omap_dss_device *dssdev) | |
264 | { | |
265 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
266 | ||
267 | cancel_delayed_work(&td->ulps_work); | |
268 | } | |
269 | ||
270 | static int taal_enter_ulps(struct omap_dss_device *dssdev) | |
271 | { | |
272 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
1abf7814 TV |
273 | int r; |
274 | ||
275 | if (td->ulps_enabled) | |
276 | return 0; | |
277 | ||
278 | taal_cancel_ulps_work(dssdev); | |
279 | ||
280 | r = _taal_enable_te(dssdev, false); | |
281 | if (r) | |
282 | goto err; | |
283 | ||
f075a594 TV |
284 | if (gpio_is_valid(td->ext_te_gpio)) |
285 | disable_irq(gpio_to_irq(td->ext_te_gpio)); | |
1abf7814 TV |
286 | |
287 | omapdss_dsi_display_disable(dssdev, false, true); | |
288 | ||
289 | td->ulps_enabled = true; | |
290 | ||
291 | return 0; | |
292 | ||
293 | err: | |
294 | dev_err(&dssdev->dev, "enter ULPS failed"); | |
295 | taal_panel_reset(dssdev); | |
296 | ||
297 | td->ulps_enabled = false; | |
298 | ||
299 | taal_queue_ulps_work(dssdev); | |
300 | ||
301 | return r; | |
302 | } | |
303 | ||
304 | static int taal_exit_ulps(struct omap_dss_device *dssdev) | |
305 | { | |
306 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
1abf7814 TV |
307 | int r; |
308 | ||
309 | if (!td->ulps_enabled) | |
310 | return 0; | |
311 | ||
312 | r = omapdss_dsi_display_enable(dssdev); | |
e8945677 TV |
313 | if (r) { |
314 | dev_err(&dssdev->dev, "failed to enable DSI\n"); | |
315 | goto err1; | |
316 | } | |
1abf7814 | 317 | |
1ffefe75 | 318 | omapdss_dsi_vc_enable_hs(dssdev, td->channel, true); |
1abf7814 TV |
319 | |
320 | r = _taal_enable_te(dssdev, true); | |
e8945677 TV |
321 | if (r) { |
322 | dev_err(&dssdev->dev, "failed to re-enable TE"); | |
323 | goto err2; | |
324 | } | |
1abf7814 | 325 | |
f075a594 TV |
326 | if (gpio_is_valid(td->ext_te_gpio)) |
327 | enable_irq(gpio_to_irq(td->ext_te_gpio)); | |
1abf7814 TV |
328 | |
329 | taal_queue_ulps_work(dssdev); | |
330 | ||
331 | td->ulps_enabled = false; | |
332 | ||
333 | return 0; | |
334 | ||
e8945677 TV |
335 | err2: |
336 | dev_err(&dssdev->dev, "failed to exit ULPS"); | |
1abf7814 | 337 | |
e8945677 TV |
338 | r = taal_panel_reset(dssdev); |
339 | if (!r) { | |
f075a594 TV |
340 | if (gpio_is_valid(td->ext_te_gpio)) |
341 | enable_irq(gpio_to_irq(td->ext_te_gpio)); | |
e8945677 TV |
342 | td->ulps_enabled = false; |
343 | } | |
344 | err1: | |
1abf7814 TV |
345 | taal_queue_ulps_work(dssdev); |
346 | ||
347 | return r; | |
348 | } | |
349 | ||
350 | static int taal_wake_up(struct omap_dss_device *dssdev) | |
351 | { | |
352 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
353 | ||
354 | if (td->ulps_enabled) | |
355 | return taal_exit_ulps(dssdev); | |
356 | ||
357 | taal_cancel_ulps_work(dssdev); | |
358 | taal_queue_ulps_work(dssdev); | |
359 | return 0; | |
360 | } | |
361 | ||
f133a9d7 TV |
362 | static int taal_bl_update_status(struct backlight_device *dev) |
363 | { | |
364 | struct omap_dss_device *dssdev = dev_get_drvdata(&dev->dev); | |
365 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
366 | int r; | |
367 | int level; | |
368 | ||
369 | if (dev->props.fb_blank == FB_BLANK_UNBLANK && | |
370 | dev->props.power == FB_BLANK_UNBLANK) | |
371 | level = dev->props.brightness; | |
372 | else | |
373 | level = 0; | |
374 | ||
375 | dev_dbg(&dssdev->dev, "update brightness to %d\n", level); | |
376 | ||
1cbc8703 TV |
377 | mutex_lock(&td->lock); |
378 | ||
bb36dbfd TV |
379 | if (td->enabled) { |
380 | dsi_bus_lock(dssdev); | |
1abf7814 | 381 | |
bb36dbfd TV |
382 | r = taal_wake_up(dssdev); |
383 | if (!r) | |
384 | r = taal_dcs_write_1(td, DCS_BRIGHTNESS, level); | |
1abf7814 | 385 | |
bb36dbfd | 386 | dsi_bus_unlock(dssdev); |
f133a9d7 | 387 | } else { |
bb36dbfd | 388 | r = 0; |
f133a9d7 TV |
389 | } |
390 | ||
1cbc8703 TV |
391 | mutex_unlock(&td->lock); |
392 | ||
393 | return r; | |
f133a9d7 TV |
394 | } |
395 | ||
396 | static int taal_bl_get_intensity(struct backlight_device *dev) | |
397 | { | |
398 | if (dev->props.fb_blank == FB_BLANK_UNBLANK && | |
399 | dev->props.power == FB_BLANK_UNBLANK) | |
400 | return dev->props.brightness; | |
401 | ||
402 | return 0; | |
403 | } | |
404 | ||
acc2472e | 405 | static const struct backlight_ops taal_bl_ops = { |
f133a9d7 TV |
406 | .get_brightness = taal_bl_get_intensity, |
407 | .update_status = taal_bl_update_status, | |
408 | }; | |
409 | ||
f133a9d7 TV |
410 | static void taal_get_resolution(struct omap_dss_device *dssdev, |
411 | u16 *xres, u16 *yres) | |
412 | { | |
d9f4e467 TV |
413 | *xres = dssdev->panel.timings.x_res; |
414 | *yres = dssdev->panel.timings.y_res; | |
f133a9d7 TV |
415 | } |
416 | ||
f133a9d7 TV |
417 | static ssize_t taal_num_errors_show(struct device *dev, |
418 | struct device_attribute *attr, char *buf) | |
419 | { | |
420 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
421 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
d1700f92 | 422 | u8 errors = 0; |
f133a9d7 TV |
423 | int r; |
424 | ||
6b316715 JN |
425 | mutex_lock(&td->lock); |
426 | ||
f133a9d7 | 427 | if (td->enabled) { |
1ffefe75 | 428 | dsi_bus_lock(dssdev); |
1abf7814 TV |
429 | |
430 | r = taal_wake_up(dssdev); | |
431 | if (!r) | |
432 | r = taal_dcs_read_1(td, DCS_READ_NUM_ERRORS, &errors); | |
433 | ||
1ffefe75 | 434 | dsi_bus_unlock(dssdev); |
f133a9d7 TV |
435 | } else { |
436 | r = -ENODEV; | |
437 | } | |
438 | ||
6b316715 JN |
439 | mutex_unlock(&td->lock); |
440 | ||
f133a9d7 TV |
441 | if (r) |
442 | return r; | |
443 | ||
444 | return snprintf(buf, PAGE_SIZE, "%d\n", errors); | |
445 | } | |
446 | ||
447 | static ssize_t taal_hw_revision_show(struct device *dev, | |
448 | struct device_attribute *attr, char *buf) | |
449 | { | |
450 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
451 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
452 | u8 id1, id2, id3; | |
453 | int r; | |
454 | ||
6b316715 JN |
455 | mutex_lock(&td->lock); |
456 | ||
f133a9d7 | 457 | if (td->enabled) { |
1ffefe75 | 458 | dsi_bus_lock(dssdev); |
1abf7814 TV |
459 | |
460 | r = taal_wake_up(dssdev); | |
461 | if (!r) | |
462 | r = taal_get_id(td, &id1, &id2, &id3); | |
463 | ||
1ffefe75 | 464 | dsi_bus_unlock(dssdev); |
f133a9d7 TV |
465 | } else { |
466 | r = -ENODEV; | |
467 | } | |
468 | ||
6b316715 JN |
469 | mutex_unlock(&td->lock); |
470 | ||
f133a9d7 TV |
471 | if (r) |
472 | return r; | |
473 | ||
474 | return snprintf(buf, PAGE_SIZE, "%02x.%02x.%02x\n", id1, id2, id3); | |
475 | } | |
476 | ||
477 | static const char *cabc_modes[] = { | |
478 | "off", /* used also always when CABC is not supported */ | |
479 | "ui", | |
480 | "still-image", | |
481 | "moving-image", | |
482 | }; | |
483 | ||
484 | static ssize_t show_cabc_mode(struct device *dev, | |
485 | struct device_attribute *attr, | |
486 | char *buf) | |
487 | { | |
488 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
489 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
490 | const char *mode_str; | |
491 | int mode; | |
492 | int len; | |
493 | ||
494 | mode = td->cabc_mode; | |
495 | ||
496 | mode_str = "unknown"; | |
497 | if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes)) | |
498 | mode_str = cabc_modes[mode]; | |
499 | len = snprintf(buf, PAGE_SIZE, "%s\n", mode_str); | |
500 | ||
501 | return len < PAGE_SIZE - 1 ? len : PAGE_SIZE - 1; | |
502 | } | |
503 | ||
504 | static ssize_t store_cabc_mode(struct device *dev, | |
505 | struct device_attribute *attr, | |
506 | const char *buf, size_t count) | |
507 | { | |
508 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
509 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
510 | int i; | |
1abf7814 | 511 | int r; |
f133a9d7 TV |
512 | |
513 | for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) { | |
514 | if (sysfs_streq(cabc_modes[i], buf)) | |
515 | break; | |
516 | } | |
517 | ||
518 | if (i == ARRAY_SIZE(cabc_modes)) | |
519 | return -EINVAL; | |
520 | ||
6b316715 JN |
521 | mutex_lock(&td->lock); |
522 | ||
f133a9d7 | 523 | if (td->enabled) { |
1ffefe75 | 524 | dsi_bus_lock(dssdev); |
1abf7814 TV |
525 | |
526 | if (!td->cabc_broken) { | |
527 | r = taal_wake_up(dssdev); | |
528 | if (r) | |
529 | goto err; | |
530 | ||
531 | r = taal_dcs_write_1(td, DCS_WRITE_CABC, i); | |
532 | if (r) | |
533 | goto err; | |
534 | } | |
535 | ||
1ffefe75 | 536 | dsi_bus_unlock(dssdev); |
f133a9d7 TV |
537 | } |
538 | ||
539 | td->cabc_mode = i; | |
540 | ||
6b316715 JN |
541 | mutex_unlock(&td->lock); |
542 | ||
f133a9d7 | 543 | return count; |
1abf7814 | 544 | err: |
1ffefe75 | 545 | dsi_bus_unlock(dssdev); |
1abf7814 TV |
546 | mutex_unlock(&td->lock); |
547 | return r; | |
f133a9d7 TV |
548 | } |
549 | ||
550 | static ssize_t show_cabc_available_modes(struct device *dev, | |
551 | struct device_attribute *attr, | |
552 | char *buf) | |
553 | { | |
554 | int len; | |
555 | int i; | |
556 | ||
557 | for (i = 0, len = 0; | |
558 | len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++) | |
559 | len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s", | |
560 | i ? " " : "", cabc_modes[i], | |
561 | i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : ""); | |
562 | ||
563 | return len < PAGE_SIZE ? len : PAGE_SIZE - 1; | |
564 | } | |
565 | ||
1f8fa452 TV |
566 | static ssize_t taal_store_esd_interval(struct device *dev, |
567 | struct device_attribute *attr, | |
568 | const char *buf, size_t count) | |
569 | { | |
570 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
571 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
572 | ||
573 | unsigned long t; | |
574 | int r; | |
575 | ||
576 | r = strict_strtoul(buf, 10, &t); | |
577 | if (r) | |
578 | return r; | |
579 | ||
580 | mutex_lock(&td->lock); | |
581 | taal_cancel_esd_work(dssdev); | |
582 | td->esd_interval = t; | |
583 | if (td->enabled) | |
584 | taal_queue_esd_work(dssdev); | |
585 | mutex_unlock(&td->lock); | |
586 | ||
587 | return count; | |
588 | } | |
589 | ||
590 | static ssize_t taal_show_esd_interval(struct device *dev, | |
591 | struct device_attribute *attr, | |
592 | char *buf) | |
593 | { | |
594 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
595 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
596 | unsigned t; | |
597 | ||
598 | mutex_lock(&td->lock); | |
599 | t = td->esd_interval; | |
600 | mutex_unlock(&td->lock); | |
601 | ||
602 | return snprintf(buf, PAGE_SIZE, "%u\n", t); | |
603 | } | |
604 | ||
1abf7814 TV |
605 | static ssize_t taal_store_ulps(struct device *dev, |
606 | struct device_attribute *attr, | |
607 | const char *buf, size_t count) | |
608 | { | |
609 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
610 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
611 | unsigned long t; | |
612 | int r; | |
613 | ||
614 | r = strict_strtoul(buf, 10, &t); | |
615 | if (r) | |
616 | return r; | |
617 | ||
618 | mutex_lock(&td->lock); | |
619 | ||
620 | if (td->enabled) { | |
1ffefe75 | 621 | dsi_bus_lock(dssdev); |
1abf7814 TV |
622 | |
623 | if (t) | |
624 | r = taal_enter_ulps(dssdev); | |
625 | else | |
626 | r = taal_wake_up(dssdev); | |
627 | ||
1ffefe75 | 628 | dsi_bus_unlock(dssdev); |
1abf7814 TV |
629 | } |
630 | ||
631 | mutex_unlock(&td->lock); | |
632 | ||
633 | if (r) | |
634 | return r; | |
635 | ||
636 | return count; | |
637 | } | |
638 | ||
639 | static ssize_t taal_show_ulps(struct device *dev, | |
640 | struct device_attribute *attr, | |
641 | char *buf) | |
642 | { | |
643 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
644 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
645 | unsigned t; | |
646 | ||
647 | mutex_lock(&td->lock); | |
648 | t = td->ulps_enabled; | |
649 | mutex_unlock(&td->lock); | |
650 | ||
651 | return snprintf(buf, PAGE_SIZE, "%u\n", t); | |
652 | } | |
653 | ||
654 | static ssize_t taal_store_ulps_timeout(struct device *dev, | |
655 | struct device_attribute *attr, | |
656 | const char *buf, size_t count) | |
657 | { | |
658 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
659 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
660 | unsigned long t; | |
661 | int r; | |
662 | ||
663 | r = strict_strtoul(buf, 10, &t); | |
664 | if (r) | |
665 | return r; | |
666 | ||
667 | mutex_lock(&td->lock); | |
668 | td->ulps_timeout = t; | |
669 | ||
670 | if (td->enabled) { | |
671 | /* taal_wake_up will restart the timer */ | |
1ffefe75 | 672 | dsi_bus_lock(dssdev); |
1abf7814 | 673 | r = taal_wake_up(dssdev); |
1ffefe75 | 674 | dsi_bus_unlock(dssdev); |
1abf7814 TV |
675 | } |
676 | ||
677 | mutex_unlock(&td->lock); | |
678 | ||
679 | if (r) | |
680 | return r; | |
681 | ||
682 | return count; | |
683 | } | |
684 | ||
685 | static ssize_t taal_show_ulps_timeout(struct device *dev, | |
686 | struct device_attribute *attr, | |
687 | char *buf) | |
688 | { | |
689 | struct omap_dss_device *dssdev = to_dss_device(dev); | |
690 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
691 | unsigned t; | |
692 | ||
693 | mutex_lock(&td->lock); | |
694 | t = td->ulps_timeout; | |
695 | mutex_unlock(&td->lock); | |
696 | ||
697 | return snprintf(buf, PAGE_SIZE, "%u\n", t); | |
698 | } | |
699 | ||
f133a9d7 TV |
700 | static DEVICE_ATTR(num_dsi_errors, S_IRUGO, taal_num_errors_show, NULL); |
701 | static DEVICE_ATTR(hw_revision, S_IRUGO, taal_hw_revision_show, NULL); | |
702 | static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR, | |
703 | show_cabc_mode, store_cabc_mode); | |
704 | static DEVICE_ATTR(cabc_available_modes, S_IRUGO, | |
705 | show_cabc_available_modes, NULL); | |
1f8fa452 TV |
706 | static DEVICE_ATTR(esd_interval, S_IRUGO | S_IWUSR, |
707 | taal_show_esd_interval, taal_store_esd_interval); | |
1abf7814 TV |
708 | static DEVICE_ATTR(ulps, S_IRUGO | S_IWUSR, |
709 | taal_show_ulps, taal_store_ulps); | |
710 | static DEVICE_ATTR(ulps_timeout, S_IRUGO | S_IWUSR, | |
711 | taal_show_ulps_timeout, taal_store_ulps_timeout); | |
f133a9d7 TV |
712 | |
713 | static struct attribute *taal_attrs[] = { | |
714 | &dev_attr_num_dsi_errors.attr, | |
715 | &dev_attr_hw_revision.attr, | |
716 | &dev_attr_cabc_mode.attr, | |
717 | &dev_attr_cabc_available_modes.attr, | |
1f8fa452 | 718 | &dev_attr_esd_interval.attr, |
1abf7814 TV |
719 | &dev_attr_ulps.attr, |
720 | &dev_attr_ulps_timeout.attr, | |
f133a9d7 TV |
721 | NULL, |
722 | }; | |
723 | ||
724 | static struct attribute_group taal_attr_group = { | |
725 | .attrs = taal_attrs, | |
726 | }; | |
727 | ||
006db7b4 JN |
728 | static void taal_hw_reset(struct omap_dss_device *dssdev) |
729 | { | |
e7f6c3f2 | 730 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
8d3573c8 | 731 | |
f075a594 | 732 | if (!gpio_is_valid(td->reset_gpio)) |
006db7b4 JN |
733 | return; |
734 | ||
f075a594 | 735 | gpio_set_value(td->reset_gpio, 1); |
e087cc21 | 736 | udelay(10); |
006db7b4 | 737 | /* reset the panel */ |
f075a594 | 738 | gpio_set_value(td->reset_gpio, 0); |
e7f6c3f2 | 739 | /* assert reset */ |
e087cc21 | 740 | udelay(10); |
f075a594 | 741 | gpio_set_value(td->reset_gpio, 1); |
e7f6c3f2 | 742 | /* wait after releasing reset */ |
e087cc21 | 743 | msleep(5); |
006db7b4 JN |
744 | } |
745 | ||
f075a594 TV |
746 | static void taal_probe_pdata(struct taal_data *td, |
747 | const struct nokia_dsi_panel_data *pdata) | |
748 | { | |
749 | td->reset_gpio = pdata->reset_gpio; | |
750 | ||
751 | if (pdata->use_ext_te) | |
752 | td->ext_te_gpio = pdata->ext_te_gpio; | |
753 | else | |
754 | td->ext_te_gpio = -1; | |
755 | ||
756 | td->esd_interval = pdata->esd_interval; | |
757 | td->ulps_timeout = pdata->ulps_timeout; | |
758 | ||
759 | td->use_dsi_backlight = pdata->use_dsi_backlight; | |
760 | ||
761 | td->pin_config = pdata->pin_config; | |
762 | } | |
763 | ||
f133a9d7 TV |
764 | static int taal_probe(struct omap_dss_device *dssdev) |
765 | { | |
a19a6ee6 | 766 | struct backlight_properties props; |
f133a9d7 | 767 | struct taal_data *td; |
bb36dbfd | 768 | struct backlight_device *bldev = NULL; |
e087cc21 | 769 | int r; |
f133a9d7 TV |
770 | |
771 | dev_dbg(&dssdev->dev, "probe\n"); | |
772 | ||
f075a594 TV |
773 | td = devm_kzalloc(&dssdev->dev, sizeof(*td), GFP_KERNEL); |
774 | if (!td) | |
775 | return -ENOMEM; | |
776 | ||
777 | dev_set_drvdata(&dssdev->dev, td); | |
778 | td->dssdev = dssdev; | |
779 | ||
780 | if (dssdev->data) { | |
781 | const struct nokia_dsi_panel_data *pdata = dssdev->data; | |
782 | ||
783 | taal_probe_pdata(td, pdata); | |
f075a594 TV |
784 | } else { |
785 | return -ENODEV; | |
8d3573c8 TV |
786 | } |
787 | ||
e087cc21 TV |
788 | dssdev->panel.timings.x_res = 864; |
789 | dssdev->panel.timings.y_res = 480; | |
a3b3cc2b | 790 | dssdev->panel.dsi_pix_fmt = OMAP_DSS_DSI_FMT_RGB888; |
ab585254 TV |
791 | dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE | |
792 | OMAP_DSS_DISPLAY_CAP_TEAR_ELIM; | |
f133a9d7 | 793 | |
a3201a0e TV |
794 | mutex_init(&td->lock); |
795 | ||
7ae2fb11 JN |
796 | atomic_set(&td->do_update, 0); |
797 | ||
f075a594 TV |
798 | if (gpio_is_valid(td->reset_gpio)) { |
799 | r = devm_gpio_request_one(&dssdev->dev, td->reset_gpio, | |
5e56ad44 | 800 | GPIOF_OUT_INIT_LOW, "taal rst"); |
3acc797c TV |
801 | if (r) { |
802 | dev_err(&dssdev->dev, "failed to request reset gpio\n"); | |
5e56ad44 | 803 | return r; |
3acc797c | 804 | } |
f133a9d7 | 805 | } |
f133a9d7 | 806 | |
f075a594 TV |
807 | if (gpio_is_valid(td->ext_te_gpio)) { |
808 | r = devm_gpio_request_one(&dssdev->dev, td->ext_te_gpio, | |
809 | GPIOF_IN, "taal irq"); | |
5e56ad44 TV |
810 | if (r) { |
811 | dev_err(&dssdev->dev, "GPIO request failed\n"); | |
812 | return r; | |
813 | } | |
814 | ||
f075a594 | 815 | r = devm_request_irq(&dssdev->dev, gpio_to_irq(td->ext_te_gpio), |
5e56ad44 TV |
816 | taal_te_isr, |
817 | IRQF_TRIGGER_RISING, | |
818 | "taal vsync", dssdev); | |
f133a9d7 | 819 | |
3acc797c | 820 | if (r) { |
5e56ad44 TV |
821 | dev_err(&dssdev->dev, "IRQ request failed\n"); |
822 | return r; | |
3acc797c | 823 | } |
5e56ad44 | 824 | |
5f76945a | 825 | INIT_DEFERRABLE_WORK(&td->te_timeout_work, |
5e56ad44 TV |
826 | taal_te_timeout_work_callback); |
827 | ||
828 | dev_dbg(&dssdev->dev, "Using GPIO TE\n"); | |
3acc797c TV |
829 | } |
830 | ||
5e56ad44 TV |
831 | td->workqueue = create_singlethread_workqueue("taal_esd"); |
832 | if (td->workqueue == NULL) { | |
833 | dev_err(&dssdev->dev, "can't create ESD workqueue\n"); | |
834 | return -ENOMEM; | |
835 | } | |
5f76945a | 836 | INIT_DEFERRABLE_WORK(&td->esd_work, taal_esd_work); |
5e56ad44 TV |
837 | INIT_DELAYED_WORK(&td->ulps_work, taal_ulps_work); |
838 | ||
006db7b4 JN |
839 | taal_hw_reset(dssdev); |
840 | ||
f075a594 | 841 | if (td->use_dsi_backlight) { |
bb36dbfd | 842 | memset(&props, 0, sizeof(struct backlight_properties)); |
a19a6ee6 | 843 | props.max_brightness = 255; |
f133a9d7 | 844 | |
bb36dbfd TV |
845 | props.type = BACKLIGHT_RAW; |
846 | bldev = backlight_device_register(dev_name(&dssdev->dev), | |
847 | &dssdev->dev, dssdev, &taal_bl_ops, &props); | |
848 | if (IS_ERR(bldev)) { | |
849 | r = PTR_ERR(bldev); | |
850 | goto err_bl; | |
851 | } | |
f133a9d7 | 852 | |
bb36dbfd | 853 | td->bldev = bldev; |
f133a9d7 | 854 | |
bb36dbfd TV |
855 | bldev->props.fb_blank = FB_BLANK_UNBLANK; |
856 | bldev->props.power = FB_BLANK_UNBLANK; | |
f133a9d7 | 857 | bldev->props.brightness = 255; |
f133a9d7 | 858 | |
bb36dbfd TV |
859 | taal_bl_update_status(bldev); |
860 | } | |
f133a9d7 | 861 | |
bc6d4b1d AT |
862 | r = omap_dsi_request_vc(dssdev, &td->channel); |
863 | if (r) { | |
864 | dev_err(&dssdev->dev, "failed to get virtual channel\n"); | |
865 | goto err_req_vc; | |
866 | } | |
867 | ||
868 | r = omap_dsi_set_vc_id(dssdev, td->channel, TCH); | |
869 | if (r) { | |
870 | dev_err(&dssdev->dev, "failed to set VC_ID\n"); | |
871 | goto err_vc_id; | |
872 | } | |
873 | ||
f133a9d7 TV |
874 | r = sysfs_create_group(&dssdev->dev.kobj, &taal_attr_group); |
875 | if (r) { | |
876 | dev_err(&dssdev->dev, "failed to create sysfs files\n"); | |
bc6d4b1d | 877 | goto err_vc_id; |
f133a9d7 TV |
878 | } |
879 | ||
880 | return 0; | |
bc6d4b1d AT |
881 | |
882 | err_vc_id: | |
883 | omap_dsi_release_vc(dssdev, td->channel); | |
884 | err_req_vc: | |
bb36dbfd TV |
885 | if (bldev != NULL) |
886 | backlight_device_unregister(bldev); | |
d2b65787 | 887 | err_bl: |
883b9ac9 | 888 | destroy_workqueue(td->workqueue); |
f133a9d7 TV |
889 | return r; |
890 | } | |
891 | ||
14e4d784 | 892 | static void __exit taal_remove(struct omap_dss_device *dssdev) |
f133a9d7 TV |
893 | { |
894 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
895 | struct backlight_device *bldev; | |
896 | ||
897 | dev_dbg(&dssdev->dev, "remove\n"); | |
898 | ||
899 | sysfs_remove_group(&dssdev->dev.kobj, &taal_attr_group); | |
bc6d4b1d | 900 | omap_dsi_release_vc(dssdev, td->channel); |
f133a9d7 | 901 | |
f133a9d7 | 902 | bldev = td->bldev; |
bb36dbfd TV |
903 | if (bldev != NULL) { |
904 | bldev->props.power = FB_BLANK_POWERDOWN; | |
905 | taal_bl_update_status(bldev); | |
906 | backlight_device_unregister(bldev); | |
907 | } | |
f133a9d7 | 908 | |
1abf7814 | 909 | taal_cancel_ulps_work(dssdev); |
1663d2f7 | 910 | taal_cancel_esd_work(dssdev); |
883b9ac9 | 911 | destroy_workqueue(td->workqueue); |
f133a9d7 | 912 | |
006db7b4 JN |
913 | /* reset, to be sure that the panel is in a valid state */ |
914 | taal_hw_reset(dssdev); | |
f133a9d7 TV |
915 | } |
916 | ||
37ac60e4 | 917 | static int taal_power_on(struct omap_dss_device *dssdev) |
f133a9d7 TV |
918 | { |
919 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
920 | u8 id1, id2, id3; | |
921 | int r; | |
777f05cc TV |
922 | struct omap_dss_dsi_config dsi_config = { |
923 | .mode = OMAP_DSS_DSI_CMD_MODE, | |
924 | .pixel_format = OMAP_DSS_DSI_FMT_RGB888, | |
925 | .timings = &dssdev->panel.timings, | |
926 | .hs_clk = 216000000, | |
927 | .lp_clk = 10000000, | |
928 | }; | |
f133a9d7 | 929 | |
f075a594 | 930 | r = omapdss_dsi_configure_pins(dssdev, &td->pin_config); |
e4a9e94c TV |
931 | if (r) { |
932 | dev_err(&dssdev->dev, "failed to configure DSI pins\n"); | |
933 | goto err0; | |
934 | }; | |
935 | ||
777f05cc | 936 | r = omapdss_dsi_set_config(dssdev, &dsi_config); |
ee144e64 | 937 | if (r) { |
777f05cc | 938 | dev_err(&dssdev->dev, "failed to configure DSI\n"); |
ee144e64 TV |
939 | goto err0; |
940 | } | |
941 | ||
37ac60e4 TV |
942 | r = omapdss_dsi_display_enable(dssdev); |
943 | if (r) { | |
944 | dev_err(&dssdev->dev, "failed to enable DSI\n"); | |
945 | goto err0; | |
946 | } | |
947 | ||
006db7b4 JN |
948 | taal_hw_reset(dssdev); |
949 | ||
1ffefe75 | 950 | omapdss_dsi_vc_enable_hs(dssdev, td->channel, false); |
37ac60e4 | 951 | |
f133a9d7 TV |
952 | r = taal_sleep_out(td); |
953 | if (r) | |
954 | goto err; | |
955 | ||
bc6d4b1d | 956 | r = taal_get_id(td, &id1, &id2, &id3); |
f133a9d7 TV |
957 | if (r) |
958 | goto err; | |
959 | ||
1e8943db | 960 | /* on early Taal revisions CABC is broken */ |
e087cc21 | 961 | if (id2 == 0x00 || id2 == 0xff || id2 == 0x81) |
f133a9d7 TV |
962 | td->cabc_broken = true; |
963 | ||
bc6d4b1d | 964 | r = taal_dcs_write_1(td, DCS_BRIGHTNESS, 0xff); |
f2a8b75c JN |
965 | if (r) |
966 | goto err; | |
f133a9d7 | 967 | |
bc6d4b1d | 968 | r = taal_dcs_write_1(td, DCS_CTRL_DISPLAY, |
f2a8b75c JN |
969 | (1<<2) | (1<<5)); /* BL | BCTRL */ |
970 | if (r) | |
971 | goto err; | |
f133a9d7 | 972 | |
7a7c48f9 AT |
973 | r = taal_dcs_write_1(td, MIPI_DCS_SET_PIXEL_FORMAT, |
974 | MIPI_DCS_PIXEL_FMT_24BIT); | |
f2a8b75c JN |
975 | if (r) |
976 | goto err; | |
f133a9d7 | 977 | |
f2a8b75c | 978 | if (!td->cabc_broken) { |
bc6d4b1d | 979 | r = taal_dcs_write_1(td, DCS_WRITE_CABC, td->cabc_mode); |
f2a8b75c JN |
980 | if (r) |
981 | goto err; | |
982 | } | |
983 | ||
7a7c48f9 | 984 | r = taal_dcs_write_0(td, MIPI_DCS_SET_DISPLAY_ON); |
f2a8b75c JN |
985 | if (r) |
986 | goto err; | |
f133a9d7 | 987 | |
21df20fc TV |
988 | r = _taal_enable_te(dssdev, td->te_enabled); |
989 | if (r) | |
990 | goto err; | |
991 | ||
9a147a65 TV |
992 | r = dsi_enable_video_output(dssdev, td->channel); |
993 | if (r) | |
994 | goto err; | |
995 | ||
f133a9d7 TV |
996 | td->enabled = 1; |
997 | ||
998 | if (!td->intro_printed) { | |
e087cc21 TV |
999 | dev_info(&dssdev->dev, "panel revision %02x.%02x.%02x\n", |
1000 | id1, id2, id3); | |
f133a9d7 TV |
1001 | if (td->cabc_broken) |
1002 | dev_info(&dssdev->dev, | |
1003 | "old Taal version, CABC disabled\n"); | |
1004 | td->intro_printed = true; | |
1005 | } | |
1006 | ||
1ffefe75 | 1007 | omapdss_dsi_vc_enable_hs(dssdev, td->channel, true); |
37ac60e4 | 1008 | |
f133a9d7 TV |
1009 | return 0; |
1010 | err: | |
006db7b4 JN |
1011 | dev_err(&dssdev->dev, "error while enabling panel, issuing HW reset\n"); |
1012 | ||
1013 | taal_hw_reset(dssdev); | |
1014 | ||
22d6d676 | 1015 | omapdss_dsi_display_disable(dssdev, true, false); |
37ac60e4 | 1016 | err0: |
f133a9d7 TV |
1017 | return r; |
1018 | } | |
1019 | ||
37ac60e4 | 1020 | static void taal_power_off(struct omap_dss_device *dssdev) |
f133a9d7 TV |
1021 | { |
1022 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
006db7b4 | 1023 | int r; |
f133a9d7 | 1024 | |
9a147a65 TV |
1025 | dsi_disable_video_output(dssdev, td->channel); |
1026 | ||
7a7c48f9 | 1027 | r = taal_dcs_write_0(td, MIPI_DCS_SET_DISPLAY_OFF); |
15ffa1da | 1028 | if (!r) |
006db7b4 | 1029 | r = taal_sleep_in(td); |
f133a9d7 | 1030 | |
006db7b4 JN |
1031 | if (r) { |
1032 | dev_err(&dssdev->dev, | |
1033 | "error disabling panel, issuing HW reset\n"); | |
1034 | taal_hw_reset(dssdev); | |
1035 | } | |
f133a9d7 | 1036 | |
22d6d676 | 1037 | omapdss_dsi_display_disable(dssdev, true, false); |
37ac60e4 | 1038 | |
f133a9d7 | 1039 | td->enabled = 0; |
37ac60e4 TV |
1040 | } |
1041 | ||
bb5476c7 TV |
1042 | static int taal_panel_reset(struct omap_dss_device *dssdev) |
1043 | { | |
1044 | dev_err(&dssdev->dev, "performing LCD reset\n"); | |
1045 | ||
1046 | taal_power_off(dssdev); | |
1047 | taal_hw_reset(dssdev); | |
1048 | return taal_power_on(dssdev); | |
1049 | } | |
1050 | ||
37ac60e4 TV |
1051 | static int taal_enable(struct omap_dss_device *dssdev) |
1052 | { | |
a3201a0e | 1053 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
37ac60e4 | 1054 | int r; |
a3201a0e | 1055 | |
37ac60e4 TV |
1056 | dev_dbg(&dssdev->dev, "enable\n"); |
1057 | ||
a3201a0e TV |
1058 | mutex_lock(&td->lock); |
1059 | ||
1060 | if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) { | |
1061 | r = -EINVAL; | |
1062 | goto err; | |
1063 | } | |
37ac60e4 | 1064 | |
1ffefe75 | 1065 | dsi_bus_lock(dssdev); |
2c2fc151 | 1066 | |
37ac60e4 | 1067 | r = taal_power_on(dssdev); |
2c2fc151 | 1068 | |
1ffefe75 | 1069 | dsi_bus_unlock(dssdev); |
2c2fc151 | 1070 | |
37ac60e4 | 1071 | if (r) |
a3201a0e | 1072 | goto err; |
37ac60e4 | 1073 | |
1663d2f7 | 1074 | taal_queue_esd_work(dssdev); |
4571a023 | 1075 | |
37ac60e4 TV |
1076 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; |
1077 | ||
a3201a0e TV |
1078 | mutex_unlock(&td->lock); |
1079 | ||
1080 | return 0; | |
1081 | err: | |
1082 | dev_dbg(&dssdev->dev, "enable failed\n"); | |
1083 | mutex_unlock(&td->lock); | |
37ac60e4 TV |
1084 | return r; |
1085 | } | |
1086 | ||
1087 | static void taal_disable(struct omap_dss_device *dssdev) | |
1088 | { | |
a3201a0e TV |
1089 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
1090 | ||
37ac60e4 TV |
1091 | dev_dbg(&dssdev->dev, "disable\n"); |
1092 | ||
a3201a0e TV |
1093 | mutex_lock(&td->lock); |
1094 | ||
1abf7814 | 1095 | taal_cancel_ulps_work(dssdev); |
1663d2f7 | 1096 | taal_cancel_esd_work(dssdev); |
4571a023 | 1097 | |
1ffefe75 | 1098 | dsi_bus_lock(dssdev); |
2c2fc151 | 1099 | |
1abf7814 | 1100 | if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { |
e8945677 TV |
1101 | int r; |
1102 | ||
1103 | r = taal_wake_up(dssdev); | |
1104 | if (!r) | |
1105 | taal_power_off(dssdev); | |
1abf7814 | 1106 | } |
37ac60e4 | 1107 | |
1ffefe75 | 1108 | dsi_bus_unlock(dssdev); |
2c2fc151 | 1109 | |
37ac60e4 | 1110 | dssdev->state = OMAP_DSS_DISPLAY_DISABLED; |
a3201a0e TV |
1111 | |
1112 | mutex_unlock(&td->lock); | |
f133a9d7 TV |
1113 | } |
1114 | ||
18946f62 TV |
1115 | static void taal_framedone_cb(int err, void *data) |
1116 | { | |
1117 | struct omap_dss_device *dssdev = data; | |
1118 | dev_dbg(&dssdev->dev, "framedone, err %d\n", err); | |
1ffefe75 | 1119 | dsi_bus_unlock(dssdev); |
18946f62 TV |
1120 | } |
1121 | ||
7ae2fb11 JN |
1122 | static irqreturn_t taal_te_isr(int irq, void *data) |
1123 | { | |
1124 | struct omap_dss_device *dssdev = data; | |
1125 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
1126 | int old; | |
1127 | int r; | |
1128 | ||
1129 | old = atomic_cmpxchg(&td->do_update, 1, 0); | |
1130 | ||
1131 | if (old) { | |
1132 | cancel_delayed_work(&td->te_timeout_work); | |
1133 | ||
5476e74a TV |
1134 | r = omap_dsi_update(dssdev, td->channel, taal_framedone_cb, |
1135 | dssdev); | |
7ae2fb11 JN |
1136 | if (r) |
1137 | goto err; | |
1138 | } | |
1139 | ||
1140 | return IRQ_HANDLED; | |
1141 | err: | |
1142 | dev_err(&dssdev->dev, "start update failed\n"); | |
1ffefe75 | 1143 | dsi_bus_unlock(dssdev); |
7ae2fb11 JN |
1144 | return IRQ_HANDLED; |
1145 | } | |
1146 | ||
1147 | static void taal_te_timeout_work_callback(struct work_struct *work) | |
1148 | { | |
1149 | struct taal_data *td = container_of(work, struct taal_data, | |
1150 | te_timeout_work.work); | |
1151 | struct omap_dss_device *dssdev = td->dssdev; | |
1152 | ||
1153 | dev_err(&dssdev->dev, "TE not received for 250ms!\n"); | |
1154 | ||
1155 | atomic_set(&td->do_update, 0); | |
1ffefe75 | 1156 | dsi_bus_unlock(dssdev); |
7ae2fb11 JN |
1157 | } |
1158 | ||
18946f62 | 1159 | static int taal_update(struct omap_dss_device *dssdev, |
f133a9d7 TV |
1160 | u16 x, u16 y, u16 w, u16 h) |
1161 | { | |
18946f62 TV |
1162 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
1163 | int r; | |
1164 | ||
1165 | dev_dbg(&dssdev->dev, "update %d, %d, %d x %d\n", x, y, w, h); | |
1166 | ||
a3201a0e | 1167 | mutex_lock(&td->lock); |
1ffefe75 | 1168 | dsi_bus_lock(dssdev); |
18946f62 | 1169 | |
1abf7814 TV |
1170 | r = taal_wake_up(dssdev); |
1171 | if (r) | |
1172 | goto err; | |
1173 | ||
18946f62 TV |
1174 | if (!td->enabled) { |
1175 | r = 0; | |
1176 | goto err; | |
1177 | } | |
1178 | ||
6331709b TV |
1179 | /* XXX no need to send this every frame, but dsi break if not done */ |
1180 | r = taal_set_update_window(td, 0, 0, | |
e087cc21 TV |
1181 | dssdev->panel.timings.x_res, |
1182 | dssdev->panel.timings.y_res); | |
18946f62 TV |
1183 | if (r) |
1184 | goto err; | |
1185 | ||
f075a594 | 1186 | if (td->te_enabled && gpio_is_valid(td->ext_te_gpio)) { |
7ae2fb11 JN |
1187 | schedule_delayed_work(&td->te_timeout_work, |
1188 | msecs_to_jiffies(250)); | |
1189 | atomic_set(&td->do_update, 1); | |
1190 | } else { | |
5476e74a TV |
1191 | r = omap_dsi_update(dssdev, td->channel, taal_framedone_cb, |
1192 | dssdev); | |
7ae2fb11 JN |
1193 | if (r) |
1194 | goto err; | |
1195 | } | |
18946f62 TV |
1196 | |
1197 | /* note: no bus_unlock here. unlock is in framedone_cb */ | |
a3201a0e | 1198 | mutex_unlock(&td->lock); |
18946f62 TV |
1199 | return 0; |
1200 | err: | |
1ffefe75 | 1201 | dsi_bus_unlock(dssdev); |
a3201a0e | 1202 | mutex_unlock(&td->lock); |
18946f62 TV |
1203 | return r; |
1204 | } | |
1205 | ||
1206 | static int taal_sync(struct omap_dss_device *dssdev) | |
1207 | { | |
a3201a0e TV |
1208 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
1209 | ||
18946f62 TV |
1210 | dev_dbg(&dssdev->dev, "sync\n"); |
1211 | ||
a3201a0e | 1212 | mutex_lock(&td->lock); |
1ffefe75 AT |
1213 | dsi_bus_lock(dssdev); |
1214 | dsi_bus_unlock(dssdev); | |
a3201a0e | 1215 | mutex_unlock(&td->lock); |
18946f62 TV |
1216 | |
1217 | dev_dbg(&dssdev->dev, "sync done\n"); | |
1218 | ||
1219 | return 0; | |
f133a9d7 TV |
1220 | } |
1221 | ||
21df20fc | 1222 | static int _taal_enable_te(struct omap_dss_device *dssdev, bool enable) |
f133a9d7 | 1223 | { |
e7f6c3f2 | 1224 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
f133a9d7 TV |
1225 | int r; |
1226 | ||
f133a9d7 | 1227 | if (enable) |
7a7c48f9 | 1228 | r = taal_dcs_write_1(td, MIPI_DCS_SET_TEAR_ON, 0); |
f133a9d7 | 1229 | else |
7a7c48f9 | 1230 | r = taal_dcs_write_0(td, MIPI_DCS_SET_TEAR_OFF); |
f133a9d7 | 1231 | |
f075a594 | 1232 | if (!gpio_is_valid(td->ext_te_gpio)) |
7ae2fb11 | 1233 | omapdss_dsi_enable_te(dssdev, enable); |
225b650d | 1234 | |
e087cc21 TV |
1235 | /* possible panel bug */ |
1236 | msleep(100); | |
225b650d | 1237 | |
21df20fc TV |
1238 | return r; |
1239 | } | |
1240 | ||
1241 | static int taal_enable_te(struct omap_dss_device *dssdev, bool enable) | |
1242 | { | |
a3201a0e | 1243 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
21df20fc TV |
1244 | int r; |
1245 | ||
a3201a0e | 1246 | mutex_lock(&td->lock); |
ffb63c95 TV |
1247 | |
1248 | if (td->te_enabled == enable) | |
1249 | goto end; | |
1250 | ||
1ffefe75 | 1251 | dsi_bus_lock(dssdev); |
21df20fc | 1252 | |
ee52c0ae | 1253 | if (td->enabled) { |
1abf7814 TV |
1254 | r = taal_wake_up(dssdev); |
1255 | if (r) | |
1256 | goto err; | |
1257 | ||
ee52c0ae JN |
1258 | r = _taal_enable_te(dssdev, enable); |
1259 | if (r) | |
1260 | goto err; | |
1261 | } | |
1262 | ||
1263 | td->te_enabled = enable; | |
21df20fc | 1264 | |
1ffefe75 | 1265 | dsi_bus_unlock(dssdev); |
ffb63c95 | 1266 | end: |
a3201a0e | 1267 | mutex_unlock(&td->lock); |
225b650d | 1268 | |
ee52c0ae JN |
1269 | return 0; |
1270 | err: | |
1ffefe75 | 1271 | dsi_bus_unlock(dssdev); |
ee52c0ae JN |
1272 | mutex_unlock(&td->lock); |
1273 | ||
f133a9d7 TV |
1274 | return r; |
1275 | } | |
1276 | ||
225b650d TV |
1277 | static int taal_get_te(struct omap_dss_device *dssdev) |
1278 | { | |
1279 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | |
a3201a0e TV |
1280 | int r; |
1281 | ||
1282 | mutex_lock(&td->lock); | |
1283 | r = td->te_enabled; | |
1284 | mutex_unlock(&td->lock); | |
1285 | ||
1286 | return r; | |
225b650d TV |
1287 | } |
1288 | ||
f133a9d7 TV |
1289 | static int taal_run_test(struct omap_dss_device *dssdev, int test_num) |
1290 | { | |
a3201a0e | 1291 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
f133a9d7 TV |
1292 | u8 id1, id2, id3; |
1293 | int r; | |
1294 | ||
a3201a0e | 1295 | mutex_lock(&td->lock); |
ee52c0ae JN |
1296 | |
1297 | if (!td->enabled) { | |
1298 | r = -ENODEV; | |
1299 | goto err1; | |
1300 | } | |
1301 | ||
1ffefe75 | 1302 | dsi_bus_lock(dssdev); |
1a75ef42 | 1303 | |
1abf7814 TV |
1304 | r = taal_wake_up(dssdev); |
1305 | if (r) | |
1306 | goto err2; | |
1307 | ||
bc6d4b1d | 1308 | r = taal_dcs_read_1(td, DCS_GET_ID1, &id1); |
f133a9d7 | 1309 | if (r) |
ee52c0ae | 1310 | goto err2; |
bc6d4b1d | 1311 | r = taal_dcs_read_1(td, DCS_GET_ID2, &id2); |
f133a9d7 | 1312 | if (r) |
ee52c0ae | 1313 | goto err2; |
bc6d4b1d | 1314 | r = taal_dcs_read_1(td, DCS_GET_ID3, &id3); |
f133a9d7 | 1315 | if (r) |
ee52c0ae | 1316 | goto err2; |
f133a9d7 | 1317 | |
1ffefe75 | 1318 | dsi_bus_unlock(dssdev); |
a3201a0e | 1319 | mutex_unlock(&td->lock); |
f133a9d7 | 1320 | return 0; |
ee52c0ae | 1321 | err2: |
1ffefe75 | 1322 | dsi_bus_unlock(dssdev); |
ee52c0ae | 1323 | err1: |
a3201a0e | 1324 | mutex_unlock(&td->lock); |
1a75ef42 | 1325 | return r; |
f133a9d7 TV |
1326 | } |
1327 | ||
1328 | static int taal_memory_read(struct omap_dss_device *dssdev, | |
1329 | void *buf, size_t size, | |
1330 | u16 x, u16 y, u16 w, u16 h) | |
1331 | { | |
1332 | int r; | |
1333 | int first = 1; | |
1334 | int plen; | |
1335 | unsigned buf_used = 0; | |
c75d9464 TV |
1336 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); |
1337 | ||
f133a9d7 TV |
1338 | if (size < w * h * 3) |
1339 | return -ENOMEM; | |
1340 | ||
a3201a0e TV |
1341 | mutex_lock(&td->lock); |
1342 | ||
1343 | if (!td->enabled) { | |
1344 | r = -ENODEV; | |
1345 | goto err1; | |
1346 | } | |
1347 | ||
f133a9d7 TV |
1348 | size = min(w * h * 3, |
1349 | dssdev->panel.timings.x_res * | |
1350 | dssdev->panel.timings.y_res * 3); | |
1351 | ||
1ffefe75 | 1352 | dsi_bus_lock(dssdev); |
c75d9464 | 1353 | |
1abf7814 TV |
1354 | r = taal_wake_up(dssdev); |
1355 | if (r) | |
1356 | goto err2; | |
1357 | ||
f133a9d7 TV |
1358 | /* plen 1 or 2 goes into short packet. until checksum error is fixed, |
1359 | * use short packets. plen 32 works, but bigger packets seem to cause | |
1360 | * an error. */ | |
1361 | if (size % 2) | |
1362 | plen = 1; | |
1363 | else | |
1364 | plen = 2; | |
1365 | ||
bc6d4b1d | 1366 | taal_set_update_window(td, x, y, w, h); |
f133a9d7 | 1367 | |
1ffefe75 | 1368 | r = dsi_vc_set_max_rx_packet_size(dssdev, td->channel, plen); |
f133a9d7 | 1369 | if (r) |
a3201a0e | 1370 | goto err2; |
f133a9d7 TV |
1371 | |
1372 | while (buf_used < size) { | |
1373 | u8 dcs_cmd = first ? 0x2e : 0x3e; | |
1374 | first = 0; | |
1375 | ||
1ffefe75 | 1376 | r = dsi_vc_dcs_read(dssdev, td->channel, dcs_cmd, |
f133a9d7 TV |
1377 | buf + buf_used, size - buf_used); |
1378 | ||
1379 | if (r < 0) { | |
1380 | dev_err(&dssdev->dev, "read error\n"); | |
a3201a0e | 1381 | goto err3; |
f133a9d7 TV |
1382 | } |
1383 | ||
1384 | buf_used += r; | |
1385 | ||
1386 | if (r < plen) { | |
1387 | dev_err(&dssdev->dev, "short read\n"); | |
1388 | break; | |
1389 | } | |
1390 | ||
1391 | if (signal_pending(current)) { | |
1392 | dev_err(&dssdev->dev, "signal pending, " | |
1393 | "aborting memory read\n"); | |
1394 | r = -ERESTARTSYS; | |
a3201a0e | 1395 | goto err3; |
f133a9d7 TV |
1396 | } |
1397 | } | |
1398 | ||
1399 | r = buf_used; | |
1400 | ||
a3201a0e | 1401 | err3: |
1ffefe75 | 1402 | dsi_vc_set_max_rx_packet_size(dssdev, td->channel, 1); |
a3201a0e | 1403 | err2: |
1ffefe75 | 1404 | dsi_bus_unlock(dssdev); |
a3201a0e TV |
1405 | err1: |
1406 | mutex_unlock(&td->lock); | |
f133a9d7 TV |
1407 | return r; |
1408 | } | |
1409 | ||
1abf7814 TV |
1410 | static void taal_ulps_work(struct work_struct *work) |
1411 | { | |
1412 | struct taal_data *td = container_of(work, struct taal_data, | |
1413 | ulps_work.work); | |
1414 | struct omap_dss_device *dssdev = td->dssdev; | |
1415 | ||
1416 | mutex_lock(&td->lock); | |
1417 | ||
1418 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || !td->enabled) { | |
1419 | mutex_unlock(&td->lock); | |
1420 | return; | |
1421 | } | |
1422 | ||
1ffefe75 | 1423 | dsi_bus_lock(dssdev); |
1abf7814 TV |
1424 | |
1425 | taal_enter_ulps(dssdev); | |
1426 | ||
1ffefe75 | 1427 | dsi_bus_unlock(dssdev); |
1abf7814 TV |
1428 | mutex_unlock(&td->lock); |
1429 | } | |
1430 | ||
f133a9d7 TV |
1431 | static void taal_esd_work(struct work_struct *work) |
1432 | { | |
1433 | struct taal_data *td = container_of(work, struct taal_data, | |
1434 | esd_work.work); | |
1435 | struct omap_dss_device *dssdev = td->dssdev; | |
1436 | u8 state1, state2; | |
1437 | int r; | |
1438 | ||
a3201a0e TV |
1439 | mutex_lock(&td->lock); |
1440 | ||
1441 | if (!td->enabled) { | |
1442 | mutex_unlock(&td->lock); | |
f133a9d7 | 1443 | return; |
a3201a0e | 1444 | } |
f133a9d7 | 1445 | |
1ffefe75 | 1446 | dsi_bus_lock(dssdev); |
f133a9d7 | 1447 | |
1abf7814 TV |
1448 | r = taal_wake_up(dssdev); |
1449 | if (r) { | |
1450 | dev_err(&dssdev->dev, "failed to exit ULPS\n"); | |
1451 | goto err; | |
1452 | } | |
1453 | ||
7a7c48f9 | 1454 | r = taal_dcs_read_1(td, MIPI_DCS_GET_DIAGNOSTIC_RESULT, &state1); |
f133a9d7 TV |
1455 | if (r) { |
1456 | dev_err(&dssdev->dev, "failed to read Taal status\n"); | |
1457 | goto err; | |
1458 | } | |
1459 | ||
1460 | /* Run self diagnostics */ | |
1461 | r = taal_sleep_out(td); | |
1462 | if (r) { | |
1463 | dev_err(&dssdev->dev, "failed to run Taal self-diagnostics\n"); | |
1464 | goto err; | |
1465 | } | |
1466 | ||
7a7c48f9 | 1467 | r = taal_dcs_read_1(td, MIPI_DCS_GET_DIAGNOSTIC_RESULT, &state2); |
f133a9d7 TV |
1468 | if (r) { |
1469 | dev_err(&dssdev->dev, "failed to read Taal status\n"); | |
1470 | goto err; | |
1471 | } | |
1472 | ||
1473 | /* Each sleep out command will trigger a self diagnostic and flip | |
1474 | * Bit6 if the test passes. | |
1475 | */ | |
1476 | if (!((state1 ^ state2) & (1 << 6))) { | |
1477 | dev_err(&dssdev->dev, "LCD self diagnostics failed\n"); | |
1478 | goto err; | |
1479 | } | |
1480 | /* Self-diagnostics result is also shown on TE GPIO line. We need | |
1481 | * to re-enable TE after self diagnostics */ | |
f075a594 | 1482 | if (td->te_enabled && gpio_is_valid(td->ext_te_gpio)) { |
7a7c48f9 | 1483 | r = taal_dcs_write_1(td, MIPI_DCS_SET_TEAR_ON, 0); |
1189b7ff TV |
1484 | if (r) |
1485 | goto err; | |
1486 | } | |
f133a9d7 | 1487 | |
1ffefe75 | 1488 | dsi_bus_unlock(dssdev); |
f133a9d7 | 1489 | |
1663d2f7 | 1490 | taal_queue_esd_work(dssdev); |
f133a9d7 | 1491 | |
a3201a0e | 1492 | mutex_unlock(&td->lock); |
f133a9d7 TV |
1493 | return; |
1494 | err: | |
1495 | dev_err(&dssdev->dev, "performing LCD reset\n"); | |
1496 | ||
bb5476c7 | 1497 | taal_panel_reset(dssdev); |
f133a9d7 | 1498 | |
1ffefe75 | 1499 | dsi_bus_unlock(dssdev); |
f133a9d7 | 1500 | |
1663d2f7 | 1501 | taal_queue_esd_work(dssdev); |
a3201a0e TV |
1502 | |
1503 | mutex_unlock(&td->lock); | |
f133a9d7 TV |
1504 | } |
1505 | ||
1506 | static struct omap_dss_driver taal_driver = { | |
1507 | .probe = taal_probe, | |
14e4d784 | 1508 | .remove = __exit_p(taal_remove), |
f133a9d7 TV |
1509 | |
1510 | .enable = taal_enable, | |
1511 | .disable = taal_disable, | |
f133a9d7 | 1512 | |
18946f62 TV |
1513 | .update = taal_update, |
1514 | .sync = taal_sync, | |
1515 | ||
96adcece | 1516 | .get_resolution = taal_get_resolution, |
a2699504 TV |
1517 | .get_recommended_bpp = omapdss_default_get_recommended_bpp, |
1518 | ||
f133a9d7 | 1519 | .enable_te = taal_enable_te, |
225b650d | 1520 | .get_te = taal_get_te, |
225b650d | 1521 | |
f133a9d7 TV |
1522 | .run_test = taal_run_test, |
1523 | .memory_read = taal_memory_read, | |
1524 | ||
1525 | .driver = { | |
1526 | .name = "taal", | |
1527 | .owner = THIS_MODULE, | |
1528 | }, | |
1529 | }; | |
1530 | ||
1531 | static int __init taal_init(void) | |
1532 | { | |
1533 | omap_dss_register_driver(&taal_driver); | |
1534 | ||
1535 | return 0; | |
1536 | } | |
1537 | ||
1538 | static void __exit taal_exit(void) | |
1539 | { | |
1540 | omap_dss_unregister_driver(&taal_driver); | |
1541 | } | |
1542 | ||
1543 | module_init(taal_init); | |
1544 | module_exit(taal_exit); | |
1545 | ||
1546 | MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@nokia.com>"); | |
1547 | MODULE_DESCRIPTION("Taal Driver"); | |
1548 | MODULE_LICENSE("GPL"); |