2 * LCD/Backlight Driver for Sharp Zaurus Handhelds (various models)
4 * Copyright (c) 2004-2006 Richard Purdie
6 * Based on Sharp's 2.4 Backlight Driver
8 * Copyright (c) 2008 Marvell International Ltd.
9 * Converted to SPI device based LCD/Backlight device driver
10 * by Eric Miao <eric.miao@marvell.com>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License version 2 as
14 * published by the Free Software Foundation.
18 #include <linux/module.h>
19 #include <linux/kernel.h>
20 #include <linux/init.h>
21 #include <linux/delay.h>
22 #include <linux/gpio.h>
24 #include <linux/lcd.h>
25 #include <linux/spi/spi.h>
26 #include <linux/spi/corgi_lcd.h>
27 #include <linux/slab.h>
28 #include <asm/mach/sharpsl_param.h>
30 #define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
32 /* Register Addresses */
33 #define RESCTL_ADRS 0x00
34 #define PHACTRL_ADRS 0x01
35 #define DUTYCTRL_ADRS 0x02
36 #define POWERREG0_ADRS 0x03
37 #define POWERREG1_ADRS 0x04
38 #define GPOR3_ADRS 0x05
39 #define PICTRL_ADRS 0x06
40 #define POLCTRL_ADRS 0x07
42 /* Register Bit Definitions */
43 #define RESCTL_QVGA 0x01
44 #define RESCTL_VGA 0x00
46 #define POWER1_VW_ON 0x01 /* VW Supply FET ON */
47 #define POWER1_GVSS_ON 0x02 /* GVSS(-8V) Power Supply ON */
48 #define POWER1_VDD_ON 0x04 /* VDD(8V),SVSS(-4V) Power Supply ON */
50 #define POWER1_VW_OFF 0x00 /* VW Supply FET OFF */
51 #define POWER1_GVSS_OFF 0x00 /* GVSS(-8V) Power Supply OFF */
52 #define POWER1_VDD_OFF 0x00 /* VDD(8V),SVSS(-4V) Power Supply OFF */
54 #define POWER0_COM_DCLK 0x01 /* COM Voltage DC Bias DAC Serial Data Clock */
55 #define POWER0_COM_DOUT 0x02 /* COM Voltage DC Bias DAC Serial Data Out */
56 #define POWER0_DAC_ON 0x04 /* DAC Power Supply ON */
57 #define POWER0_COM_ON 0x08 /* COM Power Supply ON */
58 #define POWER0_VCC5_ON 0x10 /* VCC5 Power Supply ON */
60 #define POWER0_DAC_OFF 0x00 /* DAC Power Supply OFF */
61 #define POWER0_COM_OFF 0x00 /* COM Power Supply OFF */
62 #define POWER0_VCC5_OFF 0x00 /* VCC5 Power Supply OFF */
64 #define PICTRL_INIT_STATE 0x01
65 #define PICTRL_INIOFF 0x02
66 #define PICTRL_POWER_DOWN 0x04
67 #define PICTRL_COM_SIGNAL_OFF 0x08
68 #define PICTRL_DAC_SIGNAL_OFF 0x10
70 #define POLCTRL_SYNC_POL_FALL 0x01
71 #define POLCTRL_EN_POL_FALL 0x02
72 #define POLCTRL_DATA_POL_FALL 0x04
73 #define POLCTRL_SYNC_ACT_H 0x08
74 #define POLCTRL_EN_ACT_L 0x10
76 #define POLCTRL_SYNC_POL_RISE 0x00
77 #define POLCTRL_EN_POL_RISE 0x00
78 #define POLCTRL_DATA_POL_RISE 0x00
79 #define POLCTRL_SYNC_ACT_L 0x00
80 #define POLCTRL_EN_ACT_H 0x00
82 #define PHACTRL_PHASE_MANUAL 0x01
83 #define DEFAULT_PHAD_QVGA (9)
84 #define DEFAULT_COMADJ (125)
87 struct spi_device
*spi_dev
;
88 struct lcd_device
*lcd_dev
;
89 struct backlight_device
*bl_dev
;
97 int gpio_backlight_on
;
98 int gpio_backlight_cont
;
99 int gpio_backlight_cont_inverted
;
101 void (*kick_battery
)(void);
104 static int corgi_ssp_lcdtg_send(struct corgi_lcd
*lcd
, int reg
, uint8_t val
);
106 static struct corgi_lcd
*the_corgi_lcd
;
107 static unsigned long corgibl_flags
;
108 #define CORGIBL_SUSPENDED 0x01
109 #define CORGIBL_BATTLOW 0x02
112 * This is only a pseudo I2C interface. We can't use the standard kernel
113 * routines as the interface is write only. We just assume the data is acked...
115 static void lcdtg_ssp_i2c_send(struct corgi_lcd
*lcd
, uint8_t data
)
117 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
, data
);
121 static void lcdtg_i2c_send_bit(struct corgi_lcd
*lcd
, uint8_t data
)
123 lcdtg_ssp_i2c_send(lcd
, data
);
124 lcdtg_ssp_i2c_send(lcd
, data
| POWER0_COM_DCLK
);
125 lcdtg_ssp_i2c_send(lcd
, data
);
128 static void lcdtg_i2c_send_start(struct corgi_lcd
*lcd
, uint8_t base
)
130 lcdtg_ssp_i2c_send(lcd
, base
| POWER0_COM_DCLK
| POWER0_COM_DOUT
);
131 lcdtg_ssp_i2c_send(lcd
, base
| POWER0_COM_DCLK
);
132 lcdtg_ssp_i2c_send(lcd
, base
);
135 static void lcdtg_i2c_send_stop(struct corgi_lcd
*lcd
, uint8_t base
)
137 lcdtg_ssp_i2c_send(lcd
, base
);
138 lcdtg_ssp_i2c_send(lcd
, base
| POWER0_COM_DCLK
);
139 lcdtg_ssp_i2c_send(lcd
, base
| POWER0_COM_DCLK
| POWER0_COM_DOUT
);
142 static void lcdtg_i2c_send_byte(struct corgi_lcd
*lcd
,
143 uint8_t base
, uint8_t data
)
146 for (i
= 0; i
< 8; i
++) {
148 lcdtg_i2c_send_bit(lcd
, base
| POWER0_COM_DOUT
);
150 lcdtg_i2c_send_bit(lcd
, base
);
155 static void lcdtg_i2c_wait_ack(struct corgi_lcd
*lcd
, uint8_t base
)
157 lcdtg_i2c_send_bit(lcd
, base
);
160 static void lcdtg_set_common_voltage(struct corgi_lcd
*lcd
,
161 uint8_t base_data
, uint8_t data
)
163 /* Set Common Voltage to M62332FP via I2C */
164 lcdtg_i2c_send_start(lcd
, base_data
);
165 lcdtg_i2c_send_byte(lcd
, base_data
, 0x9c);
166 lcdtg_i2c_wait_ack(lcd
, base_data
);
167 lcdtg_i2c_send_byte(lcd
, base_data
, 0x00);
168 lcdtg_i2c_wait_ack(lcd
, base_data
);
169 lcdtg_i2c_send_byte(lcd
, base_data
, data
);
170 lcdtg_i2c_wait_ack(lcd
, base_data
);
171 lcdtg_i2c_send_stop(lcd
, base_data
);
174 static int corgi_ssp_lcdtg_send(struct corgi_lcd
*lcd
, int adrs
, uint8_t data
)
176 struct spi_message msg
;
177 struct spi_transfer xfer
= {
183 lcd
->buf
[0] = ((adrs
& 0x07) << 5) | (data
& 0x1f);
184 spi_message_init(&msg
);
185 spi_message_add_tail(&xfer
, &msg
);
187 return spi_sync(lcd
->spi_dev
, &msg
);
190 /* Set Phase Adjust */
191 static void lcdtg_set_phadadj(struct corgi_lcd
*lcd
, int mode
)
196 case CORGI_LCD_MODE_VGA
:
197 /* Setting for VGA */
198 adj
= sharpsl_param
.phadadj
;
199 adj
= (adj
< 0) ? PHACTRL_PHASE_MANUAL
:
200 PHACTRL_PHASE_MANUAL
| ((adj
& 0xf) << 1);
202 case CORGI_LCD_MODE_QVGA
:
204 /* Setting for QVGA */
205 adj
= (DEFAULT_PHAD_QVGA
<< 1) | PHACTRL_PHASE_MANUAL
;
209 corgi_ssp_lcdtg_send(lcd
, PHACTRL_ADRS
, adj
);
212 static void corgi_lcd_power_on(struct corgi_lcd
*lcd
)
216 /* Initialize Internal Logic & Port */
217 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
218 PICTRL_POWER_DOWN
| PICTRL_INIOFF
|
219 PICTRL_INIT_STATE
| PICTRL_COM_SIGNAL_OFF
|
220 PICTRL_DAC_SIGNAL_OFF
);
222 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
223 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_OFF
|
224 POWER0_COM_OFF
| POWER0_VCC5_OFF
);
226 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
227 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_OFF
);
229 /* VDD(+8V), SVSS(-4V) ON */
230 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
231 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_ON
);
235 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
236 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
237 POWER0_COM_OFF
| POWER0_VCC5_OFF
);
239 /* INIB = H, INI = L */
240 /* PICTL[0] = H , PICTL[1] = PICTL[2] = PICTL[4] = L */
241 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
242 PICTRL_INIT_STATE
| PICTRL_COM_SIGNAL_OFF
);
244 /* Set Common Voltage */
245 comadj
= sharpsl_param
.comadj
;
247 comadj
= DEFAULT_COMADJ
;
249 lcdtg_set_common_voltage(lcd
, POWER0_DAC_ON
| POWER0_COM_OFF
|
250 POWER0_VCC5_OFF
, comadj
);
252 /* VCC5 ON, DAC ON */
253 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
254 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
255 POWER0_COM_OFF
| POWER0_VCC5_ON
);
257 /* GVSS(-8V) ON, VDD ON */
258 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
259 POWER1_VW_OFF
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
262 /* COM SIGNAL ON (PICTL[3] = L) */
263 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, PICTRL_INIT_STATE
);
265 /* COM ON, DAC ON, VCC5_ON */
266 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
267 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
268 POWER0_COM_ON
| POWER0_VCC5_ON
);
270 /* VW ON, GVSS ON, VDD ON */
271 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
272 POWER1_VW_ON
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
274 /* Signals output enable */
275 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, 0);
277 /* Set Phase Adjust */
278 lcdtg_set_phadadj(lcd
, lcd
->mode
);
280 /* Initialize for Input Signals from ATI */
281 corgi_ssp_lcdtg_send(lcd
, POLCTRL_ADRS
,
282 POLCTRL_SYNC_POL_RISE
| POLCTRL_EN_POL_RISE
|
283 POLCTRL_DATA_POL_RISE
| POLCTRL_SYNC_ACT_L
|
288 case CORGI_LCD_MODE_VGA
:
289 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_VGA
);
291 case CORGI_LCD_MODE_QVGA
:
293 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_QVGA
);
298 static void corgi_lcd_power_off(struct corgi_lcd
*lcd
)
300 /* 60Hz x 2 frame = 16.7msec x 2 = 33.4 msec */
304 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
305 POWER1_VW_OFF
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
308 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, PICTRL_COM_SIGNAL_OFF
);
309 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
310 POWER0_DAC_ON
| POWER0_COM_OFF
| POWER0_VCC5_ON
);
312 /* (3)Set Common Voltage Bias 0V */
313 lcdtg_set_common_voltage(lcd
, POWER0_DAC_ON
| POWER0_COM_OFF
|
317 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
318 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_ON
);
321 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
322 POWER0_DAC_ON
| POWER0_COM_OFF
| POWER0_VCC5_OFF
);
324 /* (6)Set PDWN, INIOFF, DACOFF */
325 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
326 PICTRL_INIOFF
| PICTRL_DAC_SIGNAL_OFF
|
327 PICTRL_POWER_DOWN
| PICTRL_COM_SIGNAL_OFF
);
330 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
331 POWER0_DAC_OFF
| POWER0_COM_OFF
| POWER0_VCC5_OFF
);
334 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
335 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_OFF
);
338 static int corgi_lcd_set_mode(struct lcd_device
*ld
, struct fb_videomode
*m
)
340 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
341 int mode
= CORGI_LCD_MODE_QVGA
;
343 if (m
->xres
== 640 || m
->xres
== 480)
344 mode
= CORGI_LCD_MODE_VGA
;
346 if (lcd
->mode
== mode
)
349 lcdtg_set_phadadj(lcd
, mode
);
352 case CORGI_LCD_MODE_VGA
:
353 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_VGA
);
355 case CORGI_LCD_MODE_QVGA
:
357 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_QVGA
);
365 static int corgi_lcd_set_power(struct lcd_device
*ld
, int power
)
367 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
369 if (POWER_IS_ON(power
) && !POWER_IS_ON(lcd
->power
))
370 corgi_lcd_power_on(lcd
);
372 if (!POWER_IS_ON(power
) && POWER_IS_ON(lcd
->power
))
373 corgi_lcd_power_off(lcd
);
379 static int corgi_lcd_get_power(struct lcd_device
*ld
)
381 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
386 static struct lcd_ops corgi_lcd_ops
= {
387 .get_power
= corgi_lcd_get_power
,
388 .set_power
= corgi_lcd_set_power
,
389 .set_mode
= corgi_lcd_set_mode
,
392 static int corgi_bl_get_intensity(struct backlight_device
*bd
)
394 struct corgi_lcd
*lcd
= bl_get_data(bd
);
396 return lcd
->intensity
;
399 static int corgi_bl_set_intensity(struct corgi_lcd
*lcd
, int intensity
)
403 if (intensity
> 0x10)
406 corgi_ssp_lcdtg_send(lcd
, DUTYCTRL_ADRS
, intensity
);
408 /* Bit 5 via GPIO_BACKLIGHT_CONT */
409 cont
= !!(intensity
& 0x20) ^ lcd
->gpio_backlight_cont_inverted
;
411 if (gpio_is_valid(lcd
->gpio_backlight_cont
))
412 gpio_set_value_cansleep(lcd
->gpio_backlight_cont
, cont
);
414 if (gpio_is_valid(lcd
->gpio_backlight_on
))
415 gpio_set_value_cansleep(lcd
->gpio_backlight_on
, intensity
);
417 if (lcd
->kick_battery
)
420 lcd
->intensity
= intensity
;
424 static int corgi_bl_update_status(struct backlight_device
*bd
)
426 struct corgi_lcd
*lcd
= bl_get_data(bd
);
427 int intensity
= bd
->props
.brightness
;
429 if (bd
->props
.power
!= FB_BLANK_UNBLANK
)
432 if (bd
->props
.fb_blank
!= FB_BLANK_UNBLANK
)
435 if (corgibl_flags
& CORGIBL_SUSPENDED
)
438 if ((corgibl_flags
& CORGIBL_BATTLOW
) && intensity
> lcd
->limit_mask
)
439 intensity
= lcd
->limit_mask
;
441 return corgi_bl_set_intensity(lcd
, intensity
);
444 void corgi_lcd_limit_intensity(int limit
)
447 corgibl_flags
|= CORGIBL_BATTLOW
;
449 corgibl_flags
&= ~CORGIBL_BATTLOW
;
451 backlight_update_status(the_corgi_lcd
->bl_dev
);
453 EXPORT_SYMBOL(corgi_lcd_limit_intensity
);
455 static const struct backlight_ops corgi_bl_ops
= {
456 .get_brightness
= corgi_bl_get_intensity
,
457 .update_status
= corgi_bl_update_status
,
460 #ifdef CONFIG_PM_SLEEP
461 static int corgi_lcd_suspend(struct device
*dev
)
463 struct corgi_lcd
*lcd
= dev_get_drvdata(dev
);
465 corgibl_flags
|= CORGIBL_SUSPENDED
;
466 corgi_bl_set_intensity(lcd
, 0);
467 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_POWERDOWN
);
471 static int corgi_lcd_resume(struct device
*dev
)
473 struct corgi_lcd
*lcd
= dev_get_drvdata(dev
);
475 corgibl_flags
&= ~CORGIBL_SUSPENDED
;
476 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_UNBLANK
);
477 backlight_update_status(lcd
->bl_dev
);
482 static SIMPLE_DEV_PM_OPS(corgi_lcd_pm_ops
, corgi_lcd_suspend
, corgi_lcd_resume
);
484 static int setup_gpio_backlight(struct corgi_lcd
*lcd
,
485 struct corgi_lcd_platform_data
*pdata
)
487 struct spi_device
*spi
= lcd
->spi_dev
;
490 lcd
->gpio_backlight_on
= -1;
491 lcd
->gpio_backlight_cont
= -1;
493 if (gpio_is_valid(pdata
->gpio_backlight_on
)) {
494 err
= devm_gpio_request(&spi
->dev
, pdata
->gpio_backlight_on
,
498 "failed to request GPIO%d for backlight_on\n",
499 pdata
->gpio_backlight_on
);
503 lcd
->gpio_backlight_on
= pdata
->gpio_backlight_on
;
504 gpio_direction_output(lcd
->gpio_backlight_on
, 0);
507 if (gpio_is_valid(pdata
->gpio_backlight_cont
)) {
508 err
= devm_gpio_request(&spi
->dev
, pdata
->gpio_backlight_cont
,
512 "failed to request GPIO%d for backlight_cont\n",
513 pdata
->gpio_backlight_cont
);
517 lcd
->gpio_backlight_cont
= pdata
->gpio_backlight_cont
;
519 /* spitz and akita use both GPIOs for backlight, and
520 * have inverted polarity of GPIO_BACKLIGHT_CONT
522 if (gpio_is_valid(lcd
->gpio_backlight_on
)) {
523 lcd
->gpio_backlight_cont_inverted
= 1;
524 gpio_direction_output(lcd
->gpio_backlight_cont
, 1);
526 lcd
->gpio_backlight_cont_inverted
= 0;
527 gpio_direction_output(lcd
->gpio_backlight_cont
, 0);
533 static int corgi_lcd_probe(struct spi_device
*spi
)
535 struct backlight_properties props
;
536 struct corgi_lcd_platform_data
*pdata
= spi
->dev
.platform_data
;
537 struct corgi_lcd
*lcd
;
541 dev_err(&spi
->dev
, "platform data not available\n");
545 lcd
= devm_kzalloc(&spi
->dev
, sizeof(struct corgi_lcd
), GFP_KERNEL
);
547 dev_err(&spi
->dev
, "failed to allocate memory\n");
553 lcd
->lcd_dev
= lcd_device_register("corgi_lcd", &spi
->dev
,
554 lcd
, &corgi_lcd_ops
);
555 if (IS_ERR(lcd
->lcd_dev
))
556 return PTR_ERR(lcd
->lcd_dev
);
558 lcd
->power
= FB_BLANK_POWERDOWN
;
559 lcd
->mode
= (pdata
) ? pdata
->init_mode
: CORGI_LCD_MODE_VGA
;
561 memset(&props
, 0, sizeof(struct backlight_properties
));
562 props
.type
= BACKLIGHT_RAW
;
563 props
.max_brightness
= pdata
->max_intensity
;
564 lcd
->bl_dev
= backlight_device_register("corgi_bl", &spi
->dev
, lcd
,
565 &corgi_bl_ops
, &props
);
566 if (IS_ERR(lcd
->bl_dev
)) {
567 ret
= PTR_ERR(lcd
->bl_dev
);
568 goto err_unregister_lcd
;
570 lcd
->bl_dev
->props
.brightness
= pdata
->default_intensity
;
571 lcd
->bl_dev
->props
.power
= FB_BLANK_UNBLANK
;
573 ret
= setup_gpio_backlight(lcd
, pdata
);
575 goto err_unregister_bl
;
577 lcd
->kick_battery
= pdata
->kick_battery
;
579 spi_set_drvdata(spi
, lcd
);
580 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_UNBLANK
);
581 backlight_update_status(lcd
->bl_dev
);
583 lcd
->limit_mask
= pdata
->limit_mask
;
588 backlight_device_unregister(lcd
->bl_dev
);
590 lcd_device_unregister(lcd
->lcd_dev
);
594 static int corgi_lcd_remove(struct spi_device
*spi
)
596 struct corgi_lcd
*lcd
= spi_get_drvdata(spi
);
598 lcd
->bl_dev
->props
.power
= FB_BLANK_UNBLANK
;
599 lcd
->bl_dev
->props
.brightness
= 0;
600 backlight_update_status(lcd
->bl_dev
);
601 backlight_device_unregister(lcd
->bl_dev
);
603 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_POWERDOWN
);
604 lcd_device_unregister(lcd
->lcd_dev
);
609 static struct spi_driver corgi_lcd_driver
= {
612 .owner
= THIS_MODULE
,
613 .pm
= &corgi_lcd_pm_ops
,
615 .probe
= corgi_lcd_probe
,
616 .remove
= corgi_lcd_remove
,
619 module_spi_driver(corgi_lcd_driver
);
621 MODULE_DESCRIPTION("LCD and backlight driver for SHARP C7x0/Cxx00");
622 MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
623 MODULE_LICENSE("GPL");
624 MODULE_ALIAS("spi:corgi-lcd");