Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
ams369fg06.c
Go to the documentation of this file.
1 /*
2  * ams369fg06 AMOLED LCD panel driver.
3  *
4  * Copyright (c) 2011 Samsung Electronics Co., Ltd.
5  * Author: Jingoo Han <[email protected]>
6  *
7  * Derived from drivers/video/s6e63m0.c
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22  */
23 
24 #include <linux/wait.h>
25 #include <linux/module.h>
26 #include <linux/fb.h>
27 #include <linux/delay.h>
28 #include <linux/gpio.h>
29 #include <linux/spi/spi.h>
30 #include <linux/lcd.h>
31 #include <linux/backlight.h>
32 
33 #define SLEEPMSEC 0x1000
34 #define ENDDEF 0x2000
35 #define DEFMASK 0xFF00
36 #define COMMAND_ONLY 0xFE
37 #define DATA_ONLY 0xFF
38 
39 #define MAX_GAMMA_LEVEL 5
40 #define GAMMA_TABLE_COUNT 21
41 
42 #define MIN_BRIGHTNESS 0
43 #define MAX_BRIGHTNESS 255
44 #define DEFAULT_BRIGHTNESS 150
45 
46 struct ams369fg06 {
47  struct device *dev;
48  struct spi_device *spi;
49  unsigned int power;
50  struct lcd_device *ld;
53 };
54 
55 static const unsigned short seq_display_on[] = {
56  0x14, 0x03,
57  ENDDEF, 0x0000
58 };
59 
60 static const unsigned short seq_display_off[] = {
61  0x14, 0x00,
62  ENDDEF, 0x0000
63 };
64 
65 static const unsigned short seq_stand_by_on[] = {
66  0x1D, 0xA1,
67  SLEEPMSEC, 200,
68  ENDDEF, 0x0000
69 };
70 
71 static const unsigned short seq_stand_by_off[] = {
72  0x1D, 0xA0,
73  SLEEPMSEC, 250,
74  ENDDEF, 0x0000
75 };
76 
77 static const unsigned short seq_setting[] = {
78  0x31, 0x08,
79  0x32, 0x14,
80  0x30, 0x02,
81  0x27, 0x01,
82  0x12, 0x08,
83  0x13, 0x08,
84  0x15, 0x00,
85  0x16, 0x00,
86 
87  0xef, 0xd0,
88  DATA_ONLY, 0xe8,
89 
90  0x39, 0x44,
91  0x40, 0x00,
92  0x41, 0x3f,
93  0x42, 0x2a,
94  0x43, 0x27,
95  0x44, 0x27,
96  0x45, 0x1f,
97  0x46, 0x44,
98  0x50, 0x00,
99  0x51, 0x00,
100  0x52, 0x17,
101  0x53, 0x24,
102  0x54, 0x26,
103  0x55, 0x1f,
104  0x56, 0x43,
105  0x60, 0x00,
106  0x61, 0x3f,
107  0x62, 0x2a,
108  0x63, 0x25,
109  0x64, 0x24,
110  0x65, 0x1b,
111  0x66, 0x5c,
112 
113  0x17, 0x22,
114  0x18, 0x33,
115  0x19, 0x03,
116  0x1a, 0x01,
117  0x22, 0xa4,
118  0x23, 0x00,
119  0x26, 0xa0,
120 
121  0x1d, 0xa0,
122  SLEEPMSEC, 300,
123 
124  0x14, 0x03,
125 
126  ENDDEF, 0x0000
127 };
128 
129 /* gamma value: 2.2 */
130 static const unsigned int ams369fg06_22_250[] = {
131  0x00, 0x3f, 0x2a, 0x27, 0x27, 0x1f, 0x44,
132  0x00, 0x00, 0x17, 0x24, 0x26, 0x1f, 0x43,
133  0x00, 0x3f, 0x2a, 0x25, 0x24, 0x1b, 0x5c,
134 };
135 
136 static const unsigned int ams369fg06_22_200[] = {
137  0x00, 0x3f, 0x28, 0x29, 0x27, 0x21, 0x3e,
138  0x00, 0x00, 0x10, 0x25, 0x27, 0x20, 0x3d,
139  0x00, 0x3f, 0x28, 0x27, 0x25, 0x1d, 0x53,
140 };
141 
142 static const unsigned int ams369fg06_22_150[] = {
143  0x00, 0x3f, 0x2d, 0x29, 0x28, 0x23, 0x37,
144  0x00, 0x00, 0x0b, 0x25, 0x28, 0x22, 0x36,
145  0x00, 0x3f, 0x2b, 0x28, 0x26, 0x1f, 0x4a,
146 };
147 
148 static const unsigned int ams369fg06_22_100[] = {
149  0x00, 0x3f, 0x30, 0x2a, 0x2b, 0x24, 0x2f,
150  0x00, 0x00, 0x00, 0x25, 0x29, 0x24, 0x2e,
151  0x00, 0x3f, 0x2f, 0x29, 0x29, 0x21, 0x3f,
152 };
153 
154 static const unsigned int ams369fg06_22_50[] = {
155  0x00, 0x3f, 0x3c, 0x2c, 0x2d, 0x27, 0x24,
156  0x00, 0x00, 0x00, 0x22, 0x2a, 0x27, 0x23,
157  0x00, 0x3f, 0x3b, 0x2c, 0x2b, 0x24, 0x31,
158 };
159 
162 };
163 
164 static struct ams369fg06_gamma gamma_table = {
165  .gamma_22_table[0] = (unsigned int *)&ams369fg06_22_50,
166  .gamma_22_table[1] = (unsigned int *)&ams369fg06_22_100,
167  .gamma_22_table[2] = (unsigned int *)&ams369fg06_22_150,
168  .gamma_22_table[3] = (unsigned int *)&ams369fg06_22_200,
169  .gamma_22_table[4] = (unsigned int *)&ams369fg06_22_250,
170 };
171 
172 static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data)
173 {
174  u16 buf[1];
175  struct spi_message msg;
176 
177  struct spi_transfer xfer = {
178  .len = 2,
179  .tx_buf = buf,
180  };
181 
182  buf[0] = (addr << 8) | data;
183 
184  spi_message_init(&msg);
185  spi_message_add_tail(&xfer, &msg);
186 
187  return spi_sync(lcd->spi, &msg);
188 }
189 
190 static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address,
191  unsigned char command)
192 {
193  int ret = 0;
194 
195  if (address != DATA_ONLY)
196  ret = ams369fg06_spi_write_byte(lcd, 0x70, address);
197  if (command != COMMAND_ONLY)
198  ret = ams369fg06_spi_write_byte(lcd, 0x72, command);
199 
200  return ret;
201 }
202 
203 static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd,
204  const unsigned short *wbuf)
205 {
206  int ret = 0, i = 0;
207 
208  while ((wbuf[i] & DEFMASK) != ENDDEF) {
209  if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
210  ret = ams369fg06_spi_write(lcd, wbuf[i], wbuf[i+1]);
211  if (ret)
212  break;
213  } else
214  mdelay(wbuf[i+1]);
215  i += 2;
216  }
217 
218  return ret;
219 }
220 
221 static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
222  const unsigned int *gamma)
223 {
224  unsigned int i = 0;
225  int ret = 0;
226 
227  for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) {
228  ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]);
229  ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]);
230  ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]);
231  if (ret) {
232  dev_err(lcd->dev, "failed to set gamma table.\n");
233  goto gamma_err;
234  }
235  }
236 
237 gamma_err:
238  return ret;
239 }
240 
241 static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
242 {
243  int ret = 0;
244  int gamma = 0;
245 
246  if ((brightness >= 0) && (brightness <= 50))
247  gamma = 0;
248  else if ((brightness > 50) && (brightness <= 100))
249  gamma = 1;
250  else if ((brightness > 100) && (brightness <= 150))
251  gamma = 2;
252  else if ((brightness > 150) && (brightness <= 200))
253  gamma = 3;
254  else if ((brightness > 200) && (brightness <= 255))
255  gamma = 4;
256 
257  ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
258 
259  return ret;
260 }
261 
262 static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
263 {
264  int ret, i;
265  static const unsigned short *init_seq[] = {
266  seq_setting,
267  seq_stand_by_off,
268  };
269 
270  for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
271  ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
272  if (ret)
273  break;
274  }
275 
276  return ret;
277 }
278 
279 static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
280 {
281  int ret, i;
282  static const unsigned short *init_seq[] = {
283  seq_stand_by_off,
284  seq_display_on,
285  };
286 
287  for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
288  ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
289  if (ret)
290  break;
291  }
292 
293  return ret;
294 }
295 
296 static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
297 {
298  int ret, i;
299 
300  static const unsigned short *init_seq[] = {
301  seq_display_off,
302  seq_stand_by_on,
303  };
304 
305  for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
306  ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
307  if (ret)
308  break;
309  }
310 
311  return ret;
312 }
313 
314 static int ams369fg06_power_is_on(int power)
315 {
316  return ((power) <= FB_BLANK_NORMAL);
317 }
318 
319 static int ams369fg06_power_on(struct ams369fg06 *lcd)
320 {
321  int ret = 0;
322  struct lcd_platform_data *pd = NULL;
323  struct backlight_device *bd = NULL;
324 
325  pd = lcd->lcd_pd;
326  if (!pd) {
327  dev_err(lcd->dev, "platform data is NULL.\n");
328  return -EFAULT;
329  }
330 
331  bd = lcd->bd;
332  if (!bd) {
333  dev_err(lcd->dev, "backlight device is NULL.\n");
334  return -EFAULT;
335  }
336 
337  if (!pd->power_on) {
338  dev_err(lcd->dev, "power_on is NULL.\n");
339  return -EFAULT;
340  } else {
341  pd->power_on(lcd->ld, 1);
342  mdelay(pd->power_on_delay);
343  }
344 
345  if (!pd->reset) {
346  dev_err(lcd->dev, "reset is NULL.\n");
347  return -EFAULT;
348  } else {
349  pd->reset(lcd->ld);
350  mdelay(pd->reset_delay);
351  }
352 
353  ret = ams369fg06_ldi_init(lcd);
354  if (ret) {
355  dev_err(lcd->dev, "failed to initialize ldi.\n");
356  return ret;
357  }
358 
359  ret = ams369fg06_ldi_enable(lcd);
360  if (ret) {
361  dev_err(lcd->dev, "failed to enable ldi.\n");
362  return ret;
363  }
364 
365  /* set brightness to current value after power on or resume. */
366  ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
367  if (ret) {
368  dev_err(lcd->dev, "lcd gamma setting failed.\n");
369  return ret;
370  }
371 
372  return 0;
373 }
374 
375 static int ams369fg06_power_off(struct ams369fg06 *lcd)
376 {
377  int ret = 0;
378  struct lcd_platform_data *pd = NULL;
379 
380  pd = lcd->lcd_pd;
381  if (!pd) {
382  dev_err(lcd->dev, "platform data is NULL\n");
383  return -EFAULT;
384  }
385 
386  ret = ams369fg06_ldi_disable(lcd);
387  if (ret) {
388  dev_err(lcd->dev, "lcd setting failed.\n");
389  return -EIO;
390  }
391 
392  mdelay(pd->power_off_delay);
393 
394  if (!pd->power_on) {
395  dev_err(lcd->dev, "power_on is NULL.\n");
396  return -EFAULT;
397  } else
398  pd->power_on(lcd->ld, 0);
399 
400  return 0;
401 }
402 
403 static int ams369fg06_power(struct ams369fg06 *lcd, int power)
404 {
405  int ret = 0;
406 
407  if (ams369fg06_power_is_on(power) &&
408  !ams369fg06_power_is_on(lcd->power))
409  ret = ams369fg06_power_on(lcd);
410  else if (!ams369fg06_power_is_on(power) &&
411  ams369fg06_power_is_on(lcd->power))
412  ret = ams369fg06_power_off(lcd);
413 
414  if (!ret)
415  lcd->power = power;
416 
417  return ret;
418 }
419 
420 static int ams369fg06_get_power(struct lcd_device *ld)
421 {
422  struct ams369fg06 *lcd = lcd_get_data(ld);
423 
424  return lcd->power;
425 }
426 
427 static int ams369fg06_set_power(struct lcd_device *ld, int power)
428 {
429  struct ams369fg06 *lcd = lcd_get_data(ld);
430 
431  if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
432  power != FB_BLANK_NORMAL) {
433  dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
434  return -EINVAL;
435  }
436 
437  return ams369fg06_power(lcd, power);
438 }
439 
440 static int ams369fg06_get_brightness(struct backlight_device *bd)
441 {
442  return bd->props.brightness;
443 }
444 
445 static int ams369fg06_set_brightness(struct backlight_device *bd)
446 {
447  int ret = 0;
448  int brightness = bd->props.brightness;
449  struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev);
450 
451  if (brightness < MIN_BRIGHTNESS ||
452  brightness > bd->props.max_brightness) {
453  dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
455  return -EINVAL;
456  }
457 
458  ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
459  if (ret) {
460  dev_err(&bd->dev, "lcd brightness setting failed.\n");
461  return -EIO;
462  }
463 
464  return ret;
465 }
466 
467 static struct lcd_ops ams369fg06_lcd_ops = {
468  .get_power = ams369fg06_get_power,
469  .set_power = ams369fg06_set_power,
470 };
471 
472 static const struct backlight_ops ams369fg06_backlight_ops = {
473  .get_brightness = ams369fg06_get_brightness,
474  .update_status = ams369fg06_set_brightness,
475 };
476 
477 static int __devinit ams369fg06_probe(struct spi_device *spi)
478 {
479  int ret = 0;
480  struct ams369fg06 *lcd = NULL;
481  struct lcd_device *ld = NULL;
482  struct backlight_device *bd = NULL;
483  struct backlight_properties props;
484 
485  lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
486  if (!lcd)
487  return -ENOMEM;
488 
489  /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
490  spi->bits_per_word = 16;
491 
492  ret = spi_setup(spi);
493  if (ret < 0) {
494  dev_err(&spi->dev, "spi setup failed.\n");
495  return ret;
496  }
497 
498  lcd->spi = spi;
499  lcd->dev = &spi->dev;
500 
501  lcd->lcd_pd = spi->dev.platform_data;
502  if (!lcd->lcd_pd) {
503  dev_err(&spi->dev, "platform data is NULL\n");
504  return -EFAULT;
505  }
506 
507  ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
508  &ams369fg06_lcd_ops);
509  if (IS_ERR(ld))
510  return PTR_ERR(ld);
511 
512  lcd->ld = ld;
513 
514  memset(&props, 0, sizeof(struct backlight_properties));
515  props.type = BACKLIGHT_RAW;
516  props.max_brightness = MAX_BRIGHTNESS;
517 
518  bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
519  &ams369fg06_backlight_ops, &props);
520  if (IS_ERR(bd)) {
521  ret = PTR_ERR(bd);
522  goto out_lcd_unregister;
523  }
524 
525  bd->props.brightness = DEFAULT_BRIGHTNESS;
526  lcd->bd = bd;
527 
528  if (!lcd->lcd_pd->lcd_enabled) {
529  /*
530  * if lcd panel was off from bootloader then
531  * current lcd status is powerdown and then
532  * it enables lcd panel.
533  */
534  lcd->power = FB_BLANK_POWERDOWN;
535 
536  ams369fg06_power(lcd, FB_BLANK_UNBLANK);
537  } else
538  lcd->power = FB_BLANK_UNBLANK;
539 
540  dev_set_drvdata(&spi->dev, lcd);
541 
542  dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
543 
544  return 0;
545 
546 out_lcd_unregister:
548  return ret;
549 }
550 
551 static int __devexit ams369fg06_remove(struct spi_device *spi)
552 {
553  struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
554 
555  ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
558 
559  return 0;
560 }
561 
562 #if defined(CONFIG_PM)
563 static unsigned int before_power;
564 
565 static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
566 {
567  int ret = 0;
568  struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
569 
570  dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
571 
572  before_power = lcd->power;
573 
574  /*
575  * when lcd panel is suspend, lcd panel becomes off
576  * regardless of status.
577  */
578  ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
579 
580  return ret;
581 }
582 
583 static int ams369fg06_resume(struct spi_device *spi)
584 {
585  int ret = 0;
586  struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
587 
588  /*
589  * after suspended, if lcd panel status is FB_BLANK_UNBLANK
590  * (at that time, before_power is FB_BLANK_UNBLANK) then
591  * it changes that status to FB_BLANK_POWERDOWN to get lcd on.
592  */
593  if (before_power == FB_BLANK_UNBLANK)
594  lcd->power = FB_BLANK_POWERDOWN;
595 
596  dev_dbg(&spi->dev, "before_power = %d\n", before_power);
597 
598  ret = ams369fg06_power(lcd, before_power);
599 
600  return ret;
601 }
602 #else
603 #define ams369fg06_suspend NULL
604 #define ams369fg06_resume NULL
605 #endif
606 
607 static void ams369fg06_shutdown(struct spi_device *spi)
608 {
609  struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
610 
611  ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
612 }
613 
614 static struct spi_driver ams369fg06_driver = {
615  .driver = {
616  .name = "ams369fg06",
617  .owner = THIS_MODULE,
618  },
619  .probe = ams369fg06_probe,
620  .remove = __devexit_p(ams369fg06_remove),
621  .shutdown = ams369fg06_shutdown,
622  .suspend = ams369fg06_suspend,
623  .resume = ams369fg06_resume,
624 };
625 
626 module_spi_driver(ams369fg06_driver);
627 
628 MODULE_AUTHOR("Jingoo Han <[email protected]>");
629 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
630 MODULE_LICENSE("GPL");