1 /* 2 * tdo24m - SPI-based drivers for Toppoly TDO24M series LCD panels 3 * 4 * Copyright (C) 2008 Marvell International Ltd. 5 * Eric Miao <eric.miao@marvell.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * publishhed by the Free Software Foundation. 10 */ 11 12 #include <linux/module.h> 13 #include <linux/kernel.h> 14 #include <linux/init.h> 15 #include <linux/device.h> 16 #include <linux/spi/spi.h> 17 #include <linux/fb.h> 18 #include <linux/lcd.h> 19 20 #define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL) 21 22 #define TDO24M_SPI_BUFF_SIZE (4) 23 #define MODE_QVGA 0 24 #define MODE_VGA 1 25 26 struct tdo24m { 27 struct spi_device *spi_dev; 28 struct lcd_device *lcd_dev; 29 30 struct spi_message msg; 31 struct spi_transfer xfer; 32 uint8_t *buf; 33 34 int power; 35 int mode; 36 }; 37 38 /* use bit 30, 31 as the indicator of command parameter number */ 39 #define CMD0(x) ((0 << 30) | (x)) 40 #define CMD1(x, x1) ((1 << 30) | ((x) << 9) | 0x100 | (x1)) 41 #define CMD2(x, x1, x2) ((2 << 30) | ((x) << 18) | 0x20000 |\ 42 ((x1) << 9) | 0x100 | (x2)) 43 #define CMD_NULL (-1) 44 45 static uint32_t lcd_panel_reset[] = { 46 CMD0(0x1), /* reset */ 47 CMD0(0x0), /* nop */ 48 CMD0(0x0), /* nop */ 49 CMD0(0x0), /* nop */ 50 CMD_NULL, 51 }; 52 53 static uint32_t lcd_panel_on[] = { 54 CMD0(0x29), /* Display ON */ 55 CMD2(0xB8, 0xFF, 0xF9), /* Output Control */ 56 CMD0(0x11), /* Sleep out */ 57 CMD1(0xB0, 0x16), /* Wake */ 58 CMD_NULL, 59 }; 60 61 static uint32_t lcd_panel_off[] = { 62 CMD0(0x28), /* Display OFF */ 63 CMD2(0xB8, 0x80, 0x02), /* Output Control */ 64 CMD0(0x10), /* Sleep in */ 65 CMD1(0xB0, 0x00), /* Deep stand by in */ 66 CMD_NULL, 67 }; 68 69 static uint32_t lcd_vga_pass_through[] = { 70 CMD1(0xB0, 0x16), 71 CMD1(0xBC, 0x80), 72 CMD1(0xE1, 0x00), 73 CMD1(0x36, 0x50), 74 CMD1(0x3B, 0x00), 75 CMD_NULL, 76 }; 77 78 static uint32_t lcd_qvga_pass_through[] = { 79 CMD1(0xB0, 0x16), 80 CMD1(0xBC, 0x81), 81 CMD1(0xE1, 0x00), 82 CMD1(0x36, 0x50), 83 CMD1(0x3B, 0x22), 84 CMD_NULL, 85 }; 86 87 static uint32_t lcd_vga_transfer[] = { 88 CMD1(0xcf, 0x02), /* Blanking period control (1) */ 89 CMD2(0xd0, 0x08, 0x04), /* Blanking period control (2) */ 90 CMD1(0xd1, 0x01), /* CKV timing control on/off */ 91 CMD2(0xd2, 0x14, 0x00), /* CKV 1,2 timing control */ 92 CMD2(0xd3, 0x1a, 0x0f), /* OEV timing control */ 93 CMD2(0xd4, 0x1f, 0xaf), /* ASW timing control (1) */ 94 CMD1(0xd5, 0x14), /* ASW timing control (2) */ 95 CMD0(0x21), /* Invert for normally black display */ 96 CMD0(0x29), /* Display on */ 97 CMD_NULL, 98 }; 99 100 static uint32_t lcd_qvga_transfer[] = { 101 CMD1(0xd6, 0x02), /* Blanking period control (1) */ 102 CMD2(0xd7, 0x08, 0x04), /* Blanking period control (2) */ 103 CMD1(0xd8, 0x01), /* CKV timing control on/off */ 104 CMD2(0xd9, 0x00, 0x08), /* CKV 1,2 timing control */ 105 CMD2(0xde, 0x05, 0x0a), /* OEV timing control */ 106 CMD2(0xdf, 0x0a, 0x19), /* ASW timing control (1) */ 107 CMD1(0xe0, 0x0a), /* ASW timing control (2) */ 108 CMD0(0x21), /* Invert for normally black display */ 109 CMD0(0x29), /* Display on */ 110 CMD_NULL, 111 }; 112 113 static uint32_t lcd_panel_config[] = { 114 CMD2(0xb8, 0xff, 0xf9), /* Output control */ 115 CMD0(0x11), /* sleep out */ 116 CMD1(0xba, 0x01), /* Display mode (1) */ 117 CMD1(0xbb, 0x00), /* Display mode (2) */ 118 CMD1(0x3a, 0x60), /* Display mode 18-bit RGB */ 119 CMD1(0xbf, 0x10), /* Drive system change control */ 120 CMD1(0xb1, 0x56), /* Booster operation setup */ 121 CMD1(0xb2, 0x33), /* Booster mode setup */ 122 CMD1(0xb3, 0x11), /* Booster frequency setup */ 123 CMD1(0xb4, 0x02), /* Op amp/system clock */ 124 CMD1(0xb5, 0x35), /* VCS voltage */ 125 CMD1(0xb6, 0x40), /* VCOM voltage */ 126 CMD1(0xb7, 0x03), /* External display signal */ 127 CMD1(0xbd, 0x00), /* ASW slew rate */ 128 CMD1(0xbe, 0x00), /* Dummy data for QuadData operation */ 129 CMD1(0xc0, 0x11), /* Sleep out FR count (A) */ 130 CMD1(0xc1, 0x11), /* Sleep out FR count (B) */ 131 CMD1(0xc2, 0x11), /* Sleep out FR count (C) */ 132 CMD2(0xc3, 0x20, 0x40), /* Sleep out FR count (D) */ 133 CMD2(0xc4, 0x60, 0xc0), /* Sleep out FR count (E) */ 134 CMD2(0xc5, 0x10, 0x20), /* Sleep out FR count (F) */ 135 CMD1(0xc6, 0xc0), /* Sleep out FR count (G) */ 136 CMD2(0xc7, 0x33, 0x43), /* Gamma 1 fine tuning (1) */ 137 CMD1(0xc8, 0x44), /* Gamma 1 fine tuning (2) */ 138 CMD1(0xc9, 0x33), /* Gamma 1 inclination adjustment */ 139 CMD1(0xca, 0x00), /* Gamma 1 blue offset adjustment */ 140 CMD2(0xec, 0x01, 0xf0), /* Horizontal clock cycles */ 141 CMD_NULL, 142 }; 143 144 static int tdo24m_writes(struct tdo24m *lcd, uint32_t *array) 145 { 146 struct spi_transfer *x = &lcd->xfer; 147 uint32_t data, *p = array; 148 int nparams, err = 0; 149 150 for (; *p != CMD_NULL; p++) { 151 152 nparams = (*p >> 30) & 0x3; 153 154 data = *p << (7 - nparams); 155 switch (nparams) { 156 case 0: 157 lcd->buf[0] = (data >> 8) & 0xff; 158 lcd->buf[1] = data & 0xff; 159 break; 160 case 1: 161 lcd->buf[0] = (data >> 16) & 0xff; 162 lcd->buf[1] = (data >> 8) & 0xff; 163 lcd->buf[2] = data & 0xff; 164 break; 165 case 2: 166 lcd->buf[0] = (data >> 24) & 0xff; 167 lcd->buf[1] = (data >> 16) & 0xff; 168 lcd->buf[2] = (data >> 8) & 0xff; 169 lcd->buf[3] = data & 0xff; 170 break; 171 default: 172 continue; 173 } 174 x->len = nparams + 2; 175 err = spi_sync(lcd->spi_dev, &lcd->msg); 176 if (err) 177 break; 178 } 179 180 return err; 181 } 182 183 static int tdo24m_adj_mode(struct tdo24m *lcd, int mode) 184 { 185 switch (mode) { 186 case MODE_VGA: 187 tdo24m_writes(lcd, lcd_vga_pass_through); 188 tdo24m_writes(lcd, lcd_panel_config); 189 tdo24m_writes(lcd, lcd_vga_transfer); 190 break; 191 case MODE_QVGA: 192 tdo24m_writes(lcd, lcd_qvga_pass_through); 193 tdo24m_writes(lcd, lcd_panel_config); 194 tdo24m_writes(lcd, lcd_qvga_transfer); 195 break; 196 default: 197 return -EINVAL; 198 } 199 200 lcd->mode = mode; 201 return 0; 202 } 203 204 static int tdo24m_power_on(struct tdo24m *lcd) 205 { 206 int err; 207 208 err = tdo24m_writes(lcd, lcd_panel_on); 209 if (err) 210 goto out; 211 212 err = tdo24m_writes(lcd, lcd_panel_reset); 213 if (err) 214 goto out; 215 216 err = tdo24m_adj_mode(lcd, lcd->mode); 217 out: 218 return err; 219 } 220 221 static int tdo24m_power_off(struct tdo24m *lcd) 222 { 223 return tdo24m_writes(lcd, lcd_panel_off); 224 } 225 226 static int tdo24m_power(struct tdo24m *lcd, int power) 227 { 228 int ret = 0; 229 230 if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power)) 231 ret = tdo24m_power_on(lcd); 232 else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power)) 233 ret = tdo24m_power_off(lcd); 234 235 if (!ret) 236 lcd->power = power; 237 238 return ret; 239 } 240 241 242 static int tdo24m_set_power(struct lcd_device *ld, int power) 243 { 244 struct tdo24m *lcd = lcd_get_data(ld); 245 return tdo24m_power(lcd, power); 246 } 247 248 static int tdo24m_get_power(struct lcd_device *ld) 249 { 250 struct tdo24m *lcd = lcd_get_data(ld); 251 return lcd->power; 252 } 253 254 static int tdo24m_set_mode(struct lcd_device *ld, struct fb_videomode *m) 255 { 256 struct tdo24m *lcd = lcd_get_data(ld); 257 int mode = MODE_QVGA; 258 259 if (m->xres == 640 || m->xres == 480) 260 mode = MODE_VGA; 261 262 if (lcd->mode == mode) 263 return 0; 264 265 return tdo24m_adj_mode(lcd, mode); 266 } 267 268 static struct lcd_ops tdo24m_ops = { 269 .get_power = tdo24m_get_power, 270 .set_power = tdo24m_set_power, 271 .set_mode = tdo24m_set_mode, 272 }; 273 274 static int __devinit tdo24m_probe(struct spi_device *spi) 275 { 276 struct tdo24m *lcd; 277 struct spi_message *m; 278 struct spi_transfer *x; 279 int err; 280 281 spi->bits_per_word = 8; 282 spi->mode = SPI_MODE_3; 283 err = spi_setup(spi); 284 if (err) 285 return err; 286 287 lcd = kzalloc(sizeof(struct tdo24m), GFP_KERNEL); 288 if (!lcd) 289 return -ENOMEM; 290 291 lcd->spi_dev = spi; 292 lcd->power = FB_BLANK_POWERDOWN; 293 lcd->mode = MODE_VGA; /* default to VGA */ 294 295 lcd->buf = kmalloc(TDO24M_SPI_BUFF_SIZE, sizeof(GFP_KERNEL)); 296 if (lcd->buf == NULL) { 297 kfree(lcd); 298 return -ENOMEM; 299 } 300 301 m = &lcd->msg; 302 x = &lcd->xfer; 303 304 spi_message_init(m); 305 306 x->tx_buf = &lcd->buf[0]; 307 spi_message_add_tail(x, m); 308 309 lcd->lcd_dev = lcd_device_register("tdo24m", &spi->dev, 310 lcd, &tdo24m_ops); 311 if (IS_ERR(lcd->lcd_dev)) { 312 err = PTR_ERR(lcd->lcd_dev); 313 goto out_free; 314 } 315 316 dev_set_drvdata(&spi->dev, lcd); 317 err = tdo24m_power(lcd, FB_BLANK_UNBLANK); 318 if (err) 319 goto out_unregister; 320 321 return 0; 322 323 out_unregister: 324 lcd_device_unregister(lcd->lcd_dev); 325 out_free: 326 kfree(lcd->buf); 327 kfree(lcd); 328 return err; 329 } 330 331 static int __devexit tdo24m_remove(struct spi_device *spi) 332 { 333 struct tdo24m *lcd = dev_get_drvdata(&spi->dev); 334 335 tdo24m_power(lcd, FB_BLANK_POWERDOWN); 336 lcd_device_unregister(lcd->lcd_dev); 337 kfree(lcd->buf); 338 kfree(lcd); 339 340 return 0; 341 } 342 343 #ifdef CONFIG_PM 344 static int tdo24m_suspend(struct spi_device *spi, pm_message_t state) 345 { 346 struct tdo24m *lcd = dev_get_drvdata(&spi->dev); 347 348 return tdo24m_power(lcd, FB_BLANK_POWERDOWN); 349 } 350 351 static int tdo24m_resume(struct spi_device *spi) 352 { 353 struct tdo24m *lcd = dev_get_drvdata(&spi->dev); 354 355 return tdo24m_power(lcd, FB_BLANK_UNBLANK); 356 } 357 #else 358 #define tdo24m_suspend NULL 359 #define tdo24m_resume NULL 360 #endif 361 362 /* Power down all displays on reboot, poweroff or halt */ 363 static void tdo24m_shutdown(struct spi_device *spi) 364 { 365 struct tdo24m *lcd = dev_get_drvdata(&spi->dev); 366 367 tdo24m_power(lcd, FB_BLANK_POWERDOWN); 368 } 369 370 static struct spi_driver tdo24m_driver = { 371 .driver = { 372 .name = "tdo24m", 373 .owner = THIS_MODULE, 374 }, 375 .probe = tdo24m_probe, 376 .remove = __devexit_p(tdo24m_remove), 377 .shutdown = tdo24m_shutdown, 378 .suspend = tdo24m_suspend, 379 .resume = tdo24m_resume, 380 }; 381 382 static int __init tdo24m_init(void) 383 { 384 return spi_register_driver(&tdo24m_driver); 385 } 386 module_init(tdo24m_init); 387 388 static void __exit tdo24m_exit(void) 389 { 390 spi_unregister_driver(&tdo24m_driver); 391 } 392 module_exit(tdo24m_exit); 393 394 MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"); 395 MODULE_DESCRIPTION("Driver for Toppoly TDO24M LCD Panel"); 396 MODULE_LICENSE("GPL"); 397