| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 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> | 
|  | 31 | #include <linux/completion.h> | 
|  | 32 | #include <linux/workqueue.h> | 
|  | 33 |  | 
|  | 34 | #include <plat/display.h> | 
|  | 35 |  | 
|  | 36 | /* DSI Virtual channel. Hardcoded for now. */ | 
|  | 37 | #define TCH 0 | 
|  | 38 |  | 
|  | 39 | #define DCS_READ_NUM_ERRORS	0x05 | 
|  | 40 | #define DCS_READ_POWER_MODE	0x0a | 
|  | 41 | #define DCS_READ_MADCTL		0x0b | 
|  | 42 | #define DCS_READ_PIXEL_FORMAT	0x0c | 
|  | 43 | #define DCS_RDDSDR		0x0f | 
|  | 44 | #define DCS_SLEEP_IN		0x10 | 
|  | 45 | #define DCS_SLEEP_OUT		0x11 | 
|  | 46 | #define DCS_DISPLAY_OFF		0x28 | 
|  | 47 | #define DCS_DISPLAY_ON		0x29 | 
|  | 48 | #define DCS_COLUMN_ADDR		0x2a | 
|  | 49 | #define DCS_PAGE_ADDR		0x2b | 
|  | 50 | #define DCS_MEMORY_WRITE	0x2c | 
|  | 51 | #define DCS_TEAR_OFF		0x34 | 
|  | 52 | #define DCS_TEAR_ON		0x35 | 
|  | 53 | #define DCS_MEM_ACC_CTRL	0x36 | 
|  | 54 | #define DCS_PIXEL_FORMAT	0x3a | 
|  | 55 | #define DCS_BRIGHTNESS		0x51 | 
|  | 56 | #define DCS_CTRL_DISPLAY	0x53 | 
|  | 57 | #define DCS_WRITE_CABC		0x55 | 
|  | 58 | #define DCS_READ_CABC		0x56 | 
|  | 59 | #define DCS_GET_ID1		0xda | 
|  | 60 | #define DCS_GET_ID2		0xdb | 
|  | 61 | #define DCS_GET_ID3		0xdc | 
|  | 62 |  | 
|  | 63 | /* #define TAAL_USE_ESD_CHECK */ | 
|  | 64 | #define TAAL_ESD_CHECK_PERIOD	msecs_to_jiffies(5000) | 
|  | 65 |  | 
| Tomi Valkeinen | 21df20f | 2010-03-02 12:13:55 +0200 | [diff] [blame] | 66 | static int _taal_enable_te(struct omap_dss_device *dssdev, bool enable); | 
|  | 67 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 68 | struct taal_data { | 
|  | 69 | struct backlight_device *bldev; | 
|  | 70 |  | 
|  | 71 | unsigned long	hw_guard_end;	/* next value of jiffies when we can | 
|  | 72 | * issue the next sleep in/out command | 
|  | 73 | */ | 
|  | 74 | unsigned long	hw_guard_wait;	/* max guard time in jiffies */ | 
|  | 75 |  | 
|  | 76 | struct omap_dss_device *dssdev; | 
|  | 77 |  | 
|  | 78 | bool enabled; | 
|  | 79 | u8 rotate; | 
|  | 80 | bool mirror; | 
|  | 81 |  | 
|  | 82 | bool te_enabled; | 
|  | 83 | bool use_ext_te; | 
|  | 84 | struct completion te_completion; | 
|  | 85 |  | 
|  | 86 | bool use_dsi_bl; | 
|  | 87 |  | 
|  | 88 | bool cabc_broken; | 
|  | 89 | unsigned cabc_mode; | 
|  | 90 |  | 
|  | 91 | bool intro_printed; | 
|  | 92 |  | 
|  | 93 | struct workqueue_struct *esd_wq; | 
|  | 94 | struct delayed_work esd_work; | 
|  | 95 | }; | 
|  | 96 |  | 
|  | 97 | static void taal_esd_work(struct work_struct *work); | 
|  | 98 |  | 
|  | 99 | static void hw_guard_start(struct taal_data *td, int guard_msec) | 
|  | 100 | { | 
|  | 101 | td->hw_guard_wait = msecs_to_jiffies(guard_msec); | 
|  | 102 | td->hw_guard_end = jiffies + td->hw_guard_wait; | 
|  | 103 | } | 
|  | 104 |  | 
|  | 105 | static void hw_guard_wait(struct taal_data *td) | 
|  | 106 | { | 
|  | 107 | unsigned long wait = td->hw_guard_end - jiffies; | 
|  | 108 |  | 
|  | 109 | if ((long)wait > 0 && wait <= td->hw_guard_wait) { | 
|  | 110 | set_current_state(TASK_UNINTERRUPTIBLE); | 
|  | 111 | schedule_timeout(wait); | 
|  | 112 | } | 
|  | 113 | } | 
|  | 114 |  | 
|  | 115 | static int taal_dcs_read_1(u8 dcs_cmd, u8 *data) | 
|  | 116 | { | 
|  | 117 | int r; | 
|  | 118 | u8 buf[1]; | 
|  | 119 |  | 
|  | 120 | r = dsi_vc_dcs_read(TCH, dcs_cmd, buf, 1); | 
|  | 121 |  | 
|  | 122 | if (r < 0) | 
|  | 123 | return r; | 
|  | 124 |  | 
|  | 125 | *data = buf[0]; | 
|  | 126 |  | 
|  | 127 | return 0; | 
|  | 128 | } | 
|  | 129 |  | 
|  | 130 | static int taal_dcs_write_0(u8 dcs_cmd) | 
|  | 131 | { | 
|  | 132 | return dsi_vc_dcs_write(TCH, &dcs_cmd, 1); | 
|  | 133 | } | 
|  | 134 |  | 
|  | 135 | static int taal_dcs_write_1(u8 dcs_cmd, u8 param) | 
|  | 136 | { | 
|  | 137 | u8 buf[2]; | 
|  | 138 | buf[0] = dcs_cmd; | 
|  | 139 | buf[1] = param; | 
|  | 140 | return dsi_vc_dcs_write(TCH, buf, 2); | 
|  | 141 | } | 
|  | 142 |  | 
|  | 143 | static int taal_sleep_in(struct taal_data *td) | 
|  | 144 |  | 
|  | 145 | { | 
|  | 146 | u8 cmd; | 
|  | 147 | int r; | 
|  | 148 |  | 
|  | 149 | hw_guard_wait(td); | 
|  | 150 |  | 
|  | 151 | cmd = DCS_SLEEP_IN; | 
|  | 152 | r = dsi_vc_dcs_write_nosync(TCH, &cmd, 1); | 
|  | 153 | if (r) | 
|  | 154 | return r; | 
|  | 155 |  | 
|  | 156 | hw_guard_start(td, 120); | 
|  | 157 |  | 
|  | 158 | msleep(5); | 
|  | 159 |  | 
|  | 160 | return 0; | 
|  | 161 | } | 
|  | 162 |  | 
|  | 163 | static int taal_sleep_out(struct taal_data *td) | 
|  | 164 | { | 
|  | 165 | int r; | 
|  | 166 |  | 
|  | 167 | hw_guard_wait(td); | 
|  | 168 |  | 
|  | 169 | r = taal_dcs_write_0(DCS_SLEEP_OUT); | 
|  | 170 | if (r) | 
|  | 171 | return r; | 
|  | 172 |  | 
|  | 173 | hw_guard_start(td, 120); | 
|  | 174 |  | 
|  | 175 | msleep(5); | 
|  | 176 |  | 
|  | 177 | return 0; | 
|  | 178 | } | 
|  | 179 |  | 
|  | 180 | static int taal_get_id(u8 *id1, u8 *id2, u8 *id3) | 
|  | 181 | { | 
|  | 182 | int r; | 
|  | 183 |  | 
|  | 184 | r = taal_dcs_read_1(DCS_GET_ID1, id1); | 
|  | 185 | if (r) | 
|  | 186 | return r; | 
|  | 187 | r = taal_dcs_read_1(DCS_GET_ID2, id2); | 
|  | 188 | if (r) | 
|  | 189 | return r; | 
|  | 190 | r = taal_dcs_read_1(DCS_GET_ID3, id3); | 
|  | 191 | if (r) | 
|  | 192 | return r; | 
|  | 193 |  | 
|  | 194 | return 0; | 
|  | 195 | } | 
|  | 196 |  | 
|  | 197 | static int taal_set_addr_mode(u8 rotate, bool mirror) | 
|  | 198 | { | 
|  | 199 | int r; | 
|  | 200 | u8 mode; | 
|  | 201 | int b5, b6, b7; | 
|  | 202 |  | 
|  | 203 | r = taal_dcs_read_1(DCS_READ_MADCTL, &mode); | 
|  | 204 | if (r) | 
|  | 205 | return r; | 
|  | 206 |  | 
|  | 207 | switch (rotate) { | 
|  | 208 | default: | 
|  | 209 | case 0: | 
|  | 210 | b7 = 0; | 
|  | 211 | b6 = 0; | 
|  | 212 | b5 = 0; | 
|  | 213 | break; | 
|  | 214 | case 1: | 
|  | 215 | b7 = 0; | 
|  | 216 | b6 = 1; | 
|  | 217 | b5 = 1; | 
|  | 218 | break; | 
|  | 219 | case 2: | 
|  | 220 | b7 = 1; | 
|  | 221 | b6 = 1; | 
|  | 222 | b5 = 0; | 
|  | 223 | break; | 
|  | 224 | case 3: | 
|  | 225 | b7 = 1; | 
|  | 226 | b6 = 0; | 
|  | 227 | b5 = 1; | 
|  | 228 | break; | 
|  | 229 | } | 
|  | 230 |  | 
|  | 231 | if (mirror) | 
|  | 232 | b6 = !b6; | 
|  | 233 |  | 
|  | 234 | mode &= ~((1<<7) | (1<<6) | (1<<5)); | 
|  | 235 | mode |= (b7 << 7) | (b6 << 6) | (b5 << 5); | 
|  | 236 |  | 
|  | 237 | return taal_dcs_write_1(DCS_MEM_ACC_CTRL, mode); | 
|  | 238 | } | 
|  | 239 |  | 
|  | 240 | static int taal_set_update_window(u16 x, u16 y, u16 w, u16 h) | 
|  | 241 | { | 
|  | 242 | int r; | 
|  | 243 | u16 x1 = x; | 
|  | 244 | u16 x2 = x + w - 1; | 
|  | 245 | u16 y1 = y; | 
|  | 246 | u16 y2 = y + h - 1; | 
|  | 247 |  | 
|  | 248 | u8 buf[5]; | 
|  | 249 | buf[0] = DCS_COLUMN_ADDR; | 
|  | 250 | buf[1] = (x1 >> 8) & 0xff; | 
|  | 251 | buf[2] = (x1 >> 0) & 0xff; | 
|  | 252 | buf[3] = (x2 >> 8) & 0xff; | 
|  | 253 | buf[4] = (x2 >> 0) & 0xff; | 
|  | 254 |  | 
|  | 255 | r = dsi_vc_dcs_write_nosync(TCH, buf, sizeof(buf)); | 
|  | 256 | if (r) | 
|  | 257 | return r; | 
|  | 258 |  | 
|  | 259 | buf[0] = DCS_PAGE_ADDR; | 
|  | 260 | buf[1] = (y1 >> 8) & 0xff; | 
|  | 261 | buf[2] = (y1 >> 0) & 0xff; | 
|  | 262 | buf[3] = (y2 >> 8) & 0xff; | 
|  | 263 | buf[4] = (y2 >> 0) & 0xff; | 
|  | 264 |  | 
|  | 265 | r = dsi_vc_dcs_write_nosync(TCH, buf, sizeof(buf)); | 
|  | 266 | if (r) | 
|  | 267 | return r; | 
|  | 268 |  | 
|  | 269 | dsi_vc_send_bta_sync(TCH); | 
|  | 270 |  | 
|  | 271 | return r; | 
|  | 272 | } | 
|  | 273 |  | 
|  | 274 | static int taal_bl_update_status(struct backlight_device *dev) | 
|  | 275 | { | 
|  | 276 | struct omap_dss_device *dssdev = dev_get_drvdata(&dev->dev); | 
|  | 277 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 278 | int r; | 
|  | 279 | int level; | 
|  | 280 |  | 
|  | 281 | if (dev->props.fb_blank == FB_BLANK_UNBLANK && | 
|  | 282 | dev->props.power == FB_BLANK_UNBLANK) | 
|  | 283 | level = dev->props.brightness; | 
|  | 284 | else | 
|  | 285 | level = 0; | 
|  | 286 |  | 
|  | 287 | dev_dbg(&dssdev->dev, "update brightness to %d\n", level); | 
|  | 288 |  | 
|  | 289 | if (td->use_dsi_bl) { | 
|  | 290 | if (td->enabled) { | 
|  | 291 | dsi_bus_lock(); | 
|  | 292 | r = taal_dcs_write_1(DCS_BRIGHTNESS, level); | 
|  | 293 | dsi_bus_unlock(); | 
|  | 294 | if (r) | 
|  | 295 | return r; | 
|  | 296 | } | 
|  | 297 | } else { | 
|  | 298 | if (!dssdev->set_backlight) | 
|  | 299 | return -EINVAL; | 
|  | 300 |  | 
|  | 301 | r = dssdev->set_backlight(dssdev, level); | 
|  | 302 | if (r) | 
|  | 303 | return r; | 
|  | 304 | } | 
|  | 305 |  | 
|  | 306 | return 0; | 
|  | 307 | } | 
|  | 308 |  | 
|  | 309 | static int taal_bl_get_intensity(struct backlight_device *dev) | 
|  | 310 | { | 
|  | 311 | if (dev->props.fb_blank == FB_BLANK_UNBLANK && | 
|  | 312 | dev->props.power == FB_BLANK_UNBLANK) | 
|  | 313 | return dev->props.brightness; | 
|  | 314 |  | 
|  | 315 | return 0; | 
|  | 316 | } | 
|  | 317 |  | 
|  | 318 | static struct backlight_ops taal_bl_ops = { | 
|  | 319 | .get_brightness = taal_bl_get_intensity, | 
|  | 320 | .update_status  = taal_bl_update_status, | 
|  | 321 | }; | 
|  | 322 |  | 
|  | 323 | static void taal_get_timings(struct omap_dss_device *dssdev, | 
|  | 324 | struct omap_video_timings *timings) | 
|  | 325 | { | 
|  | 326 | *timings = dssdev->panel.timings; | 
|  | 327 | } | 
|  | 328 |  | 
|  | 329 | static void taal_get_resolution(struct omap_dss_device *dssdev, | 
|  | 330 | u16 *xres, u16 *yres) | 
|  | 331 | { | 
|  | 332 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 333 |  | 
|  | 334 | if (td->rotate == 0 || td->rotate == 2) { | 
|  | 335 | *xres = dssdev->panel.timings.x_res; | 
|  | 336 | *yres = dssdev->panel.timings.y_res; | 
|  | 337 | } else { | 
|  | 338 | *yres = dssdev->panel.timings.x_res; | 
|  | 339 | *xres = dssdev->panel.timings.y_res; | 
|  | 340 | } | 
|  | 341 | } | 
|  | 342 |  | 
|  | 343 | static irqreturn_t taal_te_isr(int irq, void *data) | 
|  | 344 | { | 
|  | 345 | struct omap_dss_device *dssdev = data; | 
|  | 346 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 347 |  | 
|  | 348 | complete_all(&td->te_completion); | 
|  | 349 |  | 
|  | 350 | return IRQ_HANDLED; | 
|  | 351 | } | 
|  | 352 |  | 
|  | 353 | static ssize_t taal_num_errors_show(struct device *dev, | 
|  | 354 | struct device_attribute *attr, char *buf) | 
|  | 355 | { | 
|  | 356 | struct omap_dss_device *dssdev = to_dss_device(dev); | 
|  | 357 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 358 | u8 errors; | 
|  | 359 | int r; | 
|  | 360 |  | 
|  | 361 | if (td->enabled) { | 
|  | 362 | dsi_bus_lock(); | 
|  | 363 | r = taal_dcs_read_1(DCS_READ_NUM_ERRORS, &errors); | 
|  | 364 | dsi_bus_unlock(); | 
|  | 365 | } else { | 
|  | 366 | r = -ENODEV; | 
|  | 367 | } | 
|  | 368 |  | 
|  | 369 | if (r) | 
|  | 370 | return r; | 
|  | 371 |  | 
|  | 372 | return snprintf(buf, PAGE_SIZE, "%d\n", errors); | 
|  | 373 | } | 
|  | 374 |  | 
|  | 375 | static ssize_t taal_hw_revision_show(struct device *dev, | 
|  | 376 | struct device_attribute *attr, char *buf) | 
|  | 377 | { | 
|  | 378 | struct omap_dss_device *dssdev = to_dss_device(dev); | 
|  | 379 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 380 | u8 id1, id2, id3; | 
|  | 381 | int r; | 
|  | 382 |  | 
|  | 383 | if (td->enabled) { | 
|  | 384 | dsi_bus_lock(); | 
|  | 385 | r = taal_get_id(&id1, &id2, &id3); | 
|  | 386 | dsi_bus_unlock(); | 
|  | 387 | } else { | 
|  | 388 | r = -ENODEV; | 
|  | 389 | } | 
|  | 390 |  | 
|  | 391 | if (r) | 
|  | 392 | return r; | 
|  | 393 |  | 
|  | 394 | return snprintf(buf, PAGE_SIZE, "%02x.%02x.%02x\n", id1, id2, id3); | 
|  | 395 | } | 
|  | 396 |  | 
|  | 397 | static const char *cabc_modes[] = { | 
|  | 398 | "off",		/* used also always when CABC is not supported */ | 
|  | 399 | "ui", | 
|  | 400 | "still-image", | 
|  | 401 | "moving-image", | 
|  | 402 | }; | 
|  | 403 |  | 
|  | 404 | static ssize_t show_cabc_mode(struct device *dev, | 
|  | 405 | struct device_attribute *attr, | 
|  | 406 | char *buf) | 
|  | 407 | { | 
|  | 408 | struct omap_dss_device *dssdev = to_dss_device(dev); | 
|  | 409 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 410 | const char *mode_str; | 
|  | 411 | int mode; | 
|  | 412 | int len; | 
|  | 413 |  | 
|  | 414 | mode = td->cabc_mode; | 
|  | 415 |  | 
|  | 416 | mode_str = "unknown"; | 
|  | 417 | if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes)) | 
|  | 418 | mode_str = cabc_modes[mode]; | 
|  | 419 | len = snprintf(buf, PAGE_SIZE, "%s\n", mode_str); | 
|  | 420 |  | 
|  | 421 | return len < PAGE_SIZE - 1 ? len : PAGE_SIZE - 1; | 
|  | 422 | } | 
|  | 423 |  | 
|  | 424 | static ssize_t store_cabc_mode(struct device *dev, | 
|  | 425 | struct device_attribute *attr, | 
|  | 426 | const char *buf, size_t count) | 
|  | 427 | { | 
|  | 428 | struct omap_dss_device *dssdev = to_dss_device(dev); | 
|  | 429 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 430 | int i; | 
|  | 431 |  | 
|  | 432 | for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) { | 
|  | 433 | if (sysfs_streq(cabc_modes[i], buf)) | 
|  | 434 | break; | 
|  | 435 | } | 
|  | 436 |  | 
|  | 437 | if (i == ARRAY_SIZE(cabc_modes)) | 
|  | 438 | return -EINVAL; | 
|  | 439 |  | 
|  | 440 | if (td->enabled) { | 
|  | 441 | dsi_bus_lock(); | 
|  | 442 | if (!td->cabc_broken) | 
|  | 443 | taal_dcs_write_1(DCS_WRITE_CABC, i); | 
|  | 444 | dsi_bus_unlock(); | 
|  | 445 | } | 
|  | 446 |  | 
|  | 447 | td->cabc_mode = i; | 
|  | 448 |  | 
|  | 449 | return count; | 
|  | 450 | } | 
|  | 451 |  | 
|  | 452 | static ssize_t show_cabc_available_modes(struct device *dev, | 
|  | 453 | struct device_attribute *attr, | 
|  | 454 | char *buf) | 
|  | 455 | { | 
|  | 456 | int len; | 
|  | 457 | int i; | 
|  | 458 |  | 
|  | 459 | for (i = 0, len = 0; | 
|  | 460 | len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++) | 
|  | 461 | len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s", | 
|  | 462 | i ? " " : "", cabc_modes[i], | 
|  | 463 | i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : ""); | 
|  | 464 |  | 
|  | 465 | return len < PAGE_SIZE ? len : PAGE_SIZE - 1; | 
|  | 466 | } | 
|  | 467 |  | 
|  | 468 | static DEVICE_ATTR(num_dsi_errors, S_IRUGO, taal_num_errors_show, NULL); | 
|  | 469 | static DEVICE_ATTR(hw_revision, S_IRUGO, taal_hw_revision_show, NULL); | 
|  | 470 | static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR, | 
|  | 471 | show_cabc_mode, store_cabc_mode); | 
|  | 472 | static DEVICE_ATTR(cabc_available_modes, S_IRUGO, | 
|  | 473 | show_cabc_available_modes, NULL); | 
|  | 474 |  | 
|  | 475 | static struct attribute *taal_attrs[] = { | 
|  | 476 | &dev_attr_num_dsi_errors.attr, | 
|  | 477 | &dev_attr_hw_revision.attr, | 
|  | 478 | &dev_attr_cabc_mode.attr, | 
|  | 479 | &dev_attr_cabc_available_modes.attr, | 
|  | 480 | NULL, | 
|  | 481 | }; | 
|  | 482 |  | 
|  | 483 | static struct attribute_group taal_attr_group = { | 
|  | 484 | .attrs = taal_attrs, | 
|  | 485 | }; | 
|  | 486 |  | 
|  | 487 | static int taal_probe(struct omap_dss_device *dssdev) | 
|  | 488 | { | 
| Matthew Garrett | a19a6ee | 2010-02-17 16:39:44 -0500 | [diff] [blame] | 489 | struct backlight_properties props; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 490 | struct taal_data *td; | 
|  | 491 | struct backlight_device *bldev; | 
|  | 492 | int r; | 
|  | 493 |  | 
|  | 494 | const struct omap_video_timings taal_panel_timings = { | 
|  | 495 | .x_res		= 864, | 
|  | 496 | .y_res		= 480, | 
|  | 497 | }; | 
|  | 498 |  | 
|  | 499 | dev_dbg(&dssdev->dev, "probe\n"); | 
|  | 500 |  | 
|  | 501 | dssdev->panel.config = OMAP_DSS_LCD_TFT; | 
|  | 502 | dssdev->panel.timings = taal_panel_timings; | 
|  | 503 | dssdev->ctrl.pixel_size = 24; | 
|  | 504 |  | 
|  | 505 | td = kzalloc(sizeof(*td), GFP_KERNEL); | 
|  | 506 | if (!td) { | 
|  | 507 | r = -ENOMEM; | 
|  | 508 | goto err0; | 
|  | 509 | } | 
|  | 510 | td->dssdev = dssdev; | 
|  | 511 |  | 
|  | 512 | td->esd_wq = create_singlethread_workqueue("taal_esd"); | 
|  | 513 | if (td->esd_wq == NULL) { | 
|  | 514 | dev_err(&dssdev->dev, "can't create ESD workqueue\n"); | 
|  | 515 | r = -ENOMEM; | 
| Aaro Koskinen | 92fe0ff | 2009-12-09 17:26:25 +0100 | [diff] [blame] | 516 | goto err1; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 517 | } | 
|  | 518 | INIT_DELAYED_WORK_DEFERRABLE(&td->esd_work, taal_esd_work); | 
|  | 519 |  | 
|  | 520 | dev_set_drvdata(&dssdev->dev, td); | 
|  | 521 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 522 | /* if no platform set_backlight() defined, presume DSI backlight | 
|  | 523 | * control */ | 
| Matthew Garrett | a19a6ee | 2010-02-17 16:39:44 -0500 | [diff] [blame] | 524 | memset(&props, 0, sizeof(struct backlight_properties)); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 525 | if (!dssdev->set_backlight) | 
|  | 526 | td->use_dsi_bl = true; | 
|  | 527 |  | 
| Matthew Garrett | a19a6ee | 2010-02-17 16:39:44 -0500 | [diff] [blame] | 528 | if (td->use_dsi_bl) | 
|  | 529 | props.max_brightness = 255; | 
|  | 530 | else | 
|  | 531 | props.max_brightness = 127; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 532 | bldev = backlight_device_register("taal", &dssdev->dev, dssdev, | 
| Matthew Garrett | a19a6ee | 2010-02-17 16:39:44 -0500 | [diff] [blame] | 533 | &taal_bl_ops, &props); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 534 | if (IS_ERR(bldev)) { | 
|  | 535 | r = PTR_ERR(bldev); | 
| Aaro Koskinen | 92fe0ff | 2009-12-09 17:26:25 +0100 | [diff] [blame] | 536 | goto err2; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 537 | } | 
|  | 538 |  | 
|  | 539 | td->bldev = bldev; | 
|  | 540 |  | 
|  | 541 | bldev->props.fb_blank = FB_BLANK_UNBLANK; | 
|  | 542 | bldev->props.power = FB_BLANK_UNBLANK; | 
| Matthew Garrett | a19a6ee | 2010-02-17 16:39:44 -0500 | [diff] [blame] | 543 | if (td->use_dsi_bl) | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 544 | bldev->props.brightness = 255; | 
| Matthew Garrett | a19a6ee | 2010-02-17 16:39:44 -0500 | [diff] [blame] | 545 | else | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 546 | bldev->props.brightness = 127; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 547 |  | 
|  | 548 | taal_bl_update_status(bldev); | 
|  | 549 |  | 
|  | 550 | if (dssdev->phy.dsi.ext_te) { | 
|  | 551 | int gpio = dssdev->phy.dsi.ext_te_gpio; | 
|  | 552 |  | 
|  | 553 | r = gpio_request(gpio, "taal irq"); | 
|  | 554 | if (r) { | 
|  | 555 | dev_err(&dssdev->dev, "GPIO request failed\n"); | 
|  | 556 | goto err3; | 
|  | 557 | } | 
|  | 558 |  | 
|  | 559 | gpio_direction_input(gpio); | 
|  | 560 |  | 
|  | 561 | r = request_irq(gpio_to_irq(gpio), taal_te_isr, | 
|  | 562 | IRQF_DISABLED | IRQF_TRIGGER_RISING, | 
|  | 563 | "taal vsync", dssdev); | 
|  | 564 |  | 
|  | 565 | if (r) { | 
|  | 566 | dev_err(&dssdev->dev, "IRQ request failed\n"); | 
|  | 567 | gpio_free(gpio); | 
|  | 568 | goto err3; | 
|  | 569 | } | 
|  | 570 |  | 
|  | 571 | init_completion(&td->te_completion); | 
|  | 572 |  | 
|  | 573 | td->use_ext_te = true; | 
|  | 574 | } | 
|  | 575 |  | 
|  | 576 | r = sysfs_create_group(&dssdev->dev.kobj, &taal_attr_group); | 
|  | 577 | if (r) { | 
|  | 578 | dev_err(&dssdev->dev, "failed to create sysfs files\n"); | 
|  | 579 | goto err4; | 
|  | 580 | } | 
|  | 581 |  | 
|  | 582 | return 0; | 
|  | 583 | err4: | 
|  | 584 | if (td->use_ext_te) { | 
|  | 585 | int gpio = dssdev->phy.dsi.ext_te_gpio; | 
|  | 586 | free_irq(gpio_to_irq(gpio), dssdev); | 
|  | 587 | gpio_free(gpio); | 
|  | 588 | } | 
|  | 589 | err3: | 
|  | 590 | backlight_device_unregister(bldev); | 
|  | 591 | err2: | 
|  | 592 | cancel_delayed_work_sync(&td->esd_work); | 
|  | 593 | destroy_workqueue(td->esd_wq); | 
|  | 594 | err1: | 
|  | 595 | kfree(td); | 
|  | 596 | err0: | 
|  | 597 | return r; | 
|  | 598 | } | 
|  | 599 |  | 
|  | 600 | static void taal_remove(struct omap_dss_device *dssdev) | 
|  | 601 | { | 
|  | 602 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 603 | struct backlight_device *bldev; | 
|  | 604 |  | 
|  | 605 | dev_dbg(&dssdev->dev, "remove\n"); | 
|  | 606 |  | 
|  | 607 | sysfs_remove_group(&dssdev->dev.kobj, &taal_attr_group); | 
|  | 608 |  | 
|  | 609 | if (td->use_ext_te) { | 
|  | 610 | int gpio = dssdev->phy.dsi.ext_te_gpio; | 
|  | 611 | free_irq(gpio_to_irq(gpio), dssdev); | 
|  | 612 | gpio_free(gpio); | 
|  | 613 | } | 
|  | 614 |  | 
|  | 615 | bldev = td->bldev; | 
|  | 616 | bldev->props.power = FB_BLANK_POWERDOWN; | 
|  | 617 | taal_bl_update_status(bldev); | 
|  | 618 | backlight_device_unregister(bldev); | 
|  | 619 |  | 
|  | 620 | cancel_delayed_work_sync(&td->esd_work); | 
|  | 621 | destroy_workqueue(td->esd_wq); | 
|  | 622 |  | 
|  | 623 | kfree(td); | 
|  | 624 | } | 
|  | 625 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 626 | static int taal_power_on(struct omap_dss_device *dssdev) | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 627 | { | 
|  | 628 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 629 | u8 id1, id2, id3; | 
|  | 630 | int r; | 
|  | 631 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 632 | if (dssdev->platform_enable) { | 
|  | 633 | r = dssdev->platform_enable(dssdev); | 
|  | 634 | if (r) | 
|  | 635 | return r; | 
|  | 636 | } | 
|  | 637 |  | 
|  | 638 | /* it seems we have to wait a bit until taal is ready */ | 
|  | 639 | msleep(5); | 
|  | 640 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 641 | dsi_bus_lock(); | 
|  | 642 |  | 
|  | 643 | r = omapdss_dsi_display_enable(dssdev); | 
|  | 644 | if (r) { | 
|  | 645 | dev_err(&dssdev->dev, "failed to enable DSI\n"); | 
|  | 646 | goto err0; | 
|  | 647 | } | 
|  | 648 |  | 
|  | 649 | omapdss_dsi_vc_enable_hs(TCH, false); | 
|  | 650 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 651 | r = taal_sleep_out(td); | 
|  | 652 | if (r) | 
|  | 653 | goto err; | 
|  | 654 |  | 
|  | 655 | r = taal_get_id(&id1, &id2, &id3); | 
|  | 656 | if (r) | 
|  | 657 | goto err; | 
|  | 658 |  | 
|  | 659 | /* on early revisions CABC is broken */ | 
|  | 660 | if (id2 == 0x00 || id2 == 0xff || id2 == 0x81) | 
|  | 661 | td->cabc_broken = true; | 
|  | 662 |  | 
|  | 663 | taal_dcs_write_1(DCS_BRIGHTNESS, 0xff); | 
|  | 664 | taal_dcs_write_1(DCS_CTRL_DISPLAY, (1<<2) | (1<<5)); /* BL | BCTRL */ | 
|  | 665 |  | 
|  | 666 | taal_dcs_write_1(DCS_PIXEL_FORMAT, 0x7); /* 24bit/pixel */ | 
|  | 667 |  | 
|  | 668 | taal_set_addr_mode(td->rotate, td->mirror); | 
|  | 669 | if (!td->cabc_broken) | 
|  | 670 | taal_dcs_write_1(DCS_WRITE_CABC, td->cabc_mode); | 
|  | 671 |  | 
|  | 672 | taal_dcs_write_0(DCS_DISPLAY_ON); | 
|  | 673 |  | 
| Tomi Valkeinen | 21df20f | 2010-03-02 12:13:55 +0200 | [diff] [blame] | 674 | r = _taal_enable_te(dssdev, td->te_enabled); | 
|  | 675 | if (r) | 
|  | 676 | goto err; | 
|  | 677 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 678 | #ifdef TAAL_USE_ESD_CHECK | 
|  | 679 | queue_delayed_work(td->esd_wq, &td->esd_work, TAAL_ESD_CHECK_PERIOD); | 
|  | 680 | #endif | 
|  | 681 |  | 
|  | 682 | td->enabled = 1; | 
|  | 683 |  | 
|  | 684 | if (!td->intro_printed) { | 
|  | 685 | dev_info(&dssdev->dev, "revision %02x.%02x.%02x\n", | 
|  | 686 | id1, id2, id3); | 
|  | 687 | if (td->cabc_broken) | 
|  | 688 | dev_info(&dssdev->dev, | 
|  | 689 | "old Taal version, CABC disabled\n"); | 
|  | 690 | td->intro_printed = true; | 
|  | 691 | } | 
|  | 692 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 693 | omapdss_dsi_vc_enable_hs(TCH, true); | 
|  | 694 |  | 
|  | 695 | dsi_bus_unlock(); | 
|  | 696 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 697 | return 0; | 
|  | 698 | err: | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 699 | dsi_bus_unlock(); | 
|  | 700 |  | 
|  | 701 | omapdss_dsi_display_disable(dssdev); | 
|  | 702 | err0: | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 703 | if (dssdev->platform_disable) | 
|  | 704 | dssdev->platform_disable(dssdev); | 
|  | 705 |  | 
|  | 706 | return r; | 
|  | 707 | } | 
|  | 708 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 709 | static void taal_power_off(struct omap_dss_device *dssdev) | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 710 | { | 
|  | 711 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 712 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 713 | dsi_bus_lock(); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 714 |  | 
|  | 715 | cancel_delayed_work(&td->esd_work); | 
|  | 716 |  | 
|  | 717 | taal_dcs_write_0(DCS_DISPLAY_OFF); | 
|  | 718 | taal_sleep_in(td); | 
|  | 719 |  | 
|  | 720 | /* wait a bit so that the message goes through */ | 
|  | 721 | msleep(10); | 
|  | 722 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 723 | omapdss_dsi_display_disable(dssdev); | 
|  | 724 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 725 | if (dssdev->platform_disable) | 
|  | 726 | dssdev->platform_disable(dssdev); | 
|  | 727 |  | 
|  | 728 | td->enabled = 0; | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 729 |  | 
|  | 730 | dsi_bus_unlock(); | 
|  | 731 | } | 
|  | 732 |  | 
|  | 733 | static int taal_enable(struct omap_dss_device *dssdev) | 
|  | 734 | { | 
|  | 735 | int r; | 
|  | 736 | dev_dbg(&dssdev->dev, "enable\n"); | 
|  | 737 |  | 
|  | 738 | if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) | 
|  | 739 | return -EINVAL; | 
|  | 740 |  | 
|  | 741 | r = taal_power_on(dssdev); | 
|  | 742 | if (r) | 
|  | 743 | return r; | 
|  | 744 |  | 
|  | 745 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; | 
|  | 746 |  | 
|  | 747 | return r; | 
|  | 748 | } | 
|  | 749 |  | 
|  | 750 | static void taal_disable(struct omap_dss_device *dssdev) | 
|  | 751 | { | 
|  | 752 | dev_dbg(&dssdev->dev, "disable\n"); | 
|  | 753 |  | 
|  | 754 | if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) | 
|  | 755 | taal_power_off(dssdev); | 
|  | 756 |  | 
|  | 757 | dssdev->state = OMAP_DSS_DISPLAY_DISABLED; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 758 | } | 
|  | 759 |  | 
|  | 760 | static int taal_suspend(struct omap_dss_device *dssdev) | 
|  | 761 | { | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 762 | dev_dbg(&dssdev->dev, "suspend\n"); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 763 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 764 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) | 
|  | 765 | return -EINVAL; | 
|  | 766 |  | 
|  | 767 | taal_power_off(dssdev); | 
|  | 768 | dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 769 |  | 
|  | 770 | return 0; | 
|  | 771 | } | 
|  | 772 |  | 
|  | 773 | static int taal_resume(struct omap_dss_device *dssdev) | 
|  | 774 | { | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 775 | int r; | 
|  | 776 | dev_dbg(&dssdev->dev, "resume\n"); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 777 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 778 | if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED) | 
|  | 779 | return -EINVAL; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 780 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 781 | r = taal_power_on(dssdev); | 
|  | 782 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; | 
|  | 783 | return r; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 784 | } | 
|  | 785 |  | 
| Tomi Valkeinen | 18946f6 | 2010-01-12 14:16:41 +0200 | [diff] [blame] | 786 | static void taal_framedone_cb(int err, void *data) | 
|  | 787 | { | 
|  | 788 | struct omap_dss_device *dssdev = data; | 
|  | 789 | dev_dbg(&dssdev->dev, "framedone, err %d\n", err); | 
|  | 790 | dsi_bus_unlock(); | 
|  | 791 | } | 
|  | 792 |  | 
|  | 793 | static int taal_update(struct omap_dss_device *dssdev, | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 794 | u16 x, u16 y, u16 w, u16 h) | 
|  | 795 | { | 
| Tomi Valkeinen | 18946f6 | 2010-01-12 14:16:41 +0200 | [diff] [blame] | 796 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 797 | int r; | 
|  | 798 |  | 
|  | 799 | dev_dbg(&dssdev->dev, "update %d, %d, %d x %d\n", x, y, w, h); | 
|  | 800 |  | 
|  | 801 | dsi_bus_lock(); | 
|  | 802 |  | 
|  | 803 | if (!td->enabled) { | 
|  | 804 | r = 0; | 
|  | 805 | goto err; | 
|  | 806 | } | 
|  | 807 |  | 
|  | 808 | r = omap_dsi_prepare_update(dssdev, &x, &y, &w, &h); | 
|  | 809 | if (r) | 
|  | 810 | goto err; | 
|  | 811 |  | 
|  | 812 | r = taal_set_update_window(x, y, w, h); | 
|  | 813 | if (r) | 
|  | 814 | goto err; | 
|  | 815 |  | 
|  | 816 | r = omap_dsi_update(dssdev, TCH, x, y, w, h, | 
|  | 817 | taal_framedone_cb, dssdev); | 
|  | 818 | if (r) | 
|  | 819 | goto err; | 
|  | 820 |  | 
|  | 821 | /* note: no bus_unlock here. unlock is in framedone_cb */ | 
|  | 822 | return 0; | 
|  | 823 | err: | 
|  | 824 | dsi_bus_unlock(); | 
|  | 825 | return r; | 
|  | 826 | } | 
|  | 827 |  | 
|  | 828 | static int taal_sync(struct omap_dss_device *dssdev) | 
|  | 829 | { | 
|  | 830 | dev_dbg(&dssdev->dev, "sync\n"); | 
|  | 831 |  | 
|  | 832 | dsi_bus_lock(); | 
|  | 833 | dsi_bus_unlock(); | 
|  | 834 |  | 
|  | 835 | dev_dbg(&dssdev->dev, "sync done\n"); | 
|  | 836 |  | 
|  | 837 | return 0; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 838 | } | 
|  | 839 |  | 
| Tomi Valkeinen | 21df20f | 2010-03-02 12:13:55 +0200 | [diff] [blame] | 840 | static int _taal_enable_te(struct omap_dss_device *dssdev, bool enable) | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 841 | { | 
|  | 842 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 843 | int r; | 
|  | 844 |  | 
|  | 845 | td->te_enabled = enable; | 
|  | 846 |  | 
|  | 847 | if (enable) | 
|  | 848 | r = taal_dcs_write_1(DCS_TEAR_ON, 0); | 
|  | 849 | else | 
|  | 850 | r = taal_dcs_write_0(DCS_TEAR_OFF); | 
|  | 851 |  | 
| Tomi Valkeinen | 225b650 | 2010-01-11 15:11:01 +0200 | [diff] [blame] | 852 | omapdss_dsi_enable_te(dssdev, enable); | 
|  | 853 |  | 
|  | 854 | /* XXX for some reason, DSI TE breaks if we don't wait here. | 
|  | 855 | * Panel bug? Needs more studying */ | 
|  | 856 | msleep(100); | 
|  | 857 |  | 
| Tomi Valkeinen | 21df20f | 2010-03-02 12:13:55 +0200 | [diff] [blame] | 858 | return r; | 
|  | 859 | } | 
|  | 860 |  | 
|  | 861 | static int taal_enable_te(struct omap_dss_device *dssdev, bool enable) | 
|  | 862 | { | 
|  | 863 | int r; | 
|  | 864 |  | 
|  | 865 | dsi_bus_lock(); | 
|  | 866 |  | 
|  | 867 | r = _taal_enable_te(dssdev, enable); | 
|  | 868 |  | 
| Tomi Valkeinen | 225b650 | 2010-01-11 15:11:01 +0200 | [diff] [blame] | 869 | dsi_bus_unlock(); | 
|  | 870 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 871 | return r; | 
|  | 872 | } | 
|  | 873 |  | 
| Tomi Valkeinen | 225b650 | 2010-01-11 15:11:01 +0200 | [diff] [blame] | 874 | static int taal_get_te(struct omap_dss_device *dssdev) | 
|  | 875 | { | 
|  | 876 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 877 | return td->te_enabled; | 
|  | 878 | } | 
|  | 879 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 880 | static int taal_rotate(struct omap_dss_device *dssdev, u8 rotate) | 
|  | 881 | { | 
|  | 882 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 883 | int r; | 
|  | 884 |  | 
|  | 885 | dev_dbg(&dssdev->dev, "rotate %d\n", rotate); | 
|  | 886 |  | 
| Tomi Valkeinen | 87424e1 | 2010-01-08 16:52:48 +0200 | [diff] [blame] | 887 | dsi_bus_lock(); | 
|  | 888 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 889 | if (td->enabled) { | 
|  | 890 | r = taal_set_addr_mode(rotate, td->mirror); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 891 | if (r) | 
| Tomi Valkeinen | 87424e1 | 2010-01-08 16:52:48 +0200 | [diff] [blame] | 892 | goto err; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 893 | } | 
|  | 894 |  | 
|  | 895 | td->rotate = rotate; | 
|  | 896 |  | 
| Tomi Valkeinen | 87424e1 | 2010-01-08 16:52:48 +0200 | [diff] [blame] | 897 | dsi_bus_unlock(); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 898 | return 0; | 
| Tomi Valkeinen | 87424e1 | 2010-01-08 16:52:48 +0200 | [diff] [blame] | 899 | err: | 
|  | 900 | dsi_bus_unlock(); | 
|  | 901 | return r; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 902 | } | 
|  | 903 |  | 
|  | 904 | static u8 taal_get_rotate(struct omap_dss_device *dssdev) | 
|  | 905 | { | 
|  | 906 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 907 | return td->rotate; | 
|  | 908 | } | 
|  | 909 |  | 
|  | 910 | static int taal_mirror(struct omap_dss_device *dssdev, bool enable) | 
|  | 911 | { | 
|  | 912 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 913 | int r; | 
|  | 914 |  | 
|  | 915 | dev_dbg(&dssdev->dev, "mirror %d\n", enable); | 
|  | 916 |  | 
| Tomi Valkeinen | 8d8aa61 | 2010-01-08 16:30:33 +0200 | [diff] [blame] | 917 | dsi_bus_lock(); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 918 | if (td->enabled) { | 
|  | 919 | r = taal_set_addr_mode(td->rotate, enable); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 920 | if (r) | 
| Tomi Valkeinen | 8d8aa61 | 2010-01-08 16:30:33 +0200 | [diff] [blame] | 921 | goto err; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 922 | } | 
|  | 923 |  | 
|  | 924 | td->mirror = enable; | 
|  | 925 |  | 
| Tomi Valkeinen | 8d8aa61 | 2010-01-08 16:30:33 +0200 | [diff] [blame] | 926 | dsi_bus_unlock(); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 927 | return 0; | 
| Tomi Valkeinen | 8d8aa61 | 2010-01-08 16:30:33 +0200 | [diff] [blame] | 928 | err: | 
|  | 929 | dsi_bus_unlock(); | 
|  | 930 | return r; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 931 | } | 
|  | 932 |  | 
|  | 933 | static bool taal_get_mirror(struct omap_dss_device *dssdev) | 
|  | 934 | { | 
|  | 935 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 936 | return td->mirror; | 
|  | 937 | } | 
|  | 938 |  | 
|  | 939 | static int taal_run_test(struct omap_dss_device *dssdev, int test_num) | 
|  | 940 | { | 
|  | 941 | u8 id1, id2, id3; | 
|  | 942 | int r; | 
|  | 943 |  | 
| Tomi Valkeinen | 1a75ef4 | 2010-01-08 16:21:28 +0200 | [diff] [blame] | 944 | dsi_bus_lock(); | 
|  | 945 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 946 | r = taal_dcs_read_1(DCS_GET_ID1, &id1); | 
|  | 947 | if (r) | 
| Tomi Valkeinen | 1a75ef4 | 2010-01-08 16:21:28 +0200 | [diff] [blame] | 948 | goto err; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 949 | r = taal_dcs_read_1(DCS_GET_ID2, &id2); | 
|  | 950 | if (r) | 
| Tomi Valkeinen | 1a75ef4 | 2010-01-08 16:21:28 +0200 | [diff] [blame] | 951 | goto err; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 952 | r = taal_dcs_read_1(DCS_GET_ID3, &id3); | 
|  | 953 | if (r) | 
| Tomi Valkeinen | 1a75ef4 | 2010-01-08 16:21:28 +0200 | [diff] [blame] | 954 | goto err; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 955 |  | 
| Tomi Valkeinen | 1a75ef4 | 2010-01-08 16:21:28 +0200 | [diff] [blame] | 956 | dsi_bus_unlock(); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 957 | return 0; | 
| Tomi Valkeinen | 1a75ef4 | 2010-01-08 16:21:28 +0200 | [diff] [blame] | 958 | err: | 
|  | 959 | dsi_bus_unlock(); | 
|  | 960 | return r; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 961 | } | 
|  | 962 |  | 
|  | 963 | static int taal_memory_read(struct omap_dss_device *dssdev, | 
|  | 964 | void *buf, size_t size, | 
|  | 965 | u16 x, u16 y, u16 w, u16 h) | 
|  | 966 | { | 
|  | 967 | int r; | 
|  | 968 | int first = 1; | 
|  | 969 | int plen; | 
|  | 970 | unsigned buf_used = 0; | 
| Tomi Valkeinen | c75d946 | 2010-01-08 16:56:44 +0200 | [diff] [blame] | 971 | struct taal_data *td = dev_get_drvdata(&dssdev->dev); | 
|  | 972 |  | 
|  | 973 | if (!td->enabled) | 
|  | 974 | return -ENODEV; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 975 |  | 
|  | 976 | if (size < w * h * 3) | 
|  | 977 | return -ENOMEM; | 
|  | 978 |  | 
|  | 979 | size = min(w * h * 3, | 
|  | 980 | dssdev->panel.timings.x_res * | 
|  | 981 | dssdev->panel.timings.y_res * 3); | 
|  | 982 |  | 
| Tomi Valkeinen | c75d946 | 2010-01-08 16:56:44 +0200 | [diff] [blame] | 983 | dsi_bus_lock(); | 
|  | 984 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 985 | /* plen 1 or 2 goes into short packet. until checksum error is fixed, | 
|  | 986 | * use short packets. plen 32 works, but bigger packets seem to cause | 
|  | 987 | * an error. */ | 
|  | 988 | if (size % 2) | 
|  | 989 | plen = 1; | 
|  | 990 | else | 
|  | 991 | plen = 2; | 
|  | 992 |  | 
| Tomi Valkeinen | c75d946 | 2010-01-08 16:56:44 +0200 | [diff] [blame] | 993 | taal_set_update_window(x, y, w, h); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 994 |  | 
|  | 995 | r = dsi_vc_set_max_rx_packet_size(TCH, plen); | 
|  | 996 | if (r) | 
| Tomi Valkeinen | c75d946 | 2010-01-08 16:56:44 +0200 | [diff] [blame] | 997 | goto err0; | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 998 |  | 
|  | 999 | while (buf_used < size) { | 
|  | 1000 | u8 dcs_cmd = first ? 0x2e : 0x3e; | 
|  | 1001 | first = 0; | 
|  | 1002 |  | 
|  | 1003 | r = dsi_vc_dcs_read(TCH, dcs_cmd, | 
|  | 1004 | buf + buf_used, size - buf_used); | 
|  | 1005 |  | 
|  | 1006 | if (r < 0) { | 
|  | 1007 | dev_err(&dssdev->dev, "read error\n"); | 
|  | 1008 | goto err; | 
|  | 1009 | } | 
|  | 1010 |  | 
|  | 1011 | buf_used += r; | 
|  | 1012 |  | 
|  | 1013 | if (r < plen) { | 
|  | 1014 | dev_err(&dssdev->dev, "short read\n"); | 
|  | 1015 | break; | 
|  | 1016 | } | 
|  | 1017 |  | 
|  | 1018 | if (signal_pending(current)) { | 
|  | 1019 | dev_err(&dssdev->dev, "signal pending, " | 
|  | 1020 | "aborting memory read\n"); | 
|  | 1021 | r = -ERESTARTSYS; | 
|  | 1022 | goto err; | 
|  | 1023 | } | 
|  | 1024 | } | 
|  | 1025 |  | 
|  | 1026 | r = buf_used; | 
|  | 1027 |  | 
|  | 1028 | err: | 
|  | 1029 | dsi_vc_set_max_rx_packet_size(TCH, 1); | 
| Tomi Valkeinen | c75d946 | 2010-01-08 16:56:44 +0200 | [diff] [blame] | 1030 | err0: | 
|  | 1031 | dsi_bus_unlock(); | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 1032 | return r; | 
|  | 1033 | } | 
|  | 1034 |  | 
|  | 1035 | static void taal_esd_work(struct work_struct *work) | 
|  | 1036 | { | 
|  | 1037 | struct taal_data *td = container_of(work, struct taal_data, | 
|  | 1038 | esd_work.work); | 
|  | 1039 | struct omap_dss_device *dssdev = td->dssdev; | 
|  | 1040 | u8 state1, state2; | 
|  | 1041 | int r; | 
|  | 1042 |  | 
|  | 1043 | if (!td->enabled) | 
|  | 1044 | return; | 
|  | 1045 |  | 
|  | 1046 | dsi_bus_lock(); | 
|  | 1047 |  | 
|  | 1048 | r = taal_dcs_read_1(DCS_RDDSDR, &state1); | 
|  | 1049 | if (r) { | 
|  | 1050 | dev_err(&dssdev->dev, "failed to read Taal status\n"); | 
|  | 1051 | goto err; | 
|  | 1052 | } | 
|  | 1053 |  | 
|  | 1054 | /* Run self diagnostics */ | 
|  | 1055 | r = taal_sleep_out(td); | 
|  | 1056 | if (r) { | 
|  | 1057 | dev_err(&dssdev->dev, "failed to run Taal self-diagnostics\n"); | 
|  | 1058 | goto err; | 
|  | 1059 | } | 
|  | 1060 |  | 
|  | 1061 | r = taal_dcs_read_1(DCS_RDDSDR, &state2); | 
|  | 1062 | if (r) { | 
|  | 1063 | dev_err(&dssdev->dev, "failed to read Taal status\n"); | 
|  | 1064 | goto err; | 
|  | 1065 | } | 
|  | 1066 |  | 
|  | 1067 | /* Each sleep out command will trigger a self diagnostic and flip | 
|  | 1068 | * Bit6 if the test passes. | 
|  | 1069 | */ | 
|  | 1070 | if (!((state1 ^ state2) & (1 << 6))) { | 
|  | 1071 | dev_err(&dssdev->dev, "LCD self diagnostics failed\n"); | 
|  | 1072 | goto err; | 
|  | 1073 | } | 
|  | 1074 | /* Self-diagnostics result is also shown on TE GPIO line. We need | 
|  | 1075 | * to re-enable TE after self diagnostics */ | 
| Tomi Valkeinen | 1189b7f | 2010-03-01 13:52:10 +0200 | [diff] [blame] | 1076 | if (td->use_ext_te && td->te_enabled) { | 
|  | 1077 | r = taal_dcs_write_1(DCS_TEAR_ON, 0); | 
|  | 1078 | if (r) | 
|  | 1079 | goto err; | 
|  | 1080 | } | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 1081 |  | 
|  | 1082 | dsi_bus_unlock(); | 
|  | 1083 |  | 
|  | 1084 | queue_delayed_work(td->esd_wq, &td->esd_work, TAAL_ESD_CHECK_PERIOD); | 
|  | 1085 |  | 
|  | 1086 | return; | 
|  | 1087 | err: | 
|  | 1088 | dev_err(&dssdev->dev, "performing LCD reset\n"); | 
|  | 1089 |  | 
|  | 1090 | taal_disable(dssdev); | 
|  | 1091 | taal_enable(dssdev); | 
|  | 1092 |  | 
|  | 1093 | dsi_bus_unlock(); | 
|  | 1094 |  | 
|  | 1095 | queue_delayed_work(td->esd_wq, &td->esd_work, TAAL_ESD_CHECK_PERIOD); | 
|  | 1096 | } | 
|  | 1097 |  | 
| Tomi Valkeinen | 446f7bf | 2010-01-11 16:12:31 +0200 | [diff] [blame] | 1098 | static int taal_set_update_mode(struct omap_dss_device *dssdev, | 
|  | 1099 | enum omap_dss_update_mode mode) | 
|  | 1100 | { | 
|  | 1101 | if (mode != OMAP_DSS_UPDATE_MANUAL) | 
|  | 1102 | return -EINVAL; | 
|  | 1103 | return 0; | 
|  | 1104 | } | 
|  | 1105 |  | 
|  | 1106 | static enum omap_dss_update_mode taal_get_update_mode( | 
|  | 1107 | struct omap_dss_device *dssdev) | 
|  | 1108 | { | 
|  | 1109 | return OMAP_DSS_UPDATE_MANUAL; | 
|  | 1110 | } | 
|  | 1111 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 1112 | static struct omap_dss_driver taal_driver = { | 
|  | 1113 | .probe		= taal_probe, | 
|  | 1114 | .remove		= taal_remove, | 
|  | 1115 |  | 
|  | 1116 | .enable		= taal_enable, | 
|  | 1117 | .disable	= taal_disable, | 
|  | 1118 | .suspend	= taal_suspend, | 
|  | 1119 | .resume		= taal_resume, | 
|  | 1120 |  | 
| Tomi Valkeinen | 446f7bf | 2010-01-11 16:12:31 +0200 | [diff] [blame] | 1121 | .set_update_mode = taal_set_update_mode, | 
|  | 1122 | .get_update_mode = taal_get_update_mode, | 
| Tomi Valkeinen | 18946f6 | 2010-01-12 14:16:41 +0200 | [diff] [blame] | 1123 |  | 
|  | 1124 | .update		= taal_update, | 
|  | 1125 | .sync		= taal_sync, | 
|  | 1126 |  | 
| Tomi Valkeinen | 96adcec | 2010-01-11 13:54:33 +0200 | [diff] [blame] | 1127 | .get_resolution	= taal_get_resolution, | 
| Tomi Valkeinen | a269950 | 2010-01-11 14:33:40 +0200 | [diff] [blame] | 1128 | .get_recommended_bpp = omapdss_default_get_recommended_bpp, | 
|  | 1129 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 1130 | .enable_te	= taal_enable_te, | 
| Tomi Valkeinen | 225b650 | 2010-01-11 15:11:01 +0200 | [diff] [blame] | 1131 | .get_te		= taal_get_te, | 
| Tomi Valkeinen | 225b650 | 2010-01-11 15:11:01 +0200 | [diff] [blame] | 1132 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 1133 | .set_rotate	= taal_rotate, | 
|  | 1134 | .get_rotate	= taal_get_rotate, | 
|  | 1135 | .set_mirror	= taal_mirror, | 
|  | 1136 | .get_mirror	= taal_get_mirror, | 
|  | 1137 | .run_test	= taal_run_test, | 
|  | 1138 | .memory_read	= taal_memory_read, | 
|  | 1139 |  | 
| Tomi Valkeinen | 69b2048 | 2010-01-20 12:11:25 +0200 | [diff] [blame] | 1140 | .get_timings	= taal_get_timings, | 
|  | 1141 |  | 
| Tomi Valkeinen | f133a9d | 2009-10-28 11:31:05 +0200 | [diff] [blame] | 1142 | .driver         = { | 
|  | 1143 | .name   = "taal", | 
|  | 1144 | .owner  = THIS_MODULE, | 
|  | 1145 | }, | 
|  | 1146 | }; | 
|  | 1147 |  | 
|  | 1148 | static int __init taal_init(void) | 
|  | 1149 | { | 
|  | 1150 | omap_dss_register_driver(&taal_driver); | 
|  | 1151 |  | 
|  | 1152 | return 0; | 
|  | 1153 | } | 
|  | 1154 |  | 
|  | 1155 | static void __exit taal_exit(void) | 
|  | 1156 | { | 
|  | 1157 | omap_dss_unregister_driver(&taal_driver); | 
|  | 1158 | } | 
|  | 1159 |  | 
|  | 1160 | module_init(taal_init); | 
|  | 1161 | module_exit(taal_exit); | 
|  | 1162 |  | 
|  | 1163 | MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@nokia.com>"); | 
|  | 1164 | MODULE_DESCRIPTION("Taal Driver"); | 
|  | 1165 | MODULE_LICENSE("GPL"); |