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
)
147 for (i
= 0; i
< 8; i
++) {
149 lcdtg_i2c_send_bit(lcd
, base
| POWER0_COM_DOUT
);
151 lcdtg_i2c_send_bit(lcd
, base
);
156 static void lcdtg_i2c_wait_ack(struct corgi_lcd
*lcd
, uint8_t base
)
158 lcdtg_i2c_send_bit(lcd
, base
);
161 static void lcdtg_set_common_voltage(struct corgi_lcd
*lcd
,
162 uint8_t base_data
, uint8_t data
)
164 /* Set Common Voltage to M62332FP via I2C */
165 lcdtg_i2c_send_start(lcd
, base_data
);
166 lcdtg_i2c_send_byte(lcd
, base_data
, 0x9c);
167 lcdtg_i2c_wait_ack(lcd
, base_data
);
168 lcdtg_i2c_send_byte(lcd
, base_data
, 0x00);
169 lcdtg_i2c_wait_ack(lcd
, base_data
);
170 lcdtg_i2c_send_byte(lcd
, base_data
, data
);
171 lcdtg_i2c_wait_ack(lcd
, base_data
);
172 lcdtg_i2c_send_stop(lcd
, base_data
);
175 static int corgi_ssp_lcdtg_send(struct corgi_lcd
*lcd
, int adrs
, uint8_t data
)
177 struct spi_message msg
;
178 struct spi_transfer xfer
= {
184 lcd
->buf
[0] = ((adrs
& 0x07) << 5) | (data
& 0x1f);
185 spi_message_init(&msg
);
186 spi_message_add_tail(&xfer
, &msg
);
188 return spi_sync(lcd
->spi_dev
, &msg
);
191 /* Set Phase Adjust */
192 static void lcdtg_set_phadadj(struct corgi_lcd
*lcd
, int mode
)
197 case CORGI_LCD_MODE_VGA
:
198 /* Setting for VGA */
199 adj
= sharpsl_param
.phadadj
;
200 adj
= (adj
< 0) ? PHACTRL_PHASE_MANUAL
:
201 PHACTRL_PHASE_MANUAL
| ((adj
& 0xf) << 1);
203 case CORGI_LCD_MODE_QVGA
:
205 /* Setting for QVGA */
206 adj
= (DEFAULT_PHAD_QVGA
<< 1) | PHACTRL_PHASE_MANUAL
;
210 corgi_ssp_lcdtg_send(lcd
, PHACTRL_ADRS
, adj
);
213 static void corgi_lcd_power_on(struct corgi_lcd
*lcd
)
217 /* Initialize Internal Logic & Port */
218 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
219 PICTRL_POWER_DOWN
| PICTRL_INIOFF
|
220 PICTRL_INIT_STATE
| PICTRL_COM_SIGNAL_OFF
|
221 PICTRL_DAC_SIGNAL_OFF
);
223 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
224 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_OFF
|
225 POWER0_COM_OFF
| POWER0_VCC5_OFF
);
227 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
228 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_OFF
);
230 /* VDD(+8V), SVSS(-4V) ON */
231 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
232 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_ON
);
236 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
237 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
238 POWER0_COM_OFF
| POWER0_VCC5_OFF
);
240 /* INIB = H, INI = L */
241 /* PICTL[0] = H , PICTL[1] = PICTL[2] = PICTL[4] = L */
242 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
243 PICTRL_INIT_STATE
| PICTRL_COM_SIGNAL_OFF
);
245 /* Set Common Voltage */
246 comadj
= sharpsl_param
.comadj
;
248 comadj
= DEFAULT_COMADJ
;
250 lcdtg_set_common_voltage(lcd
, POWER0_DAC_ON
| POWER0_COM_OFF
|
251 POWER0_VCC5_OFF
, comadj
);
253 /* VCC5 ON, DAC ON */
254 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
255 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
256 POWER0_COM_OFF
| POWER0_VCC5_ON
);
258 /* GVSS(-8V) ON, VDD ON */
259 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
260 POWER1_VW_OFF
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
263 /* COM SIGNAL ON (PICTL[3] = L) */
264 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, PICTRL_INIT_STATE
);
266 /* COM ON, DAC ON, VCC5_ON */
267 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
268 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
269 POWER0_COM_ON
| POWER0_VCC5_ON
);
271 /* VW ON, GVSS ON, VDD ON */
272 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
273 POWER1_VW_ON
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
275 /* Signals output enable */
276 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, 0);
278 /* Set Phase Adjust */
279 lcdtg_set_phadadj(lcd
, lcd
->mode
);
281 /* Initialize for Input Signals from ATI */
282 corgi_ssp_lcdtg_send(lcd
, POLCTRL_ADRS
,
283 POLCTRL_SYNC_POL_RISE
| POLCTRL_EN_POL_RISE
|
284 POLCTRL_DATA_POL_RISE
| POLCTRL_SYNC_ACT_L
|
289 case CORGI_LCD_MODE_VGA
:
290 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_VGA
);
292 case CORGI_LCD_MODE_QVGA
:
294 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_QVGA
);
299 static void corgi_lcd_power_off(struct corgi_lcd
*lcd
)
301 /* 60Hz x 2 frame = 16.7msec x 2 = 33.4 msec */
305 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
306 POWER1_VW_OFF
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
309 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, PICTRL_COM_SIGNAL_OFF
);
310 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
311 POWER0_DAC_ON
| POWER0_COM_OFF
| POWER0_VCC5_ON
);
313 /* (3)Set Common Voltage Bias 0V */
314 lcdtg_set_common_voltage(lcd
, POWER0_DAC_ON
| POWER0_COM_OFF
|
318 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
319 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_ON
);
322 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
323 POWER0_DAC_ON
| POWER0_COM_OFF
| POWER0_VCC5_OFF
);
325 /* (6)Set PDWN, INIOFF, DACOFF */
326 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
327 PICTRL_INIOFF
| PICTRL_DAC_SIGNAL_OFF
|
328 PICTRL_POWER_DOWN
| PICTRL_COM_SIGNAL_OFF
);
331 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
332 POWER0_DAC_OFF
| POWER0_COM_OFF
| POWER0_VCC5_OFF
);
335 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
336 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_OFF
);
339 static int corgi_lcd_set_mode(struct lcd_device
*ld
, struct fb_videomode
*m
)
341 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
342 int mode
= CORGI_LCD_MODE_QVGA
;
344 if (m
->xres
== 640 || m
->xres
== 480)
345 mode
= CORGI_LCD_MODE_VGA
;
347 if (lcd
->mode
== mode
)
350 lcdtg_set_phadadj(lcd
, mode
);
353 case CORGI_LCD_MODE_VGA
:
354 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_VGA
);
356 case CORGI_LCD_MODE_QVGA
:
358 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_QVGA
);
366 static int corgi_lcd_set_power(struct lcd_device
*ld
, int power
)
368 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
370 if (POWER_IS_ON(power
) && !POWER_IS_ON(lcd
->power
))
371 corgi_lcd_power_on(lcd
);
373 if (!POWER_IS_ON(power
) && POWER_IS_ON(lcd
->power
))
374 corgi_lcd_power_off(lcd
);
380 static int corgi_lcd_get_power(struct lcd_device
*ld
)
382 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
387 static struct lcd_ops corgi_lcd_ops
= {
388 .get_power
= corgi_lcd_get_power
,
389 .set_power
= corgi_lcd_set_power
,
390 .set_mode
= corgi_lcd_set_mode
,
393 static int corgi_bl_get_intensity(struct backlight_device
*bd
)
395 struct corgi_lcd
*lcd
= bl_get_data(bd
);
397 return lcd
->intensity
;
400 static int corgi_bl_set_intensity(struct corgi_lcd
*lcd
, int intensity
)
404 if (intensity
> 0x10)
407 corgi_ssp_lcdtg_send(lcd
, DUTYCTRL_ADRS
, intensity
);
409 /* Bit 5 via GPIO_BACKLIGHT_CONT */
410 cont
= !!(intensity
& 0x20) ^ lcd
->gpio_backlight_cont_inverted
;
412 if (gpio_is_valid(lcd
->gpio_backlight_cont
))
413 gpio_set_value_cansleep(lcd
->gpio_backlight_cont
, cont
);
415 if (gpio_is_valid(lcd
->gpio_backlight_on
))
416 gpio_set_value_cansleep(lcd
->gpio_backlight_on
, intensity
);
418 if (lcd
->kick_battery
)
421 lcd
->intensity
= intensity
;
425 static int corgi_bl_update_status(struct backlight_device
*bd
)
427 struct corgi_lcd
*lcd
= bl_get_data(bd
);
428 int intensity
= bd
->props
.brightness
;
430 if (bd
->props
.power
!= FB_BLANK_UNBLANK
)
433 if (bd
->props
.fb_blank
!= FB_BLANK_UNBLANK
)
436 if (corgibl_flags
& CORGIBL_SUSPENDED
)
439 if ((corgibl_flags
& CORGIBL_BATTLOW
) && intensity
> lcd
->limit_mask
)
440 intensity
= lcd
->limit_mask
;
442 return corgi_bl_set_intensity(lcd
, intensity
);
445 void corgi_lcd_limit_intensity(int limit
)
448 corgibl_flags
|= CORGIBL_BATTLOW
;
450 corgibl_flags
&= ~CORGIBL_BATTLOW
;
452 backlight_update_status(the_corgi_lcd
->bl_dev
);
454 EXPORT_SYMBOL(corgi_lcd_limit_intensity
);
456 static const struct backlight_ops corgi_bl_ops
= {
457 .get_brightness
= corgi_bl_get_intensity
,
458 .update_status
= corgi_bl_update_status
,
461 #ifdef CONFIG_PM_SLEEP
462 static int corgi_lcd_suspend(struct device
*dev
)
464 struct corgi_lcd
*lcd
= dev_get_drvdata(dev
);
466 corgibl_flags
|= CORGIBL_SUSPENDED
;
467 corgi_bl_set_intensity(lcd
, 0);
468 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_POWERDOWN
);
472 static int corgi_lcd_resume(struct device
*dev
)
474 struct corgi_lcd
*lcd
= dev_get_drvdata(dev
);
476 corgibl_flags
&= ~CORGIBL_SUSPENDED
;
477 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_UNBLANK
);
478 backlight_update_status(lcd
->bl_dev
);
483 static SIMPLE_DEV_PM_OPS(corgi_lcd_pm_ops
, corgi_lcd_suspend
, corgi_lcd_resume
);
485 static int setup_gpio_backlight(struct corgi_lcd
*lcd
,
486 struct corgi_lcd_platform_data
*pdata
)
488 struct spi_device
*spi
= lcd
->spi_dev
;
491 lcd
->gpio_backlight_on
= -1;
492 lcd
->gpio_backlight_cont
= -1;
494 if (gpio_is_valid(pdata
->gpio_backlight_on
)) {
495 err
= devm_gpio_request(&spi
->dev
, pdata
->gpio_backlight_on
,
499 "failed to request GPIO%d for backlight_on\n",
500 pdata
->gpio_backlight_on
);
504 lcd
->gpio_backlight_on
= pdata
->gpio_backlight_on
;
505 gpio_direction_output(lcd
->gpio_backlight_on
, 0);
508 if (gpio_is_valid(pdata
->gpio_backlight_cont
)) {
509 err
= devm_gpio_request(&spi
->dev
, pdata
->gpio_backlight_cont
,
513 "failed to request GPIO%d for backlight_cont\n",
514 pdata
->gpio_backlight_cont
);
518 lcd
->gpio_backlight_cont
= pdata
->gpio_backlight_cont
;
520 /* spitz and akita use both GPIOs for backlight, and
521 * have inverted polarity of GPIO_BACKLIGHT_CONT
523 if (gpio_is_valid(lcd
->gpio_backlight_on
)) {
524 lcd
->gpio_backlight_cont_inverted
= 1;
525 gpio_direction_output(lcd
->gpio_backlight_cont
, 1);
527 lcd
->gpio_backlight_cont_inverted
= 0;
528 gpio_direction_output(lcd
->gpio_backlight_cont
, 0);
534 static int corgi_lcd_probe(struct spi_device
*spi
)
536 struct backlight_properties props
;
537 struct corgi_lcd_platform_data
*pdata
= dev_get_platdata(&spi
->dev
);
538 struct corgi_lcd
*lcd
;
542 dev_err(&spi
->dev
, "platform data not available\n");
546 lcd
= devm_kzalloc(&spi
->dev
, sizeof(struct corgi_lcd
), GFP_KERNEL
);
552 lcd
->lcd_dev
= devm_lcd_device_register(&spi
->dev
, "corgi_lcd",
553 &spi
->dev
, lcd
, &corgi_lcd_ops
);
554 if (IS_ERR(lcd
->lcd_dev
))
555 return PTR_ERR(lcd
->lcd_dev
);
557 lcd
->power
= FB_BLANK_POWERDOWN
;
558 lcd
->mode
= (pdata
) ? pdata
->init_mode
: CORGI_LCD_MODE_VGA
;
560 memset(&props
, 0, sizeof(struct backlight_properties
));
561 props
.type
= BACKLIGHT_RAW
;
562 props
.max_brightness
= pdata
->max_intensity
;
563 lcd
->bl_dev
= devm_backlight_device_register(&spi
->dev
, "corgi_bl",
564 &spi
->dev
, lcd
, &corgi_bl_ops
,
566 if (IS_ERR(lcd
->bl_dev
))
567 return PTR_ERR(lcd
->bl_dev
);
569 lcd
->bl_dev
->props
.brightness
= pdata
->default_intensity
;
570 lcd
->bl_dev
->props
.power
= FB_BLANK_UNBLANK
;
572 ret
= setup_gpio_backlight(lcd
, pdata
);
576 lcd
->kick_battery
= pdata
->kick_battery
;
578 spi_set_drvdata(spi
, lcd
);
579 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_UNBLANK
);
580 backlight_update_status(lcd
->bl_dev
);
582 lcd
->limit_mask
= pdata
->limit_mask
;
587 static int corgi_lcd_remove(struct spi_device
*spi
)
589 struct corgi_lcd
*lcd
= spi_get_drvdata(spi
);
591 lcd
->bl_dev
->props
.power
= FB_BLANK_UNBLANK
;
592 lcd
->bl_dev
->props
.brightness
= 0;
593 backlight_update_status(lcd
->bl_dev
);
594 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_POWERDOWN
);
598 static struct spi_driver corgi_lcd_driver
= {
601 .pm
= &corgi_lcd_pm_ops
,
603 .probe
= corgi_lcd_probe
,
604 .remove
= corgi_lcd_remove
,
607 module_spi_driver(corgi_lcd_driver
);
609 MODULE_DESCRIPTION("LCD and backlight driver for SHARP C7x0/Cxx00");
610 MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
611 MODULE_LICENSE("GPL");
612 MODULE_ALIAS("spi:corgi-lcd");