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