| 1 | /* Linux kernel driver for the tpo JBT6K74-AS LCM ASIC |
| 2 | * |
| 3 | * Copyright (C) 2006-2007 by Openmoko, Inc. |
| 4 | * Author: Harald Welte <laforge@openmoko.org>, |
| 5 | * Stefan Schmidt <stefan@openmoko.org> |
| 6 | * Copyright (C) 2008 by Harald Welte <laforge@openmoko.org> |
| 7 | * Copyright (C) 2009 by Lars-Peter Clausen <lars@metafoo.de> |
| 8 | * All rights reserved. |
| 9 | * |
| 10 | * This program is free software; you can redistribute it and/or |
| 11 | * modify it under the terms of the GNU General Public License as |
| 12 | * published by the Free Software Foundation; either version 2 of |
| 13 | * the License, or (at your option) any later version. |
| 14 | * |
| 15 | * This program is distributed in the hope that it will be useful, |
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | * GNU General Public License for more details. |
| 19 | * |
| 20 | * You should have received a copy of the GNU General Public License |
| 21 | * along with this program; if not, write to the Free Software |
| 22 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| 23 | * MA 02111-1307 USA |
| 24 | * |
| 25 | */ |
| 26 | |
| 27 | #include <linux/kernel.h> |
| 28 | #include <linux/types.h> |
| 29 | #include <linux/module.h> |
| 30 | #include <linux/device.h> |
| 31 | #include <linux/platform_device.h> |
| 32 | #include <linux/delay.h> |
| 33 | #include <linux/workqueue.h> |
| 34 | #include <linux/jbt6k74.h> |
| 35 | #include <linux/fb.h> |
| 36 | #include <linux/lcd.h> |
| 37 | #include <linux/time.h> |
| 38 | |
| 39 | enum jbt_register { |
| 40 | JBT_REG_SLEEP_IN = 0x10, |
| 41 | JBT_REG_SLEEP_OUT = 0x11, |
| 42 | |
| 43 | JBT_REG_DISPLAY_OFF = 0x28, |
| 44 | JBT_REG_DISPLAY_ON = 0x29, |
| 45 | |
| 46 | JBT_REG_RGB_FORMAT = 0x3a, |
| 47 | JBT_REG_QUAD_RATE = 0x3b, |
| 48 | |
| 49 | JBT_REG_POWER_ON_OFF = 0xb0, |
| 50 | JBT_REG_BOOSTER_OP = 0xb1, |
| 51 | JBT_REG_BOOSTER_MODE = 0xb2, |
| 52 | JBT_REG_BOOSTER_FREQ = 0xb3, |
| 53 | JBT_REG_OPAMP_SYSCLK = 0xb4, |
| 54 | JBT_REG_VSC_VOLTAGE = 0xb5, |
| 55 | JBT_REG_VCOM_VOLTAGE = 0xb6, |
| 56 | JBT_REG_EXT_DISPL = 0xb7, |
| 57 | JBT_REG_OUTPUT_CONTROL = 0xb8, |
| 58 | JBT_REG_DCCLK_DCEV = 0xb9, |
| 59 | JBT_REG_DISPLAY_MODE1 = 0xba, |
| 60 | JBT_REG_DISPLAY_MODE2 = 0xbb, |
| 61 | JBT_REG_DISPLAY_MODE = 0xbc, |
| 62 | JBT_REG_ASW_SLEW = 0xbd, |
| 63 | JBT_REG_DUMMY_DISPLAY = 0xbe, |
| 64 | JBT_REG_DRIVE_SYSTEM = 0xbf, |
| 65 | |
| 66 | JBT_REG_SLEEP_OUT_FR_A = 0xc0, |
| 67 | JBT_REG_SLEEP_OUT_FR_B = 0xc1, |
| 68 | JBT_REG_SLEEP_OUT_FR_C = 0xc2, |
| 69 | JBT_REG_SLEEP_IN_LCCNT_D = 0xc3, |
| 70 | JBT_REG_SLEEP_IN_LCCNT_E = 0xc4, |
| 71 | JBT_REG_SLEEP_IN_LCCNT_F = 0xc5, |
| 72 | JBT_REG_SLEEP_IN_LCCNT_G = 0xc6, |
| 73 | |
| 74 | JBT_REG_GAMMA1_FINE_1 = 0xc7, |
| 75 | JBT_REG_GAMMA1_FINE_2 = 0xc8, |
| 76 | JBT_REG_GAMMA1_INCLINATION = 0xc9, |
| 77 | JBT_REG_GAMMA1_BLUE_OFFSET = 0xca, |
| 78 | |
| 79 | /* VGA */ |
| 80 | JBT_REG_BLANK_CONTROL = 0xcf, |
| 81 | JBT_REG_BLANK_TH_TV = 0xd0, |
| 82 | JBT_REG_CKV_ON_OFF = 0xd1, |
| 83 | JBT_REG_CKV_1_2 = 0xd2, |
| 84 | JBT_REG_OEV_TIMING = 0xd3, |
| 85 | JBT_REG_ASW_TIMING_1 = 0xd4, |
| 86 | JBT_REG_ASW_TIMING_2 = 0xd5, |
| 87 | |
| 88 | /* QVGA */ |
| 89 | JBT_REG_BLANK_CONTROL_QVGA = 0xd6, |
| 90 | JBT_REG_BLANK_TH_TV_QVGA = 0xd7, |
| 91 | JBT_REG_CKV_ON_OFF_QVGA = 0xd8, |
| 92 | JBT_REG_CKV_1_2_QVGA = 0xd9, |
| 93 | JBT_REG_OEV_TIMING_QVGA = 0xde, |
| 94 | JBT_REG_ASW_TIMING_1_QVGA = 0xdf, |
| 95 | JBT_REG_ASW_TIMING_2_QVGA = 0xe0, |
| 96 | |
| 97 | |
| 98 | JBT_REG_HCLOCK_VGA = 0xec, |
| 99 | JBT_REG_HCLOCK_QVGA = 0xed, |
| 100 | |
| 101 | }; |
| 102 | |
| 103 | enum jbt_resolution { |
| 104 | JBT_RESOLUTION_VGA, |
| 105 | JBT_RESOLUTION_QVGA, |
| 106 | }; |
| 107 | |
| 108 | enum jbt_power_mode { |
| 109 | JBT_POWER_MODE_DEEP_STANDBY, |
| 110 | JBT_POWER_MODE_SLEEP, |
| 111 | JBT_POWER_MODE_NORMAL, |
| 112 | }; |
| 113 | |
| 114 | static const char *jbt_power_mode_names[] = { |
| 115 | [JBT_POWER_MODE_DEEP_STANDBY] = "deep-standby", |
| 116 | [JBT_POWER_MODE_SLEEP] = "sleep", |
| 117 | [JBT_POWER_MODE_NORMAL] = "normal", |
| 118 | }; |
| 119 | |
| 120 | static const char *jbt_resolution_names[] = { |
| 121 | [JBT_RESOLUTION_VGA] = "vga", |
| 122 | [JBT_RESOLUTION_QVGA] = "qvga", |
| 123 | }; |
| 124 | |
| 125 | struct jbt_info { |
| 126 | struct mutex lock; /* protects this structure */ |
| 127 | enum jbt_resolution resolution; |
| 128 | enum jbt_power_mode power_mode; |
| 129 | enum jbt_power_mode suspend_mode; |
| 130 | int suspended; |
| 131 | struct spi_device *spi_dev; |
| 132 | struct lcd_device *lcd_dev; |
| 133 | unsigned long last_sleep; |
| 134 | struct delayed_work blank_work; |
| 135 | int blank_mode; |
| 136 | u16 tx_buf[4]; |
| 137 | u16 reg_cache[0xEE]; |
| 138 | }; |
| 139 | |
| 140 | #define JBT_COMMAND 0x000 |
| 141 | #define JBT_DATA 0x100 |
| 142 | |
| 143 | static int jbt_reg_write_nodata(struct jbt_info *jbt, u8 reg) |
| 144 | { |
| 145 | int rc; |
| 146 | |
| 147 | jbt->tx_buf[0] = JBT_COMMAND | reg; |
| 148 | rc = spi_write(jbt->spi_dev, (u8 *)jbt->tx_buf, |
| 149 | 1*sizeof(u16)); |
| 150 | if (rc == 0) |
| 151 | jbt->reg_cache[reg] = 0; |
| 152 | else |
| 153 | dev_err(&jbt->spi_dev->dev, "jbt_reg_write_nodata spi_write ret %d\n", |
| 154 | rc); |
| 155 | |
| 156 | return rc; |
| 157 | } |
| 158 | |
| 159 | |
| 160 | static int jbt_reg_write(struct jbt_info *jbt, u8 reg, u8 data) |
| 161 | { |
| 162 | int rc; |
| 163 | |
| 164 | jbt->tx_buf[0] = JBT_COMMAND | reg; |
| 165 | jbt->tx_buf[1] = JBT_DATA | data; |
| 166 | rc = spi_write(jbt->spi_dev, (u8 *)jbt->tx_buf, |
| 167 | 2*sizeof(u16)); |
| 168 | if (rc == 0) |
| 169 | jbt->reg_cache[reg] = data; |
| 170 | else |
| 171 | dev_err(&jbt->spi_dev->dev, "jbt_reg_write spi_write ret %d\n", rc); |
| 172 | |
| 173 | return rc; |
| 174 | } |
| 175 | |
| 176 | static int jbt_reg_write16(struct jbt_info *jbt, u8 reg, u16 data) |
| 177 | { |
| 178 | int rc; |
| 179 | |
| 180 | jbt->tx_buf[0] = JBT_COMMAND | reg; |
| 181 | jbt->tx_buf[1] = JBT_DATA | (data >> 8); |
| 182 | jbt->tx_buf[2] = JBT_DATA | (data & 0xff); |
| 183 | |
| 184 | rc = spi_write(jbt->spi_dev, (u8 *)jbt->tx_buf, |
| 185 | 3*sizeof(u16)); |
| 186 | if (rc == 0) |
| 187 | jbt->reg_cache[reg] = data; |
| 188 | else |
| 189 | dev_err(&jbt->spi_dev->dev, "jbt_reg_write16 spi_write ret %d\n", rc); |
| 190 | |
| 191 | return rc; |
| 192 | } |
| 193 | |
| 194 | static int jbt_init_regs(struct jbt_info *jbt) |
| 195 | { |
| 196 | int rc; |
| 197 | |
| 198 | dev_dbg(&jbt->spi_dev->dev, "entering %cVGA mode\n", |
| 199 | jbt->resolution == JBT_RESOLUTION_QVGA ? 'Q' : ' '); |
| 200 | |
| 201 | rc = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE1, 0x01); |
| 202 | rc |= jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE2, 0x00); |
| 203 | rc |= jbt_reg_write(jbt, JBT_REG_RGB_FORMAT, 0x60); |
| 204 | rc |= jbt_reg_write(jbt, JBT_REG_DRIVE_SYSTEM, 0x10); |
| 205 | rc |= jbt_reg_write(jbt, JBT_REG_BOOSTER_OP, 0x56); |
| 206 | rc |= jbt_reg_write(jbt, JBT_REG_BOOSTER_MODE, 0x33); |
| 207 | rc |= jbt_reg_write(jbt, JBT_REG_BOOSTER_FREQ, 0x11); |
| 208 | rc |= jbt_reg_write(jbt, JBT_REG_OPAMP_SYSCLK, 0x02); |
| 209 | rc |= jbt_reg_write(jbt, JBT_REG_VSC_VOLTAGE, 0x2b); |
| 210 | rc |= jbt_reg_write(jbt, JBT_REG_VCOM_VOLTAGE, 0x40); |
| 211 | rc |= jbt_reg_write(jbt, JBT_REG_EXT_DISPL, 0x03); |
| 212 | rc |= jbt_reg_write(jbt, JBT_REG_DCCLK_DCEV, 0x04); |
| 213 | /* |
| 214 | * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement |
| 215 | * to avoid red / blue flicker |
| 216 | */ |
| 217 | rc |= jbt_reg_write(jbt, JBT_REG_ASW_SLEW, 0x04 | (1 << 5)); |
| 218 | rc |= jbt_reg_write(jbt, JBT_REG_DUMMY_DISPLAY, 0x00); |
| 219 | |
| 220 | rc |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_A, 0x11); |
| 221 | rc |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_B, 0x11); |
| 222 | rc |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_C, 0x11); |
| 223 | rc |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040); |
| 224 | rc |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0); |
| 225 | rc |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020); |
| 226 | rc |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0); |
| 227 | |
| 228 | rc |= jbt_reg_write16(jbt, JBT_REG_GAMMA1_FINE_1, 0x5533); |
| 229 | rc |= jbt_reg_write(jbt, JBT_REG_GAMMA1_FINE_2, 0x00); |
| 230 | rc |= jbt_reg_write(jbt, JBT_REG_GAMMA1_INCLINATION, 0x00); |
| 231 | rc |= jbt_reg_write(jbt, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00); |
| 232 | |
| 233 | if (jbt->resolution != JBT_RESOLUTION_QVGA) { |
| 234 | rc |= jbt_reg_write16(jbt, JBT_REG_HCLOCK_VGA, 0x1f0); |
| 235 | rc |= jbt_reg_write(jbt, JBT_REG_BLANK_CONTROL, 0x02); |
| 236 | rc |= jbt_reg_write16(jbt, JBT_REG_BLANK_TH_TV, 0x0804); |
| 237 | |
| 238 | rc |= jbt_reg_write(jbt, JBT_REG_CKV_ON_OFF, 0x01); |
| 239 | rc |= jbt_reg_write16(jbt, JBT_REG_CKV_1_2, 0x0000); |
| 240 | |
| 241 | rc |= jbt_reg_write16(jbt, JBT_REG_OEV_TIMING, 0x0d0e); |
| 242 | rc |= jbt_reg_write16(jbt, JBT_REG_ASW_TIMING_1, 0x11a4); |
| 243 | rc |= jbt_reg_write(jbt, JBT_REG_ASW_TIMING_2, 0x0e); |
| 244 | } else { |
| 245 | rc |= jbt_reg_write16(jbt, JBT_REG_HCLOCK_QVGA, 0x00ff); |
| 246 | rc |= jbt_reg_write(jbt, JBT_REG_BLANK_CONTROL_QVGA, 0x02); |
| 247 | rc |= jbt_reg_write16(jbt, JBT_REG_BLANK_TH_TV_QVGA, 0x0804); |
| 248 | |
| 249 | rc |= jbt_reg_write(jbt, JBT_REG_CKV_ON_OFF_QVGA, 0x01); |
| 250 | rc |= jbt_reg_write16(jbt, JBT_REG_CKV_1_2_QVGA, 0x0008); |
| 251 | |
| 252 | rc |= jbt_reg_write16(jbt, JBT_REG_OEV_TIMING_QVGA, 0x050a); |
| 253 | rc |= jbt_reg_write16(jbt, JBT_REG_ASW_TIMING_1_QVGA, 0x0a19); |
| 254 | rc |= jbt_reg_write(jbt, JBT_REG_ASW_TIMING_2_QVGA, 0x0a); |
| 255 | } |
| 256 | |
| 257 | return rc ? -EIO : 0; |
| 258 | } |
| 259 | |
| 260 | static int standby_to_sleep(struct jbt_info *jbt) |
| 261 | { |
| 262 | int rc; |
| 263 | |
| 264 | /* three times command zero */ |
| 265 | rc = jbt_reg_write_nodata(jbt, 0x00); |
| 266 | mdelay(1); |
| 267 | rc |= jbt_reg_write_nodata(jbt, 0x00); |
| 268 | mdelay(1); |
| 269 | rc |= jbt_reg_write_nodata(jbt, 0x00); |
| 270 | mdelay(1); |
| 271 | |
| 272 | /* deep standby out */ |
| 273 | rc |= jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x11); |
| 274 | mdelay(1); |
| 275 | rc = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x28); |
| 276 | |
| 277 | /* (re)initialize register set */ |
| 278 | rc |= jbt_init_regs(jbt); |
| 279 | |
| 280 | return rc ? -EIO : 0; |
| 281 | } |
| 282 | |
| 283 | static int sleep_to_normal(struct jbt_info *jbt) |
| 284 | { |
| 285 | int rc; |
| 286 | |
| 287 | /* Make sure we are 120 ms after SLEEP_OUT */ |
| 288 | if (time_before(jiffies, jbt->last_sleep)) |
| 289 | mdelay(jiffies_to_msecs(jbt->last_sleep - jiffies)); |
| 290 | |
| 291 | if (jbt->resolution == JBT_RESOLUTION_VGA) { |
| 292 | /* RGB I/F on, RAM wirte off, QVGA through, SIGCON enable */ |
| 293 | rc = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x80); |
| 294 | |
| 295 | /* Quad mode off */ |
| 296 | rc |= jbt_reg_write(jbt, JBT_REG_QUAD_RATE, 0x00); |
| 297 | } else { |
| 298 | /* RGB I/F on, RAM wirte off, QVGA through, SIGCON enable */ |
| 299 | rc = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x81); |
| 300 | |
| 301 | /* Quad mode on */ |
| 302 | rc |= jbt_reg_write(jbt, JBT_REG_QUAD_RATE, 0x22); |
| 303 | } |
| 304 | |
| 305 | /* AVDD on, XVDD on */ |
| 306 | rc |= jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x16); |
| 307 | |
| 308 | /* Output control */ |
| 309 | rc |= jbt_reg_write16(jbt, JBT_REG_OUTPUT_CONTROL, 0xfff9); |
| 310 | |
| 311 | /* Turn on display */ |
| 312 | rc |= jbt_reg_write_nodata(jbt, JBT_REG_DISPLAY_ON); |
| 313 | |
| 314 | /* Sleep mode off */ |
| 315 | rc |= jbt_reg_write_nodata(jbt, JBT_REG_SLEEP_OUT); |
| 316 | jbt->last_sleep = jiffies + msecs_to_jiffies(120); |
| 317 | |
| 318 | /* Allow the booster and display controller to restart stably */ |
| 319 | mdelay(5); |
| 320 | |
| 321 | return rc ? -EIO : 0; |
| 322 | } |
| 323 | |
| 324 | static int normal_to_sleep(struct jbt_info *jbt) |
| 325 | { |
| 326 | int rc; |
| 327 | |
| 328 | /* Make sure we are 120 ms after SLEEP_OUT */ |
| 329 | while (time_before(jiffies, jbt->last_sleep)) |
| 330 | cpu_relax(); |
| 331 | |
| 332 | rc = jbt_reg_write_nodata(jbt, JBT_REG_DISPLAY_OFF); |
| 333 | rc |= jbt_reg_write16(jbt, JBT_REG_OUTPUT_CONTROL, 0x8000 | 1 << 3); |
| 334 | rc |= jbt_reg_write_nodata(jbt, JBT_REG_SLEEP_IN); |
| 335 | jbt->last_sleep = jiffies + msecs_to_jiffies(120); |
| 336 | |
| 337 | /* Allow the internal circuits to stop automatically */ |
| 338 | mdelay(5); |
| 339 | |
| 340 | return rc ? -EIO : 0; |
| 341 | } |
| 342 | |
| 343 | static int sleep_to_standby(struct jbt_info *jbt) |
| 344 | { |
| 345 | return jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x00); |
| 346 | } |
| 347 | |
| 348 | int jbt6k74_enter_power_mode(struct jbt_info *jbt, enum jbt_power_mode new_mode) |
| 349 | { |
| 350 | struct jbt6k74_platform_data *pdata = jbt->spi_dev->dev.platform_data; |
| 351 | int rc = -EINVAL; |
| 352 | |
| 353 | dev_dbg(&jbt->spi_dev->dev, "entering (old_state=%s, new_state=%s)\n", |
| 354 | jbt_power_mode_names[jbt->power_mode], |
| 355 | jbt_power_mode_names[new_mode]); |
| 356 | |
| 357 | mutex_lock(&jbt->lock); |
| 358 | |
| 359 | if (jbt->suspended) { |
| 360 | switch (new_mode) { |
| 361 | case JBT_POWER_MODE_DEEP_STANDBY: |
| 362 | case JBT_POWER_MODE_SLEEP: |
| 363 | case JBT_POWER_MODE_NORMAL: |
| 364 | rc = 0; |
| 365 | jbt->suspend_mode = new_mode; |
| 366 | break; |
| 367 | default: |
| 368 | break; |
| 369 | } |
| 370 | } else if (new_mode == JBT_POWER_MODE_NORMAL && |
| 371 | pdata->enable_pixel_clock) { |
| 372 | pdata->enable_pixel_clock(&jbt->spi_dev->dev, 1); |
| 373 | } |
| 374 | |
| 375 | switch (jbt->power_mode) { |
| 376 | case JBT_POWER_MODE_DEEP_STANDBY: |
| 377 | switch (new_mode) { |
| 378 | case JBT_POWER_MODE_DEEP_STANDBY: |
| 379 | rc = 0; |
| 380 | break; |
| 381 | case JBT_POWER_MODE_SLEEP: |
| 382 | rc = standby_to_sleep(jbt); |
| 383 | break; |
| 384 | case JBT_POWER_MODE_NORMAL: |
| 385 | /* first transition into sleep */ |
| 386 | rc = standby_to_sleep(jbt); |
| 387 | /* then transition into normal */ |
| 388 | rc |= sleep_to_normal(jbt); |
| 389 | break; |
| 390 | } |
| 391 | break; |
| 392 | case JBT_POWER_MODE_SLEEP: |
| 393 | switch (new_mode) { |
| 394 | case JBT_POWER_MODE_SLEEP: |
| 395 | rc = 0; |
| 396 | break; |
| 397 | case JBT_POWER_MODE_DEEP_STANDBY: |
| 398 | rc = sleep_to_standby(jbt); |
| 399 | break; |
| 400 | case JBT_POWER_MODE_NORMAL: |
| 401 | rc = sleep_to_normal(jbt); |
| 402 | break; |
| 403 | } |
| 404 | break; |
| 405 | case JBT_POWER_MODE_NORMAL: |
| 406 | switch (new_mode) { |
| 407 | case JBT_POWER_MODE_NORMAL: |
| 408 | rc = 0; |
| 409 | break; |
| 410 | case JBT_POWER_MODE_DEEP_STANDBY: |
| 411 | /* first transition into sleep */ |
| 412 | rc = normal_to_sleep(jbt); |
| 413 | /* then transition into deep standby */ |
| 414 | rc |= sleep_to_standby(jbt); |
| 415 | break; |
| 416 | case JBT_POWER_MODE_SLEEP: |
| 417 | rc = normal_to_sleep(jbt); |
| 418 | break; |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | if (rc == 0) { |
| 423 | jbt->power_mode = new_mode; |
| 424 | if (new_mode != JBT_POWER_MODE_NORMAL && |
| 425 | pdata->enable_pixel_clock) |
| 426 | pdata->enable_pixel_clock(&jbt->spi_dev->dev, 0); |
| 427 | } else { |
| 428 | dev_err(&jbt->spi_dev->dev, "Failed enter state '%s')\n", |
| 429 | jbt_power_mode_names[new_mode]); |
| 430 | } |
| 431 | |
| 432 | mutex_unlock(&jbt->lock); |
| 433 | |
| 434 | return rc; |
| 435 | } |
| 436 | EXPORT_SYMBOL_GPL(jbt6k74_enter_power_mode); |
| 437 | |
| 438 | int jbt6k74_set_resolution(struct jbt_info *jbt, enum jbt_resolution new_resolution) { |
| 439 | int rc = 0; |
| 440 | enum jbt_resolution old_resolution; |
| 441 | |
| 442 | if (new_resolution != JBT_RESOLUTION_VGA && |
| 443 | new_resolution != JBT_RESOLUTION_QVGA) |
| 444 | return -EINVAL; |
| 445 | |
| 446 | mutex_lock(&jbt->lock); |
| 447 | |
| 448 | if (jbt->resolution == new_resolution) |
| 449 | goto out_unlock; |
| 450 | |
| 451 | old_resolution = jbt->resolution; |
| 452 | jbt->resolution = new_resolution; |
| 453 | |
| 454 | if (jbt->power_mode == JBT_POWER_MODE_NORMAL) { |
| 455 | |
| 456 | /* first transition into sleep */ |
| 457 | rc = normal_to_sleep(jbt); |
| 458 | /* second transition into deep standby */ |
| 459 | /* rc |= sleep_to_standby(jbt);*/ |
| 460 | /* third transition into sleep */ |
| 461 | /* rc |= standby_to_sleep(jbt);*/ |
| 462 | /* fourth transition into normal */ |
| 463 | rc |= sleep_to_normal(jbt); |
| 464 | |
| 465 | if (rc) { |
| 466 | jbt->resolution = old_resolution; |
| 467 | dev_err(&jbt->spi_dev->dev, "Failed to set resolution '%s')\n", |
| 468 | jbt_resolution_names[new_resolution]); |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | out_unlock: |
| 473 | mutex_unlock(&jbt->lock); |
| 474 | |
| 475 | return rc; |
| 476 | } |
| 477 | EXPORT_SYMBOL_GPL(jbt6k74_set_resolution); |
| 478 | |
| 479 | static ssize_t resolution_read(struct device *dev, struct device_attribute *attr, |
| 480 | char *buf) |
| 481 | { |
| 482 | struct jbt_info *jbt = dev_get_drvdata(dev); |
| 483 | |
| 484 | if (jbt->resolution >= ARRAY_SIZE(jbt_resolution_names)) |
| 485 | return -EIO; |
| 486 | |
| 487 | return sprintf(buf, "%s\n", jbt_resolution_names[jbt->resolution]); |
| 488 | } |
| 489 | |
| 490 | static ssize_t resolution_write(struct device *dev, struct device_attribute *attr, |
| 491 | const char *buf, size_t count) |
| 492 | { |
| 493 | struct jbt_info *jbt = dev_get_drvdata(dev); |
| 494 | int i, rc; |
| 495 | |
| 496 | for (i = 0; i < ARRAY_SIZE(jbt_resolution_names); i++) { |
| 497 | if (!strncmp(buf, jbt_resolution_names[i], |
| 498 | strlen(jbt_resolution_names[i]))) { |
| 499 | rc = jbt6k74_set_resolution(jbt, i); |
| 500 | if (rc) |
| 501 | return rc; |
| 502 | return count; |
| 503 | } |
| 504 | } |
| 505 | |
| 506 | return -EINVAL; |
| 507 | } |
| 508 | |
| 509 | static DEVICE_ATTR(resolution, 0644, resolution_read, resolution_write); |
| 510 | |
| 511 | static int reg_by_string(const char *name) |
| 512 | { |
| 513 | if (!strcmp(name, "gamma_fine1")) |
| 514 | return JBT_REG_GAMMA1_FINE_1; |
| 515 | else if (!strcmp(name, "gamma_fine2")) |
| 516 | return JBT_REG_GAMMA1_FINE_2; |
| 517 | else if (!strcmp(name, "gamma_inclination")) |
| 518 | return JBT_REG_GAMMA1_INCLINATION; |
| 519 | else |
| 520 | return JBT_REG_GAMMA1_BLUE_OFFSET; |
| 521 | } |
| 522 | |
| 523 | static ssize_t gamma_read(struct device *dev, struct device_attribute *attr, |
| 524 | char *buf) |
| 525 | { |
| 526 | struct jbt_info *jbt = dev_get_drvdata(dev); |
| 527 | int reg = reg_by_string(attr->attr.name); |
| 528 | u16 val; |
| 529 | |
| 530 | mutex_lock(&jbt->lock); |
| 531 | val = jbt->reg_cache[reg]; |
| 532 | mutex_unlock(&jbt->lock); |
| 533 | |
| 534 | return sprintf(buf, "0x%04x\n", val); |
| 535 | } |
| 536 | |
| 537 | static ssize_t gamma_write(struct device *dev, struct device_attribute *attr, |
| 538 | const char *buf, size_t count) |
| 539 | { |
| 540 | struct jbt_info *jbt = dev_get_drvdata(dev); |
| 541 | int reg = reg_by_string(attr->attr.name); |
| 542 | unsigned long val = simple_strtoul(buf, NULL, 10); |
| 543 | |
| 544 | dev_info(dev, "writing gama %lu\n", val & 0xff); |
| 545 | |
| 546 | mutex_lock(&jbt->lock); |
| 547 | jbt_reg_write(jbt, reg, val & 0xff); |
| 548 | mutex_unlock(&jbt->lock); |
| 549 | |
| 550 | return count; |
| 551 | } |
| 552 | |
| 553 | static ssize_t reset_write(struct device *dev, struct device_attribute *attr, |
| 554 | const char *buf, size_t count) |
| 555 | { |
| 556 | int rc; |
| 557 | struct jbt_info *jbt = dev_get_drvdata(dev); |
| 558 | struct jbt6k74_platform_data *pdata = jbt->spi_dev->dev.platform_data; |
| 559 | |
| 560 | dev_info(dev, "reset\n"); |
| 561 | |
| 562 | mutex_lock(&jbt->lock); |
| 563 | |
| 564 | /* hard reset the jbt6k74 */ |
| 565 | (pdata->reset)(0, 0); |
| 566 | mdelay(1); |
| 567 | (pdata->reset)(0, 1); |
| 568 | mdelay(120); |
| 569 | |
| 570 | rc = jbt_reg_write_nodata(jbt, 0x01); |
| 571 | if (rc < 0) |
| 572 | dev_err(&jbt->spi_dev->dev, "cannot soft reset\n"); |
| 573 | mdelay(120); |
| 574 | |
| 575 | mutex_unlock(&jbt->lock); |
| 576 | |
| 577 | jbt6k74_enter_power_mode(jbt, jbt->power_mode); |
| 578 | |
| 579 | return count; |
| 580 | } |
| 581 | |
| 582 | static DEVICE_ATTR(gamma_fine1, 0644, gamma_read, gamma_write); |
| 583 | static DEVICE_ATTR(gamma_fine2, 0644, gamma_read, gamma_write); |
| 584 | static DEVICE_ATTR(gamma_inclination, 0644, gamma_read, gamma_write); |
| 585 | static DEVICE_ATTR(gamma_blue_offset, 0644, gamma_read, gamma_write); |
| 586 | static DEVICE_ATTR(reset, 0600, NULL, reset_write); |
| 587 | |
| 588 | static struct attribute *jbt_sysfs_entries[] = { |
| 589 | &dev_attr_resolution.attr, |
| 590 | &dev_attr_gamma_fine1.attr, |
| 591 | &dev_attr_gamma_fine2.attr, |
| 592 | &dev_attr_gamma_inclination.attr, |
| 593 | &dev_attr_gamma_blue_offset.attr, |
| 594 | &dev_attr_reset.attr, |
| 595 | NULL, |
| 596 | }; |
| 597 | |
| 598 | static struct attribute_group jbt_attr_group = { |
| 599 | .name = NULL, |
| 600 | .attrs = jbt_sysfs_entries, |
| 601 | }; |
| 602 | |
| 603 | /* FIXME: This in an ugly hack to delay display blanking. |
| 604 | When the jbt is in sleep mode it displays an all white screen and thus one |
| 605 | will a see a short flash. |
| 606 | By delaying the blanking we will give the backlight a chance to turn off and |
| 607 | thus avoid getting the flash */ |
| 608 | static void jbt_blank_worker(struct work_struct *work) { |
| 609 | struct jbt_info *jbt = container_of(work, struct jbt_info, |
| 610 | blank_work.work); |
| 611 | |
| 612 | switch (jbt->blank_mode) { |
| 613 | case FB_BLANK_NORMAL: |
| 614 | jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_SLEEP); |
| 615 | break; |
| 616 | case FB_BLANK_POWERDOWN: |
| 617 | jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY); |
| 618 | break; |
| 619 | default: |
| 620 | break; |
| 621 | } |
| 622 | } |
| 623 | |
| 624 | static int jbt6k74_set_mode(struct lcd_device *ld, struct fb_videomode *m) { |
| 625 | int rc = -EINVAL; |
| 626 | struct jbt_info *jbt = dev_get_drvdata(&ld->dev); |
| 627 | |
| 628 | if (m->xres == 240 && m->yres == 320) { |
| 629 | rc = jbt6k74_set_resolution(jbt, JBT_RESOLUTION_QVGA); |
| 630 | } else if (m->xres == 480 && m->yres == 640) { |
| 631 | rc = jbt6k74_set_resolution(jbt, JBT_RESOLUTION_VGA); |
| 632 | } else { |
| 633 | dev_err(&jbt->spi_dev->dev, "Unknown resolution. Entering sleep mode.\n"); |
| 634 | jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_SLEEP); |
| 635 | } |
| 636 | |
| 637 | return rc; |
| 638 | } |
| 639 | |
| 640 | static int jbt6k74_set_power(struct lcd_device *ld, int power) { |
| 641 | int rc = -EINVAL; |
| 642 | struct jbt_info *jbt = dev_get_drvdata(&ld->dev); |
| 643 | |
| 644 | jbt->blank_mode = power; |
| 645 | cancel_rearming_delayed_work(&jbt->blank_work); |
| 646 | |
| 647 | switch (power) { |
| 648 | case FB_BLANK_UNBLANK: |
| 649 | dev_dbg(&jbt->spi_dev->dev, "unblank\n"); |
| 650 | rc = jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); |
| 651 | break; |
| 652 | case FB_BLANK_NORMAL: |
| 653 | dev_dbg(&jbt->spi_dev->dev, "blank\n"); |
| 654 | rc = schedule_delayed_work(&jbt->blank_work, HZ); |
| 655 | break; |
| 656 | case FB_BLANK_POWERDOWN: |
| 657 | dev_dbg(&jbt->spi_dev->dev, "powerdown\n"); |
| 658 | rc = schedule_delayed_work(&jbt->blank_work, HZ); |
| 659 | break; |
| 660 | default: |
| 661 | break; |
| 662 | } |
| 663 | |
| 664 | return rc; |
| 665 | } |
| 666 | |
| 667 | static int jbt6k74_get_power(struct lcd_device *ld) { |
| 668 | struct jbt_info *jbt = dev_get_drvdata(&ld->dev); |
| 669 | |
| 670 | switch (jbt->power_mode) { |
| 671 | case JBT_POWER_MODE_NORMAL: |
| 672 | return FB_BLANK_UNBLANK; |
| 673 | case JBT_POWER_MODE_SLEEP: |
| 674 | return FB_BLANK_NORMAL; |
| 675 | default: |
| 676 | return JBT_POWER_MODE_DEEP_STANDBY; |
| 677 | } |
| 678 | } |
| 679 | |
| 680 | struct lcd_ops jbt6k74_lcd_ops = { |
| 681 | .set_power = jbt6k74_set_power, |
| 682 | .get_power = jbt6k74_get_power, |
| 683 | .set_mode = jbt6k74_set_mode, |
| 684 | }; |
| 685 | |
| 686 | /* linux device model infrastructure */ |
| 687 | |
| 688 | static int __devinit jbt_probe(struct spi_device *spi) |
| 689 | { |
| 690 | int rc; |
| 691 | struct jbt_info *jbt; |
| 692 | struct jbt6k74_platform_data *pdata = spi->dev.platform_data; |
| 693 | |
| 694 | /* the controller doesn't have a MISO pin; we can't do detection */ |
| 695 | |
| 696 | spi->mode = SPI_CPOL | SPI_CPHA; |
| 697 | spi->bits_per_word = 9; |
| 698 | |
| 699 | rc = spi_setup(spi); |
| 700 | if (rc < 0) { |
| 701 | dev_err(&spi->dev, |
| 702 | "error during spi_setup of jbt6k74 driver\n"); |
| 703 | return rc; |
| 704 | } |
| 705 | |
| 706 | jbt = kzalloc(sizeof(*jbt), GFP_KERNEL); |
| 707 | if (!jbt) |
| 708 | return -ENOMEM; |
| 709 | |
| 710 | jbt->spi_dev = spi; |
| 711 | |
| 712 | jbt->lcd_dev = lcd_device_register("jbt6k74-lcd", &spi->dev, |
| 713 | jbt, &jbt6k74_lcd_ops); |
| 714 | |
| 715 | if (IS_ERR(jbt->lcd_dev)) { |
| 716 | rc = PTR_ERR(jbt->lcd_dev); |
| 717 | goto err_free_drvdata; |
| 718 | } |
| 719 | |
| 720 | INIT_DELAYED_WORK(&jbt->blank_work, jbt_blank_worker); |
| 721 | |
| 722 | jbt->resolution = JBT_RESOLUTION_VGA; |
| 723 | jbt->power_mode = JBT_POWER_MODE_DEEP_STANDBY; |
| 724 | jbt->last_sleep = jiffies + msecs_to_jiffies(120); |
| 725 | mutex_init(&jbt->lock); |
| 726 | |
| 727 | dev_set_drvdata(&spi->dev, jbt); |
| 728 | |
| 729 | rc = jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); |
| 730 | if (rc < 0) { |
| 731 | dev_err(&spi->dev, "cannot enter NORMAL state\n"); |
| 732 | goto err_unregister_lcd; |
| 733 | } |
| 734 | |
| 735 | rc = sysfs_create_group(&spi->dev.kobj, &jbt_attr_group); |
| 736 | if (rc < 0) { |
| 737 | dev_err(&spi->dev, "cannot create sysfs group\n"); |
| 738 | goto err_standby; |
| 739 | } |
| 740 | |
| 741 | if (pdata->probe_completed) |
| 742 | (pdata->probe_completed)(&spi->dev); |
| 743 | |
| 744 | return 0; |
| 745 | |
| 746 | err_standby: |
| 747 | jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY); |
| 748 | err_unregister_lcd: |
| 749 | lcd_device_unregister(jbt->lcd_dev); |
| 750 | err_free_drvdata: |
| 751 | dev_set_drvdata(&spi->dev, NULL); |
| 752 | kfree(jbt); |
| 753 | |
| 754 | return rc; |
| 755 | } |
| 756 | |
| 757 | static int __devexit jbt_remove(struct spi_device *spi) |
| 758 | { |
| 759 | struct jbt_info *jbt = dev_get_drvdata(&spi->dev); |
| 760 | |
| 761 | /* We don't want to switch off the display in case the user |
| 762 | * accidentially unloads the module (whose use count normally is 0) */ |
| 763 | jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); |
| 764 | |
| 765 | sysfs_remove_group(&spi->dev.kobj, &jbt_attr_group); |
| 766 | dev_set_drvdata(&spi->dev, NULL); |
| 767 | |
| 768 | lcd_device_unregister(jbt->lcd_dev); |
| 769 | |
| 770 | kfree(jbt); |
| 771 | |
| 772 | return 0; |
| 773 | } |
| 774 | |
| 775 | #ifdef CONFIG_PM |
| 776 | static int jbt_suspend(struct spi_device *spi, pm_message_t state) |
| 777 | { |
| 778 | struct jbt_info *jbt = dev_get_drvdata(&spi->dev); |
| 779 | |
| 780 | jbt->suspend_mode = jbt->power_mode; |
| 781 | |
| 782 | jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY); |
| 783 | jbt->suspended = 1; |
| 784 | |
| 785 | dev_info(&spi->dev, "suspended\n"); |
| 786 | |
| 787 | return 0; |
| 788 | } |
| 789 | |
| 790 | int jbt6k74_resume(struct spi_device *spi) |
| 791 | { |
| 792 | struct jbt_info *jbt = dev_get_drvdata(&spi->dev); |
| 793 | |
| 794 | jbt->suspended = 0; |
| 795 | jbt6k74_enter_power_mode(jbt, jbt->suspend_mode); |
| 796 | |
| 797 | dev_info(&spi->dev, "resumed\n"); |
| 798 | |
| 799 | return 0; |
| 800 | } |
| 801 | EXPORT_SYMBOL_GPL(jbt6k74_resume); |
| 802 | |
| 803 | #else |
| 804 | #define jbt_suspend NULL |
| 805 | #define jbt6k74_resume NULL |
| 806 | #endif |
| 807 | |
| 808 | static struct spi_driver jbt6k74_driver = { |
| 809 | .driver = { |
| 810 | .name = "jbt6k74", |
| 811 | .owner = THIS_MODULE, |
| 812 | }, |
| 813 | |
| 814 | .probe = jbt_probe, |
| 815 | .remove = __devexit_p(jbt_remove), |
| 816 | .suspend = jbt_suspend, |
| 817 | .resume = jbt6k74_resume, |
| 818 | }; |
| 819 | |
| 820 | static int __init jbt_init(void) |
| 821 | { |
| 822 | return spi_register_driver(&jbt6k74_driver); |
| 823 | } |
| 824 | |
| 825 | static void __exit jbt_exit(void) |
| 826 | { |
| 827 | spi_unregister_driver(&jbt6k74_driver); |
| 828 | } |
| 829 | |
| 830 | MODULE_DESCRIPTION("SPI driver for tpo JBT6K74-AS LCM control interface"); |
| 831 | MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); |
| 832 | MODULE_LICENSE("GPL"); |
| 833 | |
| 834 | module_init(jbt_init); |
| 835 | module_exit(jbt_exit); |
| 836 | |