diff options
author | Jingoo Han <jg1.han@samsung.com> | 2013-02-21 19:43:14 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-02-21 20:22:21 -0500 |
commit | 1be9ca2579aa901a86cfd353e10814a2dcfb15fe (patch) | |
tree | 8e556db407460cf01732cd9d0358b28f8a64b24c /drivers/video/backlight | |
parent | 551450bb586d56cb995e6c75ae0f021bd353e983 (diff) |
backlight: add lms501kf03 LCD driver
Add the lms501kf03 LCD panel driver. The lms501kf03 LCD panel (800 x 480)
driver uses 3-wired SPI inteface.
[akpm@linux-foundation.org: remove unused variable `before_power']
[akpm@linux-foundation.org: make lms501kf03_shutdown() static, per Fengguang]
Signed-off-by: Ilho Lee <Ilho215.lee@samsung.com>
Signed-off-by: Jingoo Han <jg1.han@samsung.com>
Cc: "devendra.aaru" <devendra.aaru@gmail.com>
Cc: Sachin Kamat <sachin.kamat@linaro.org>
Cc: Wu Fengguang <fengguang.wu@intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/video/backlight')
-rw-r--r-- | drivers/video/backlight/Kconfig | 8 | ||||
-rw-r--r-- | drivers/video/backlight/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/backlight/lms501kf03.c | 441 |
3 files changed, 450 insertions, 0 deletions
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 765a945f8ea1..081d6cf02476 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig | |||
@@ -126,6 +126,14 @@ config LCD_AMS369FG06 | |||
126 | If you have an AMS369FG06 AMOLED Panel, say Y to enable its | 126 | If you have an AMS369FG06 AMOLED Panel, say Y to enable its |
127 | LCD control driver. | 127 | LCD control driver. |
128 | 128 | ||
129 | config LCD_LMS501KF03 | ||
130 | tristate "LMS501KF03 LCD Driver" | ||
131 | depends on SPI | ||
132 | default n | ||
133 | help | ||
134 | If you have an LMS501KF03 LCD Panel, say Y to enable its | ||
135 | LCD control driver. | ||
136 | |||
129 | endif # LCD_CLASS_DEVICE | 137 | endif # LCD_CLASS_DEVICE |
130 | 138 | ||
131 | # | 139 | # |
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index e7ce7291635d..d02a7284dd27 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile | |||
@@ -14,6 +14,7 @@ obj-$(CONFIG_LCD_TOSA) += tosa_lcd.o | |||
14 | obj-$(CONFIG_LCD_S6E63M0) += s6e63m0.o | 14 | obj-$(CONFIG_LCD_S6E63M0) += s6e63m0.o |
15 | obj-$(CONFIG_LCD_LD9040) += ld9040.o | 15 | obj-$(CONFIG_LCD_LD9040) += ld9040.o |
16 | obj-$(CONFIG_LCD_AMS369FG06) += ams369fg06.o | 16 | obj-$(CONFIG_LCD_AMS369FG06) += ams369fg06.o |
17 | obj-$(CONFIG_LCD_LMS501KF03) += lms501kf03.o | ||
17 | 18 | ||
18 | obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o | 19 | obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o |
19 | obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o | 20 | obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o |
diff --git a/drivers/video/backlight/lms501kf03.c b/drivers/video/backlight/lms501kf03.c new file mode 100644 index 000000000000..43865ba89d37 --- /dev/null +++ b/drivers/video/backlight/lms501kf03.c | |||
@@ -0,0 +1,441 @@ | |||
1 | /* | ||
2 | * lms501kf03 TFT LCD panel driver. | ||
3 | * | ||
4 | * Copyright (c) 2012 Samsung Electronics Co., Ltd. | ||
5 | * Author: Jingoo Han <jg1.han@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms of the GNU General Public License as published by the | ||
9 | * Free Software Foundation; either version 2 of the License, or (at your | ||
10 | * option) any later version. | ||
11 | */ | ||
12 | |||
13 | #include <linux/backlight.h> | ||
14 | #include <linux/delay.h> | ||
15 | #include <linux/fb.h> | ||
16 | #include <linux/gpio.h> | ||
17 | #include <linux/lcd.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/spi/spi.h> | ||
20 | #include <linux/wait.h> | ||
21 | |||
22 | #define COMMAND_ONLY 0x00 | ||
23 | #define DATA_ONLY 0x01 | ||
24 | |||
25 | struct lms501kf03 { | ||
26 | struct device *dev; | ||
27 | struct spi_device *spi; | ||
28 | unsigned int power; | ||
29 | struct lcd_device *ld; | ||
30 | struct lcd_platform_data *lcd_pd; | ||
31 | }; | ||
32 | |||
33 | static const unsigned char seq_password[] = { | ||
34 | 0xb9, 0xff, 0x83, 0x69, | ||
35 | }; | ||
36 | |||
37 | static const unsigned char seq_power[] = { | ||
38 | 0xb1, 0x01, 0x00, 0x34, 0x06, 0x00, 0x14, 0x14, 0x20, 0x28, | ||
39 | 0x12, 0x12, 0x17, 0x0a, 0x01, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, | ||
40 | }; | ||
41 | |||
42 | static const unsigned char seq_display[] = { | ||
43 | 0xb2, 0x00, 0x2b, 0x03, 0x03, 0x70, 0x00, 0xff, 0x00, 0x00, | ||
44 | 0x00, 0x00, 0x03, 0x03, 0x00, 0x01, | ||
45 | }; | ||
46 | |||
47 | static const unsigned char seq_rgb_if[] = { | ||
48 | 0xb3, 0x09, | ||
49 | }; | ||
50 | |||
51 | static const unsigned char seq_display_inv[] = { | ||
52 | 0xb4, 0x01, 0x08, 0x77, 0x0e, 0x06, | ||
53 | }; | ||
54 | |||
55 | static const unsigned char seq_vcom[] = { | ||
56 | 0xb6, 0x4c, 0x2e, | ||
57 | }; | ||
58 | |||
59 | static const unsigned char seq_gate[] = { | ||
60 | 0xd5, 0x00, 0x05, 0x03, 0x29, 0x01, 0x07, 0x17, 0x68, 0x13, | ||
61 | 0x37, 0x20, 0x31, 0x8a, 0x46, 0x9b, 0x57, 0x13, 0x02, 0x75, | ||
62 | 0xb9, 0x64, 0xa8, 0x07, 0x0f, 0x04, 0x07, | ||
63 | }; | ||
64 | |||
65 | static const unsigned char seq_panel[] = { | ||
66 | 0xcc, 0x02, | ||
67 | }; | ||
68 | |||
69 | static const unsigned char seq_col_mod[] = { | ||
70 | 0x3a, 0x77, | ||
71 | }; | ||
72 | |||
73 | static const unsigned char seq_w_gamma[] = { | ||
74 | 0xe0, 0x00, 0x04, 0x09, 0x0f, 0x1f, 0x3f, 0x1f, 0x2f, 0x0a, | ||
75 | 0x0f, 0x10, 0x16, 0x18, 0x16, 0x17, 0x0d, 0x15, 0x00, 0x04, | ||
76 | 0x09, 0x0f, 0x38, 0x3f, 0x20, 0x39, 0x0a, 0x0f, 0x10, 0x16, | ||
77 | 0x18, 0x16, 0x17, 0x0d, 0x15, | ||
78 | }; | ||
79 | |||
80 | static const unsigned char seq_rgb_gamma[] = { | ||
81 | 0xc1, 0x01, 0x03, 0x07, 0x0f, 0x1a, 0x22, 0x2c, 0x33, 0x3c, | ||
82 | 0x46, 0x4f, 0x58, 0x60, 0x69, 0x71, 0x79, 0x82, 0x89, 0x92, | ||
83 | 0x9a, 0xa1, 0xa9, 0xb1, 0xb9, 0xc1, 0xc9, 0xcf, 0xd6, 0xde, | ||
84 | 0xe5, 0xec, 0xf3, 0xf9, 0xff, 0xdd, 0x39, 0x07, 0x1c, 0xcb, | ||
85 | 0xab, 0x5f, 0x49, 0x80, 0x03, 0x07, 0x0f, 0x19, 0x20, 0x2a, | ||
86 | 0x31, 0x39, 0x42, 0x4b, 0x53, 0x5b, 0x63, 0x6b, 0x73, 0x7b, | ||
87 | 0x83, 0x8a, 0x92, 0x9b, 0xa2, 0xaa, 0xb2, 0xba, 0xc2, 0xca, | ||
88 | 0xd0, 0xd8, 0xe1, 0xe8, 0xf0, 0xf8, 0xff, 0xf7, 0xd8, 0xbe, | ||
89 | 0xa7, 0x39, 0x40, 0x85, 0x8c, 0xc0, 0x04, 0x07, 0x0c, 0x17, | ||
90 | 0x1c, 0x23, 0x2b, 0x34, 0x3b, 0x43, 0x4c, 0x54, 0x5b, 0x63, | ||
91 | 0x6a, 0x73, 0x7a, 0x82, 0x8a, 0x91, 0x98, 0xa1, 0xa8, 0xb0, | ||
92 | 0xb7, 0xc1, 0xc9, 0xcf, 0xd9, 0xe3, 0xea, 0xf4, 0xff, 0x00, | ||
93 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
94 | }; | ||
95 | |||
96 | static const unsigned char seq_up_dn[] = { | ||
97 | 0x36, 0x10, | ||
98 | }; | ||
99 | |||
100 | static const unsigned char seq_sleep_in[] = { | ||
101 | 0x10, | ||
102 | }; | ||
103 | |||
104 | static const unsigned char seq_sleep_out[] = { | ||
105 | 0x11, | ||
106 | }; | ||
107 | |||
108 | static const unsigned char seq_display_on[] = { | ||
109 | 0x29, | ||
110 | }; | ||
111 | |||
112 | static const unsigned char seq_display_off[] = { | ||
113 | 0x10, | ||
114 | }; | ||
115 | |||
116 | static int lms501kf03_spi_write_byte(struct lms501kf03 *lcd, int addr, int data) | ||
117 | { | ||
118 | u16 buf[1]; | ||
119 | struct spi_message msg; | ||
120 | |||
121 | struct spi_transfer xfer = { | ||
122 | .len = 2, | ||
123 | .tx_buf = buf, | ||
124 | }; | ||
125 | |||
126 | buf[0] = (addr << 8) | data; | ||
127 | |||
128 | spi_message_init(&msg); | ||
129 | spi_message_add_tail(&xfer, &msg); | ||
130 | |||
131 | return spi_sync(lcd->spi, &msg); | ||
132 | } | ||
133 | |||
134 | static int lms501kf03_spi_write(struct lms501kf03 *lcd, unsigned char address, | ||
135 | unsigned char command) | ||
136 | { | ||
137 | return lms501kf03_spi_write_byte(lcd, address, command); | ||
138 | } | ||
139 | |||
140 | static int lms501kf03_panel_send_sequence(struct lms501kf03 *lcd, | ||
141 | const unsigned char *wbuf, | ||
142 | unsigned int len) | ||
143 | { | ||
144 | int ret = 0, i = 0; | ||
145 | |||
146 | while (i < len) { | ||
147 | if (i == 0) | ||
148 | ret = lms501kf03_spi_write(lcd, COMMAND_ONLY, wbuf[i]); | ||
149 | else | ||
150 | ret = lms501kf03_spi_write(lcd, DATA_ONLY, wbuf[i]); | ||
151 | if (ret) | ||
152 | break; | ||
153 | i += 1; | ||
154 | } | ||
155 | |||
156 | return ret; | ||
157 | } | ||
158 | |||
159 | static int lms501kf03_ldi_init(struct lms501kf03 *lcd) | ||
160 | { | ||
161 | int ret, i; | ||
162 | static const unsigned char *init_seq[] = { | ||
163 | seq_password, | ||
164 | seq_power, | ||
165 | seq_display, | ||
166 | seq_rgb_if, | ||
167 | seq_display_inv, | ||
168 | seq_vcom, | ||
169 | seq_gate, | ||
170 | seq_panel, | ||
171 | seq_col_mod, | ||
172 | seq_w_gamma, | ||
173 | seq_rgb_gamma, | ||
174 | seq_sleep_out, | ||
175 | }; | ||
176 | |||
177 | static const unsigned int size_seq[] = { | ||
178 | ARRAY_SIZE(seq_password), | ||
179 | ARRAY_SIZE(seq_power), | ||
180 | ARRAY_SIZE(seq_display), | ||
181 | ARRAY_SIZE(seq_rgb_if), | ||
182 | ARRAY_SIZE(seq_display_inv), | ||
183 | ARRAY_SIZE(seq_vcom), | ||
184 | ARRAY_SIZE(seq_gate), | ||
185 | ARRAY_SIZE(seq_panel), | ||
186 | ARRAY_SIZE(seq_col_mod), | ||
187 | ARRAY_SIZE(seq_w_gamma), | ||
188 | ARRAY_SIZE(seq_rgb_gamma), | ||
189 | ARRAY_SIZE(seq_sleep_out), | ||
190 | }; | ||
191 | |||
192 | for (i = 0; i < ARRAY_SIZE(init_seq); i++) { | ||
193 | ret = lms501kf03_panel_send_sequence(lcd, init_seq[i], | ||
194 | size_seq[i]); | ||
195 | if (ret) | ||
196 | break; | ||
197 | } | ||
198 | /* | ||
199 | * According to the datasheet, 120ms delay time is required. | ||
200 | * After sleep out sequence, command is blocked for 120ms. | ||
201 | * Thus, LDI should wait for 120ms. | ||
202 | */ | ||
203 | msleep(120); | ||
204 | |||
205 | return ret; | ||
206 | } | ||
207 | |||
208 | static int lms501kf03_ldi_enable(struct lms501kf03 *lcd) | ||
209 | { | ||
210 | return lms501kf03_panel_send_sequence(lcd, seq_display_on, | ||
211 | ARRAY_SIZE(seq_display_on)); | ||
212 | } | ||
213 | |||
214 | static int lms501kf03_ldi_disable(struct lms501kf03 *lcd) | ||
215 | { | ||
216 | return lms501kf03_panel_send_sequence(lcd, seq_display_off, | ||
217 | ARRAY_SIZE(seq_display_off)); | ||
218 | } | ||
219 | |||
220 | static int lms501kf03_power_is_on(int power) | ||
221 | { | ||
222 | return (power) <= FB_BLANK_NORMAL; | ||
223 | } | ||
224 | |||
225 | static int lms501kf03_power_on(struct lms501kf03 *lcd) | ||
226 | { | ||
227 | int ret = 0; | ||
228 | struct lcd_platform_data *pd; | ||
229 | |||
230 | pd = lcd->lcd_pd; | ||
231 | |||
232 | if (!pd->power_on) { | ||
233 | dev_err(lcd->dev, "power_on is NULL.\n"); | ||
234 | return -EINVAL; | ||
235 | } else { | ||
236 | pd->power_on(lcd->ld, 1); | ||
237 | msleep(pd->power_on_delay); | ||
238 | } | ||
239 | |||
240 | if (!pd->reset) { | ||
241 | dev_err(lcd->dev, "reset is NULL.\n"); | ||
242 | return -EINVAL; | ||
243 | } else { | ||
244 | pd->reset(lcd->ld); | ||
245 | msleep(pd->reset_delay); | ||
246 | } | ||
247 | |||
248 | ret = lms501kf03_ldi_init(lcd); | ||
249 | if (ret) { | ||
250 | dev_err(lcd->dev, "failed to initialize ldi.\n"); | ||
251 | return ret; | ||
252 | } | ||
253 | |||
254 | ret = lms501kf03_ldi_enable(lcd); | ||
255 | if (ret) { | ||
256 | dev_err(lcd->dev, "failed to enable ldi.\n"); | ||
257 | return ret; | ||
258 | } | ||
259 | |||
260 | return 0; | ||
261 | } | ||
262 | |||
263 | static int lms501kf03_power_off(struct lms501kf03 *lcd) | ||
264 | { | ||
265 | int ret = 0; | ||
266 | struct lcd_platform_data *pd; | ||
267 | |||
268 | pd = lcd->lcd_pd; | ||
269 | |||
270 | ret = lms501kf03_ldi_disable(lcd); | ||
271 | if (ret) { | ||
272 | dev_err(lcd->dev, "lcd setting failed.\n"); | ||
273 | return -EIO; | ||
274 | } | ||
275 | |||
276 | msleep(pd->power_off_delay); | ||
277 | |||
278 | pd->power_on(lcd->ld, 0); | ||
279 | |||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | static int lms501kf03_power(struct lms501kf03 *lcd, int power) | ||
284 | { | ||
285 | int ret = 0; | ||
286 | |||
287 | if (lms501kf03_power_is_on(power) && | ||
288 | !lms501kf03_power_is_on(lcd->power)) | ||
289 | ret = lms501kf03_power_on(lcd); | ||
290 | else if (!lms501kf03_power_is_on(power) && | ||
291 | lms501kf03_power_is_on(lcd->power)) | ||
292 | ret = lms501kf03_power_off(lcd); | ||
293 | |||
294 | if (!ret) | ||
295 | lcd->power = power; | ||
296 | |||
297 | return ret; | ||
298 | } | ||
299 | |||
300 | static int lms501kf03_get_power(struct lcd_device *ld) | ||
301 | { | ||
302 | struct lms501kf03 *lcd = lcd_get_data(ld); | ||
303 | |||
304 | return lcd->power; | ||
305 | } | ||
306 | |||
307 | static int lms501kf03_set_power(struct lcd_device *ld, int power) | ||
308 | { | ||
309 | struct lms501kf03 *lcd = lcd_get_data(ld); | ||
310 | |||
311 | if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && | ||
312 | power != FB_BLANK_NORMAL) { | ||
313 | dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); | ||
314 | return -EINVAL; | ||
315 | } | ||
316 | |||
317 | return lms501kf03_power(lcd, power); | ||
318 | } | ||
319 | |||
320 | static struct lcd_ops lms501kf03_lcd_ops = { | ||
321 | .get_power = lms501kf03_get_power, | ||
322 | .set_power = lms501kf03_set_power, | ||
323 | }; | ||
324 | |||
325 | static int lms501kf03_probe(struct spi_device *spi) | ||
326 | { | ||
327 | struct lms501kf03 *lcd = NULL; | ||
328 | struct lcd_device *ld = NULL; | ||
329 | int ret = 0; | ||
330 | |||
331 | lcd = devm_kzalloc(&spi->dev, sizeof(struct lms501kf03), GFP_KERNEL); | ||
332 | if (!lcd) | ||
333 | return -ENOMEM; | ||
334 | |||
335 | /* lms501kf03 lcd panel uses 3-wire 9-bit SPI Mode. */ | ||
336 | spi->bits_per_word = 9; | ||
337 | |||
338 | ret = spi_setup(spi); | ||
339 | if (ret < 0) { | ||
340 | dev_err(&spi->dev, "spi setup failed.\n"); | ||
341 | return ret; | ||
342 | } | ||
343 | |||
344 | lcd->spi = spi; | ||
345 | lcd->dev = &spi->dev; | ||
346 | |||
347 | lcd->lcd_pd = spi->dev.platform_data; | ||
348 | if (!lcd->lcd_pd) { | ||
349 | dev_err(&spi->dev, "platform data is NULL\n"); | ||
350 | return -EINVAL; | ||
351 | } | ||
352 | |||
353 | ld = lcd_device_register("lms501kf03", &spi->dev, lcd, | ||
354 | &lms501kf03_lcd_ops); | ||
355 | if (IS_ERR(ld)) | ||
356 | return PTR_ERR(ld); | ||
357 | |||
358 | lcd->ld = ld; | ||
359 | |||
360 | if (!lcd->lcd_pd->lcd_enabled) { | ||
361 | /* | ||
362 | * if lcd panel was off from bootloader then | ||
363 | * current lcd status is powerdown and then | ||
364 | * it enables lcd panel. | ||
365 | */ | ||
366 | lcd->power = FB_BLANK_POWERDOWN; | ||
367 | |||
368 | lms501kf03_power(lcd, FB_BLANK_UNBLANK); | ||
369 | } else { | ||
370 | lcd->power = FB_BLANK_UNBLANK; | ||
371 | } | ||
372 | |||
373 | dev_set_drvdata(&spi->dev, lcd); | ||
374 | |||
375 | dev_info(&spi->dev, "lms501kf03 panel driver has been probed.\n"); | ||
376 | |||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | static int lms501kf03_remove(struct spi_device *spi) | ||
381 | { | ||
382 | struct lms501kf03 *lcd = dev_get_drvdata(&spi->dev); | ||
383 | |||
384 | lms501kf03_power(lcd, FB_BLANK_POWERDOWN); | ||
385 | lcd_device_unregister(lcd->ld); | ||
386 | |||
387 | return 0; | ||
388 | } | ||
389 | |||
390 | #if defined(CONFIG_PM) | ||
391 | |||
392 | static int lms501kf03_suspend(struct spi_device *spi, pm_message_t mesg) | ||
393 | { | ||
394 | struct lms501kf03 *lcd = dev_get_drvdata(&spi->dev); | ||
395 | |||
396 | dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power); | ||
397 | |||
398 | /* | ||
399 | * when lcd panel is suspend, lcd panel becomes off | ||
400 | * regardless of status. | ||
401 | */ | ||
402 | return lms501kf03_power(lcd, FB_BLANK_POWERDOWN); | ||
403 | } | ||
404 | |||
405 | static int lms501kf03_resume(struct spi_device *spi) | ||
406 | { | ||
407 | struct lms501kf03 *lcd = dev_get_drvdata(&spi->dev); | ||
408 | |||
409 | lcd->power = FB_BLANK_POWERDOWN; | ||
410 | |||
411 | return lms501kf03_power(lcd, FB_BLANK_UNBLANK); | ||
412 | } | ||
413 | #else | ||
414 | #define lms501kf03_suspend NULL | ||
415 | #define lms501kf03_resume NULL | ||
416 | #endif | ||
417 | |||
418 | static void lms501kf03_shutdown(struct spi_device *spi) | ||
419 | { | ||
420 | struct lms501kf03 *lcd = dev_get_drvdata(&spi->dev); | ||
421 | |||
422 | lms501kf03_power(lcd, FB_BLANK_POWERDOWN); | ||
423 | } | ||
424 | |||
425 | static struct spi_driver lms501kf03_driver = { | ||
426 | .driver = { | ||
427 | .name = "lms501kf03", | ||
428 | .owner = THIS_MODULE, | ||
429 | }, | ||
430 | .probe = lms501kf03_probe, | ||
431 | .remove = lms501kf03_remove, | ||
432 | .shutdown = lms501kf03_shutdown, | ||
433 | .suspend = lms501kf03_suspend, | ||
434 | .resume = lms501kf03_resume, | ||
435 | }; | ||
436 | |||
437 | module_spi_driver(lms501kf03_driver); | ||
438 | |||
439 | MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); | ||
440 | MODULE_DESCRIPTION("lms501kf03 LCD Driver"); | ||
441 | MODULE_LICENSE("GPL"); | ||