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, 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");
475
MODULE_ALIAS("spi:tdo24m");