1 // SPDX-License-Identifier: GPL-2.0-only
3 * LCD/Backlight Driver for Sharp Zaurus Handhelds (various models)
5 * Copyright (c) 2004-2006 Richard Purdie
7 * Based on Sharp's 2.4 Backlight Driver
9 * Copyright (c) 2008 Marvell International Ltd.
10 * Converted to SPI device based LCD/Backlight device driver
11 * by Eric Miao <eric.miao@marvell.com>
14 #include <linux/module.h>
15 #include <linux/kernel.h>
16 #include <linux/init.h>
17 #include <linux/delay.h>
18 #include <linux/gpio/consumer.h>
20 #include <linux/lcd.h>
21 #include <linux/spi/spi.h>
22 #include <linux/spi/corgi_lcd.h>
23 #include <linux/slab.h>
24 #include <asm/mach/sharpsl_param.h>
26 #define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
28 /* Register Addresses */
29 #define RESCTL_ADRS 0x00
30 #define PHACTRL_ADRS 0x01
31 #define DUTYCTRL_ADRS 0x02
32 #define POWERREG0_ADRS 0x03
33 #define POWERREG1_ADRS 0x04
34 #define GPOR3_ADRS 0x05
35 #define PICTRL_ADRS 0x06
36 #define POLCTRL_ADRS 0x07
38 /* Register Bit Definitions */
39 #define RESCTL_QVGA 0x01
40 #define RESCTL_VGA 0x00
42 #define POWER1_VW_ON 0x01 /* VW Supply FET ON */
43 #define POWER1_GVSS_ON 0x02 /* GVSS(-8V) Power Supply ON */
44 #define POWER1_VDD_ON 0x04 /* VDD(8V),SVSS(-4V) Power Supply ON */
46 #define POWER1_VW_OFF 0x00 /* VW Supply FET OFF */
47 #define POWER1_GVSS_OFF 0x00 /* GVSS(-8V) Power Supply OFF */
48 #define POWER1_VDD_OFF 0x00 /* VDD(8V),SVSS(-4V) Power Supply OFF */
50 #define POWER0_COM_DCLK 0x01 /* COM Voltage DC Bias DAC Serial Data Clock */
51 #define POWER0_COM_DOUT 0x02 /* COM Voltage DC Bias DAC Serial Data Out */
52 #define POWER0_DAC_ON 0x04 /* DAC Power Supply ON */
53 #define POWER0_COM_ON 0x08 /* COM Power Supply ON */
54 #define POWER0_VCC5_ON 0x10 /* VCC5 Power Supply ON */
56 #define POWER0_DAC_OFF 0x00 /* DAC Power Supply OFF */
57 #define POWER0_COM_OFF 0x00 /* COM Power Supply OFF */
58 #define POWER0_VCC5_OFF 0x00 /* VCC5 Power Supply OFF */
60 #define PICTRL_INIT_STATE 0x01
61 #define PICTRL_INIOFF 0x02
62 #define PICTRL_POWER_DOWN 0x04
63 #define PICTRL_COM_SIGNAL_OFF 0x08
64 #define PICTRL_DAC_SIGNAL_OFF 0x10
66 #define POLCTRL_SYNC_POL_FALL 0x01
67 #define POLCTRL_EN_POL_FALL 0x02
68 #define POLCTRL_DATA_POL_FALL 0x04
69 #define POLCTRL_SYNC_ACT_H 0x08
70 #define POLCTRL_EN_ACT_L 0x10
72 #define POLCTRL_SYNC_POL_RISE 0x00
73 #define POLCTRL_EN_POL_RISE 0x00
74 #define POLCTRL_DATA_POL_RISE 0x00
75 #define POLCTRL_SYNC_ACT_L 0x00
76 #define POLCTRL_EN_ACT_H 0x00
78 #define PHACTRL_PHASE_MANUAL 0x01
79 #define DEFAULT_PHAD_QVGA (9)
80 #define DEFAULT_COMADJ (125)
83 struct spi_device
*spi_dev
;
84 struct lcd_device
*lcd_dev
;
85 struct backlight_device
*bl_dev
;
93 struct gpio_desc
*backlight_on
;
94 struct gpio_desc
*backlight_cont
;
96 void (*kick_battery
)(void);
99 static int corgi_ssp_lcdtg_send(struct corgi_lcd
*lcd
, int reg
, uint8_t val
);
101 static struct corgi_lcd
*the_corgi_lcd
;
102 static unsigned long corgibl_flags
;
103 #define CORGIBL_SUSPENDED 0x01
104 #define CORGIBL_BATTLOW 0x02
107 * This is only a pseudo I2C interface. We can't use the standard kernel
108 * routines as the interface is write only. We just assume the data is acked...
110 static void lcdtg_ssp_i2c_send(struct corgi_lcd
*lcd
, uint8_t data
)
112 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
, data
);
116 static void lcdtg_i2c_send_bit(struct corgi_lcd
*lcd
, uint8_t data
)
118 lcdtg_ssp_i2c_send(lcd
, data
);
119 lcdtg_ssp_i2c_send(lcd
, data
| POWER0_COM_DCLK
);
120 lcdtg_ssp_i2c_send(lcd
, data
);
123 static void lcdtg_i2c_send_start(struct corgi_lcd
*lcd
, uint8_t base
)
125 lcdtg_ssp_i2c_send(lcd
, base
| POWER0_COM_DCLK
| POWER0_COM_DOUT
);
126 lcdtg_ssp_i2c_send(lcd
, base
| POWER0_COM_DCLK
);
127 lcdtg_ssp_i2c_send(lcd
, base
);
130 static void lcdtg_i2c_send_stop(struct corgi_lcd
*lcd
, uint8_t base
)
132 lcdtg_ssp_i2c_send(lcd
, base
);
133 lcdtg_ssp_i2c_send(lcd
, base
| POWER0_COM_DCLK
);
134 lcdtg_ssp_i2c_send(lcd
, base
| POWER0_COM_DCLK
| POWER0_COM_DOUT
);
137 static void lcdtg_i2c_send_byte(struct corgi_lcd
*lcd
,
138 uint8_t base
, uint8_t data
)
142 for (i
= 0; i
< 8; i
++) {
144 lcdtg_i2c_send_bit(lcd
, base
| POWER0_COM_DOUT
);
146 lcdtg_i2c_send_bit(lcd
, base
);
151 static void lcdtg_i2c_wait_ack(struct corgi_lcd
*lcd
, uint8_t base
)
153 lcdtg_i2c_send_bit(lcd
, base
);
156 static void lcdtg_set_common_voltage(struct corgi_lcd
*lcd
,
157 uint8_t base_data
, uint8_t data
)
159 /* Set Common Voltage to M62332FP via I2C */
160 lcdtg_i2c_send_start(lcd
, base_data
);
161 lcdtg_i2c_send_byte(lcd
, base_data
, 0x9c);
162 lcdtg_i2c_wait_ack(lcd
, base_data
);
163 lcdtg_i2c_send_byte(lcd
, base_data
, 0x00);
164 lcdtg_i2c_wait_ack(lcd
, base_data
);
165 lcdtg_i2c_send_byte(lcd
, base_data
, data
);
166 lcdtg_i2c_wait_ack(lcd
, base_data
);
167 lcdtg_i2c_send_stop(lcd
, base_data
);
170 static int corgi_ssp_lcdtg_send(struct corgi_lcd
*lcd
, int adrs
, uint8_t data
)
172 struct spi_message msg
;
173 struct spi_transfer xfer
= {
179 lcd
->buf
[0] = ((adrs
& 0x07) << 5) | (data
& 0x1f);
180 spi_message_init(&msg
);
181 spi_message_add_tail(&xfer
, &msg
);
183 return spi_sync(lcd
->spi_dev
, &msg
);
186 /* Set Phase Adjust */
187 static void lcdtg_set_phadadj(struct corgi_lcd
*lcd
, int mode
)
192 case CORGI_LCD_MODE_VGA
:
193 /* Setting for VGA */
194 adj
= sharpsl_param
.phadadj
;
195 adj
= (adj
< 0) ? PHACTRL_PHASE_MANUAL
:
196 PHACTRL_PHASE_MANUAL
| ((adj
& 0xf) << 1);
198 case CORGI_LCD_MODE_QVGA
:
200 /* Setting for QVGA */
201 adj
= (DEFAULT_PHAD_QVGA
<< 1) | PHACTRL_PHASE_MANUAL
;
205 corgi_ssp_lcdtg_send(lcd
, PHACTRL_ADRS
, adj
);
208 static void corgi_lcd_power_on(struct corgi_lcd
*lcd
)
212 /* Initialize Internal Logic & Port */
213 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
214 PICTRL_POWER_DOWN
| PICTRL_INIOFF
|
215 PICTRL_INIT_STATE
| PICTRL_COM_SIGNAL_OFF
|
216 PICTRL_DAC_SIGNAL_OFF
);
218 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
219 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_OFF
|
220 POWER0_COM_OFF
| POWER0_VCC5_OFF
);
222 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
223 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_OFF
);
225 /* VDD(+8V), SVSS(-4V) ON */
226 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
227 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_ON
);
231 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
232 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
233 POWER0_COM_OFF
| POWER0_VCC5_OFF
);
235 /* INIB = H, INI = L */
236 /* PICTL[0] = H , PICTL[1] = PICTL[2] = PICTL[4] = L */
237 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
238 PICTRL_INIT_STATE
| PICTRL_COM_SIGNAL_OFF
);
240 /* Set Common Voltage */
241 comadj
= sharpsl_param
.comadj
;
243 comadj
= DEFAULT_COMADJ
;
245 lcdtg_set_common_voltage(lcd
, POWER0_DAC_ON
| POWER0_COM_OFF
|
246 POWER0_VCC5_OFF
, comadj
);
248 /* VCC5 ON, DAC ON */
249 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
250 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
251 POWER0_COM_OFF
| POWER0_VCC5_ON
);
253 /* GVSS(-8V) ON, VDD ON */
254 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
255 POWER1_VW_OFF
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
258 /* COM SIGNAL ON (PICTL[3] = L) */
259 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, PICTRL_INIT_STATE
);
261 /* COM ON, DAC ON, VCC5_ON */
262 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
263 POWER0_COM_DCLK
| POWER0_COM_DOUT
| POWER0_DAC_ON
|
264 POWER0_COM_ON
| POWER0_VCC5_ON
);
266 /* VW ON, GVSS ON, VDD ON */
267 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
268 POWER1_VW_ON
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
270 /* Signals output enable */
271 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, 0);
273 /* Set Phase Adjust */
274 lcdtg_set_phadadj(lcd
, lcd
->mode
);
276 /* Initialize for Input Signals from ATI */
277 corgi_ssp_lcdtg_send(lcd
, POLCTRL_ADRS
,
278 POLCTRL_SYNC_POL_RISE
| POLCTRL_EN_POL_RISE
|
279 POLCTRL_DATA_POL_RISE
| POLCTRL_SYNC_ACT_L
|
284 case CORGI_LCD_MODE_VGA
:
285 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_VGA
);
287 case CORGI_LCD_MODE_QVGA
:
289 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_QVGA
);
294 static void corgi_lcd_power_off(struct corgi_lcd
*lcd
)
296 /* 60Hz x 2 frame = 16.7msec x 2 = 33.4 msec */
300 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
301 POWER1_VW_OFF
| POWER1_GVSS_ON
| POWER1_VDD_ON
);
304 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
, PICTRL_COM_SIGNAL_OFF
);
305 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
306 POWER0_DAC_ON
| POWER0_COM_OFF
| POWER0_VCC5_ON
);
308 /* (3)Set Common Voltage Bias 0V */
309 lcdtg_set_common_voltage(lcd
, POWER0_DAC_ON
| POWER0_COM_OFF
|
313 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
314 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_ON
);
317 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
318 POWER0_DAC_ON
| POWER0_COM_OFF
| POWER0_VCC5_OFF
);
320 /* (6)Set PDWN, INIOFF, DACOFF */
321 corgi_ssp_lcdtg_send(lcd
, PICTRL_ADRS
,
322 PICTRL_INIOFF
| PICTRL_DAC_SIGNAL_OFF
|
323 PICTRL_POWER_DOWN
| PICTRL_COM_SIGNAL_OFF
);
326 corgi_ssp_lcdtg_send(lcd
, POWERREG0_ADRS
,
327 POWER0_DAC_OFF
| POWER0_COM_OFF
| POWER0_VCC5_OFF
);
330 corgi_ssp_lcdtg_send(lcd
, POWERREG1_ADRS
,
331 POWER1_VW_OFF
| POWER1_GVSS_OFF
| POWER1_VDD_OFF
);
334 static int corgi_lcd_set_mode(struct lcd_device
*ld
, struct fb_videomode
*m
)
336 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
337 int mode
= CORGI_LCD_MODE_QVGA
;
339 if (m
->xres
== 640 || m
->xres
== 480)
340 mode
= CORGI_LCD_MODE_VGA
;
342 if (lcd
->mode
== mode
)
345 lcdtg_set_phadadj(lcd
, mode
);
348 case CORGI_LCD_MODE_VGA
:
349 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_VGA
);
351 case CORGI_LCD_MODE_QVGA
:
353 corgi_ssp_lcdtg_send(lcd
, RESCTL_ADRS
, RESCTL_QVGA
);
361 static int corgi_lcd_set_power(struct lcd_device
*ld
, int power
)
363 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
365 if (POWER_IS_ON(power
) && !POWER_IS_ON(lcd
->power
))
366 corgi_lcd_power_on(lcd
);
368 if (!POWER_IS_ON(power
) && POWER_IS_ON(lcd
->power
))
369 corgi_lcd_power_off(lcd
);
375 static int corgi_lcd_get_power(struct lcd_device
*ld
)
377 struct corgi_lcd
*lcd
= lcd_get_data(ld
);
382 static struct lcd_ops corgi_lcd_ops
= {
383 .get_power
= corgi_lcd_get_power
,
384 .set_power
= corgi_lcd_set_power
,
385 .set_mode
= corgi_lcd_set_mode
,
388 static int corgi_bl_get_intensity(struct backlight_device
*bd
)
390 struct corgi_lcd
*lcd
= bl_get_data(bd
);
392 return lcd
->intensity
;
395 static int corgi_bl_set_intensity(struct corgi_lcd
*lcd
, int intensity
)
399 if (intensity
> 0x10)
402 corgi_ssp_lcdtg_send(lcd
, DUTYCTRL_ADRS
, intensity
);
404 /* Bit 5 via GPIO_BACKLIGHT_CONT */
405 cont
= !!(intensity
& 0x20);
407 if (lcd
->backlight_cont
)
408 gpiod_set_value_cansleep(lcd
->backlight_cont
, cont
);
410 if (lcd
->backlight_on
)
411 gpiod_set_value_cansleep(lcd
->backlight_on
, intensity
);
413 if (lcd
->kick_battery
)
416 lcd
->intensity
= intensity
;
420 static int corgi_bl_update_status(struct backlight_device
*bd
)
422 struct corgi_lcd
*lcd
= bl_get_data(bd
);
423 int intensity
= backlight_get_brightness(bd
);
425 if (corgibl_flags
& CORGIBL_SUSPENDED
)
428 if ((corgibl_flags
& CORGIBL_BATTLOW
) && intensity
> lcd
->limit_mask
)
429 intensity
= lcd
->limit_mask
;
431 return corgi_bl_set_intensity(lcd
, intensity
);
434 void corgi_lcd_limit_intensity(int limit
)
437 corgibl_flags
|= CORGIBL_BATTLOW
;
439 corgibl_flags
&= ~CORGIBL_BATTLOW
;
441 backlight_update_status(the_corgi_lcd
->bl_dev
);
443 EXPORT_SYMBOL(corgi_lcd_limit_intensity
);
445 static const struct backlight_ops corgi_bl_ops
= {
446 .get_brightness
= corgi_bl_get_intensity
,
447 .update_status
= corgi_bl_update_status
,
450 #ifdef CONFIG_PM_SLEEP
451 static int corgi_lcd_suspend(struct device
*dev
)
453 struct corgi_lcd
*lcd
= dev_get_drvdata(dev
);
455 corgibl_flags
|= CORGIBL_SUSPENDED
;
456 corgi_bl_set_intensity(lcd
, 0);
457 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_POWERDOWN
);
461 static int corgi_lcd_resume(struct device
*dev
)
463 struct corgi_lcd
*lcd
= dev_get_drvdata(dev
);
465 corgibl_flags
&= ~CORGIBL_SUSPENDED
;
466 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_UNBLANK
);
467 backlight_update_status(lcd
->bl_dev
);
472 static SIMPLE_DEV_PM_OPS(corgi_lcd_pm_ops
, corgi_lcd_suspend
, corgi_lcd_resume
);
474 static int setup_gpio_backlight(struct corgi_lcd
*lcd
,
475 struct corgi_lcd_platform_data
*pdata
)
477 struct spi_device
*spi
= lcd
->spi_dev
;
479 lcd
->backlight_on
= devm_gpiod_get_optional(&spi
->dev
,
480 "BL_ON", GPIOD_OUT_LOW
);
481 if (IS_ERR(lcd
->backlight_on
))
482 return PTR_ERR(lcd
->backlight_on
);
484 lcd
->backlight_cont
= devm_gpiod_get_optional(&spi
->dev
, "BL_CONT",
486 if (IS_ERR(lcd
->backlight_cont
))
487 return PTR_ERR(lcd
->backlight_cont
);
492 static int corgi_lcd_probe(struct spi_device
*spi
)
494 struct backlight_properties props
;
495 struct corgi_lcd_platform_data
*pdata
= dev_get_platdata(&spi
->dev
);
496 struct corgi_lcd
*lcd
;
500 dev_err(&spi
->dev
, "platform data not available\n");
504 lcd
= devm_kzalloc(&spi
->dev
, sizeof(struct corgi_lcd
), GFP_KERNEL
);
510 lcd
->lcd_dev
= devm_lcd_device_register(&spi
->dev
, "corgi_lcd",
511 &spi
->dev
, lcd
, &corgi_lcd_ops
);
512 if (IS_ERR(lcd
->lcd_dev
))
513 return PTR_ERR(lcd
->lcd_dev
);
515 lcd
->power
= FB_BLANK_POWERDOWN
;
516 lcd
->mode
= (pdata
) ? pdata
->init_mode
: CORGI_LCD_MODE_VGA
;
518 memset(&props
, 0, sizeof(struct backlight_properties
));
519 props
.type
= BACKLIGHT_RAW
;
520 props
.max_brightness
= pdata
->max_intensity
;
521 lcd
->bl_dev
= devm_backlight_device_register(&spi
->dev
, "corgi_bl",
522 &spi
->dev
, lcd
, &corgi_bl_ops
,
524 if (IS_ERR(lcd
->bl_dev
))
525 return PTR_ERR(lcd
->bl_dev
);
527 lcd
->bl_dev
->props
.brightness
= pdata
->default_intensity
;
528 lcd
->bl_dev
->props
.power
= FB_BLANK_UNBLANK
;
530 ret
= setup_gpio_backlight(lcd
, pdata
);
534 lcd
->kick_battery
= pdata
->kick_battery
;
536 spi_set_drvdata(spi
, lcd
);
537 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_UNBLANK
);
538 backlight_update_status(lcd
->bl_dev
);
540 lcd
->limit_mask
= pdata
->limit_mask
;
545 static int corgi_lcd_remove(struct spi_device
*spi
)
547 struct corgi_lcd
*lcd
= spi_get_drvdata(spi
);
549 lcd
->bl_dev
->props
.power
= FB_BLANK_UNBLANK
;
550 lcd
->bl_dev
->props
.brightness
= 0;
551 backlight_update_status(lcd
->bl_dev
);
552 corgi_lcd_set_power(lcd
->lcd_dev
, FB_BLANK_POWERDOWN
);
556 static struct spi_driver corgi_lcd_driver
= {
559 .pm
= &corgi_lcd_pm_ops
,
561 .probe
= corgi_lcd_probe
,
562 .remove
= corgi_lcd_remove
,
565 module_spi_driver(corgi_lcd_driver
);
567 MODULE_DESCRIPTION("LCD and backlight driver for SHARP C7x0/Cxx00");
568 MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
569 MODULE_LICENSE("GPL");
570 MODULE_ALIAS("spi:corgi-lcd");