| Eric Miao | dd89ccb | 2008-09-01 06:50:23 +0800 | [diff] [blame] | 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"); |