diff options
author | Eric Miao <eric.miao@marvell.com> | 2008-08-28 16:21:44 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2008-09-23 17:04:30 -0400 |
commit | b18250a8f66050bd2a52287cd543fb93100e8ee0 (patch) | |
tree | 20e0d142575d3147d2c58542d5bd8ed377e5f3cb /drivers/video | |
parent | faa312da9cd0b044bdc84483162c6ee10b9c83c0 (diff) |
lcd: add SPI-based LCD and backlight driver for SHARP corgi/spitz
The driver is based on different source files including corgi_ssp.c,
corgi_lcd.c and corgi_bl.c, previously authored by Richard Purdie
and many others.
The LCD and Backlight device actually share the same SPI device, so
they are made into this single driver.
Signed-off-by: Eric Miao <eric.miao@marvell.com>
Cc: Richard Purdie <rpurdie@rpsys.net>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/backlight/Kconfig | 7 | ||||
-rw-r--r-- | drivers/video/backlight/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/backlight/corgi_lcd.c | 541 |
3 files changed, 549 insertions, 0 deletions
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 452b770d8cc9..f5406cd78e28 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig | |||
@@ -24,6 +24,13 @@ config LCD_CLASS_DEVICE | |||
24 | To have support for your specific LCD panel you will have to | 24 | To have support for your specific LCD panel you will have to |
25 | select the proper drivers which depend on this option. | 25 | select the proper drivers which depend on this option. |
26 | 26 | ||
27 | config LCD_CORGI | ||
28 | tristate "LCD Panel support for SHARP corgi/spitz model" | ||
29 | depends on LCD_CLASS_DEVICE && SPI_MASTER && PXA_SHARPSL | ||
30 | help | ||
31 | Say y here to support the LCD panels usually found on SHARP | ||
32 | corgi (C7x0) and spitz (Cxx00) models. | ||
33 | |||
27 | config LCD_LTV350QV | 34 | config LCD_LTV350QV |
28 | tristate "Samsung LTV350QV LCD Panel" | 35 | tristate "Samsung LTV350QV LCD Panel" |
29 | depends on LCD_CLASS_DEVICE && SPI_MASTER | 36 | depends on LCD_CLASS_DEVICE && SPI_MASTER |
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index b405aace803f..cf12d58392e0 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile | |||
@@ -1,6 +1,7 @@ | |||
1 | # Backlight & LCD drivers | 1 | # Backlight & LCD drivers |
2 | 2 | ||
3 | obj-$(CONFIG_LCD_CLASS_DEVICE) += lcd.o | 3 | obj-$(CONFIG_LCD_CLASS_DEVICE) += lcd.o |
4 | obj-$(CONFIG_LCD_CORGI) += corgi_lcd.o | ||
4 | obj-$(CONFIG_LCD_LTV350QV) += ltv350qv.o | 5 | obj-$(CONFIG_LCD_LTV350QV) += ltv350qv.o |
5 | obj-$(CONFIG_LCD_ILI9320) += ili9320.o | 6 | obj-$(CONFIG_LCD_ILI9320) += ili9320.o |
6 | obj-$(CONFIG_LCD_PLATFORM) += platform_lcd.o | 7 | obj-$(CONFIG_LCD_PLATFORM) += platform_lcd.o |
diff --git a/drivers/video/backlight/corgi_lcd.c b/drivers/video/backlight/corgi_lcd.c new file mode 100644 index 000000000000..bf69e50d262e --- /dev/null +++ b/drivers/video/backlight/corgi_lcd.c | |||
@@ -0,0 +1,541 @@ | |||
1 | /* | ||
2 | * LCD/Backlight Driver for Sharp Zaurus Handhelds (various models) | ||
3 | * | ||
4 | * Copyright (c) 2004-2006 Richard Purdie | ||
5 | * | ||
6 | * Based on Sharp's 2.4 Backlight Driver | ||
7 | * | ||
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> | ||
11 | * | ||
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. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/delay.h> | ||
22 | #include <linux/fb.h> | ||
23 | #include <linux/lcd.h> | ||
24 | #include <linux/spi/spi.h> | ||
25 | #include <linux/spi/corgi_lcd.h> | ||
26 | #include <asm/mach/sharpsl_param.h> | ||
27 | |||
28 | #define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL) | ||
29 | |||
30 | /* Register Addresses */ | ||
31 | #define RESCTL_ADRS 0x00 | ||
32 | #define PHACTRL_ADRS 0x01 | ||
33 | #define DUTYCTRL_ADRS 0x02 | ||
34 | #define POWERREG0_ADRS 0x03 | ||
35 | #define POWERREG1_ADRS 0x04 | ||
36 | #define GPOR3_ADRS 0x05 | ||
37 | #define PICTRL_ADRS 0x06 | ||
38 | #define POLCTRL_ADRS 0x07 | ||
39 | |||
40 | /* Register Bit Definitions */ | ||
41 | #define RESCTL_QVGA 0x01 | ||
42 | #define RESCTL_VGA 0x00 | ||
43 | |||
44 | #define POWER1_VW_ON 0x01 /* VW Supply FET ON */ | ||
45 | #define POWER1_GVSS_ON 0x02 /* GVSS(-8V) Power Supply ON */ | ||
46 | #define POWER1_VDD_ON 0x04 /* VDD(8V),SVSS(-4V) Power Supply ON */ | ||
47 | |||
48 | #define POWER1_VW_OFF 0x00 /* VW Supply FET OFF */ | ||
49 | #define POWER1_GVSS_OFF 0x00 /* GVSS(-8V) Power Supply OFF */ | ||
50 | #define POWER1_VDD_OFF 0x00 /* VDD(8V),SVSS(-4V) Power Supply OFF */ | ||
51 | |||
52 | #define POWER0_COM_DCLK 0x01 /* COM Voltage DC Bias DAC Serial Data Clock */ | ||
53 | #define POWER0_COM_DOUT 0x02 /* COM Voltage DC Bias DAC Serial Data Out */ | ||
54 | #define POWER0_DAC_ON 0x04 /* DAC Power Supply ON */ | ||
55 | #define POWER0_COM_ON 0x08 /* COM Power Supply ON */ | ||
56 | #define POWER0_VCC5_ON 0x10 /* VCC5 Power Supply ON */ | ||
57 | |||
58 | #define POWER0_DAC_OFF 0x00 /* DAC Power Supply OFF */ | ||
59 | #define POWER0_COM_OFF 0x00 /* COM Power Supply OFF */ | ||
60 | #define POWER0_VCC5_OFF 0x00 /* VCC5 Power Supply OFF */ | ||
61 | |||
62 | #define PICTRL_INIT_STATE 0x01 | ||
63 | #define PICTRL_INIOFF 0x02 | ||
64 | #define PICTRL_POWER_DOWN 0x04 | ||
65 | #define PICTRL_COM_SIGNAL_OFF 0x08 | ||
66 | #define PICTRL_DAC_SIGNAL_OFF 0x10 | ||
67 | |||
68 | #define POLCTRL_SYNC_POL_FALL 0x01 | ||
69 | #define POLCTRL_EN_POL_FALL 0x02 | ||
70 | #define POLCTRL_DATA_POL_FALL 0x04 | ||
71 | #define POLCTRL_SYNC_ACT_H 0x08 | ||
72 | #define POLCTRL_EN_ACT_L 0x10 | ||
73 | |||
74 | #define POLCTRL_SYNC_POL_RISE 0x00 | ||
75 | #define POLCTRL_EN_POL_RISE 0x00 | ||
76 | #define POLCTRL_DATA_POL_RISE 0x00 | ||
77 | #define POLCTRL_SYNC_ACT_L 0x00 | ||
78 | #define POLCTRL_EN_ACT_H 0x00 | ||
79 | |||
80 | #define PHACTRL_PHASE_MANUAL 0x01 | ||
81 | #define DEFAULT_PHAD_QVGA (9) | ||
82 | #define DEFAULT_COMADJ (125) | ||
83 | |||
84 | struct corgi_lcd { | ||
85 | struct spi_device *spi_dev; | ||
86 | struct lcd_device *lcd_dev; | ||
87 | struct backlight_device *bl_dev; | ||
88 | |||
89 | int intensity; | ||
90 | int power; | ||
91 | int mode; | ||
92 | char buf[2]; | ||
93 | |||
94 | void (*notify)(int intensity); | ||
95 | void (*kick_battery)(void); | ||
96 | }; | ||
97 | |||
98 | static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int reg, uint8_t val); | ||
99 | |||
100 | /* | ||
101 | * This is only a psuedo I2C interface. We can't use the standard kernel | ||
102 | * routines as the interface is write only. We just assume the data is acked... | ||
103 | */ | ||
104 | static void lcdtg_ssp_i2c_send(struct corgi_lcd *lcd, uint8_t data) | ||
105 | { | ||
106 | corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, data); | ||
107 | udelay(10); | ||
108 | } | ||
109 | |||
110 | static void lcdtg_i2c_send_bit(struct corgi_lcd *lcd, uint8_t data) | ||
111 | { | ||
112 | lcdtg_ssp_i2c_send(lcd, data); | ||
113 | lcdtg_ssp_i2c_send(lcd, data | POWER0_COM_DCLK); | ||
114 | lcdtg_ssp_i2c_send(lcd, data); | ||
115 | } | ||
116 | |||
117 | static void lcdtg_i2c_send_start(struct corgi_lcd *lcd, uint8_t base) | ||
118 | { | ||
119 | lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK | POWER0_COM_DOUT); | ||
120 | lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK); | ||
121 | lcdtg_ssp_i2c_send(lcd, base); | ||
122 | } | ||
123 | |||
124 | static void lcdtg_i2c_send_stop(struct corgi_lcd *lcd, uint8_t base) | ||
125 | { | ||
126 | lcdtg_ssp_i2c_send(lcd, base); | ||
127 | lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK); | ||
128 | lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK | POWER0_COM_DOUT); | ||
129 | } | ||
130 | |||
131 | static void lcdtg_i2c_send_byte(struct corgi_lcd *lcd, | ||
132 | uint8_t base, uint8_t data) | ||
133 | { | ||
134 | int i; | ||
135 | for (i = 0; i < 8; i++) { | ||
136 | if (data & 0x80) | ||
137 | lcdtg_i2c_send_bit(lcd, base | POWER0_COM_DOUT); | ||
138 | else | ||
139 | lcdtg_i2c_send_bit(lcd, base); | ||
140 | data <<= 1; | ||
141 | } | ||
142 | } | ||
143 | |||
144 | static void lcdtg_i2c_wait_ack(struct corgi_lcd *lcd, uint8_t base) | ||
145 | { | ||
146 | lcdtg_i2c_send_bit(lcd, base); | ||
147 | } | ||
148 | |||
149 | static void lcdtg_set_common_voltage(struct corgi_lcd *lcd, | ||
150 | uint8_t base_data, uint8_t data) | ||
151 | { | ||
152 | /* Set Common Voltage to M62332FP via I2C */ | ||
153 | lcdtg_i2c_send_start(lcd, base_data); | ||
154 | lcdtg_i2c_send_byte(lcd, base_data, 0x9c); | ||
155 | lcdtg_i2c_wait_ack(lcd, base_data); | ||
156 | lcdtg_i2c_send_byte(lcd, base_data, 0x00); | ||
157 | lcdtg_i2c_wait_ack(lcd, base_data); | ||
158 | lcdtg_i2c_send_byte(lcd, base_data, data); | ||
159 | lcdtg_i2c_wait_ack(lcd, base_data); | ||
160 | lcdtg_i2c_send_stop(lcd, base_data); | ||
161 | } | ||
162 | |||
163 | static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int adrs, uint8_t data) | ||
164 | { | ||
165 | struct spi_message msg; | ||
166 | struct spi_transfer xfer = { | ||
167 | .len = 1, | ||
168 | .cs_change = 1, | ||
169 | .tx_buf = lcd->buf, | ||
170 | }; | ||
171 | |||
172 | lcd->buf[0] = ((adrs & 0x07) << 5) | (data & 0x1f); | ||
173 | spi_message_init(&msg); | ||
174 | spi_message_add_tail(&xfer, &msg); | ||
175 | |||
176 | return spi_sync(lcd->spi_dev, &msg); | ||
177 | } | ||
178 | |||
179 | /* Set Phase Adjust */ | ||
180 | static void lcdtg_set_phadadj(struct corgi_lcd *lcd, int mode) | ||
181 | { | ||
182 | int adj; | ||
183 | |||
184 | switch(mode) { | ||
185 | case CORGI_LCD_MODE_VGA: | ||
186 | /* Setting for VGA */ | ||
187 | adj = sharpsl_param.phadadj; | ||
188 | adj = (adj < 0) ? PHACTRL_PHASE_MANUAL : | ||
189 | PHACTRL_PHASE_MANUAL | ((adj & 0xf) << 1); | ||
190 | break; | ||
191 | case CORGI_LCD_MODE_QVGA: | ||
192 | default: | ||
193 | /* Setting for QVGA */ | ||
194 | adj = (DEFAULT_PHAD_QVGA << 1) | PHACTRL_PHASE_MANUAL; | ||
195 | break; | ||
196 | } | ||
197 | |||
198 | corgi_ssp_lcdtg_send(lcd, PHACTRL_ADRS, adj); | ||
199 | } | ||
200 | |||
201 | static void corgi_lcd_power_on(struct corgi_lcd *lcd) | ||
202 | { | ||
203 | int comadj; | ||
204 | |||
205 | /* Initialize Internal Logic & Port */ | ||
206 | corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, | ||
207 | PICTRL_POWER_DOWN | PICTRL_INIOFF | | ||
208 | PICTRL_INIT_STATE | PICTRL_COM_SIGNAL_OFF | | ||
209 | PICTRL_DAC_SIGNAL_OFF); | ||
210 | |||
211 | corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, | ||
212 | POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_OFF | | ||
213 | POWER0_COM_OFF | POWER0_VCC5_OFF); | ||
214 | |||
215 | corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, | ||
216 | POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_OFF); | ||
217 | |||
218 | /* VDD(+8V), SVSS(-4V) ON */ | ||
219 | corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, | ||
220 | POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_ON); | ||
221 | mdelay(3); | ||
222 | |||
223 | /* DAC ON */ | ||
224 | corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, | ||
225 | POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON | | ||
226 | POWER0_COM_OFF | POWER0_VCC5_OFF); | ||
227 | |||
228 | /* INIB = H, INI = L */ | ||
229 | /* PICTL[0] = H , PICTL[1] = PICTL[2] = PICTL[4] = L */ | ||
230 | corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, | ||
231 | PICTRL_INIT_STATE | PICTRL_COM_SIGNAL_OFF); | ||
232 | |||
233 | /* Set Common Voltage */ | ||
234 | comadj = sharpsl_param.comadj; | ||
235 | if (comadj < 0) | ||
236 | comadj = DEFAULT_COMADJ; | ||
237 | |||
238 | lcdtg_set_common_voltage(lcd, POWER0_DAC_ON | POWER0_COM_OFF | | ||
239 | POWER0_VCC5_OFF, comadj); | ||
240 | |||
241 | /* VCC5 ON, DAC ON */ | ||
242 | corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, | ||
243 | POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON | | ||
244 | POWER0_COM_OFF | POWER0_VCC5_ON); | ||
245 | |||
246 | /* GVSS(-8V) ON, VDD ON */ | ||
247 | corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, | ||
248 | POWER1_VW_OFF | POWER1_GVSS_ON | POWER1_VDD_ON); | ||
249 | mdelay(2); | ||
250 | |||
251 | /* COM SIGNAL ON (PICTL[3] = L) */ | ||
252 | corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, PICTRL_INIT_STATE); | ||
253 | |||
254 | /* COM ON, DAC ON, VCC5_ON */ | ||
255 | corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, | ||
256 | POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON | | ||
257 | POWER0_COM_ON | POWER0_VCC5_ON); | ||
258 | |||
259 | /* VW ON, GVSS ON, VDD ON */ | ||
260 | corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, | ||
261 | POWER1_VW_ON | POWER1_GVSS_ON | POWER1_VDD_ON); | ||
262 | |||
263 | /* Signals output enable */ | ||
264 | corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, 0); | ||
265 | |||
266 | /* Set Phase Adjust */ | ||
267 | lcdtg_set_phadadj(lcd, lcd->mode); | ||
268 | |||
269 | /* Initialize for Input Signals from ATI */ | ||
270 | corgi_ssp_lcdtg_send(lcd, POLCTRL_ADRS, | ||
271 | POLCTRL_SYNC_POL_RISE | POLCTRL_EN_POL_RISE | | ||
272 | POLCTRL_DATA_POL_RISE | POLCTRL_SYNC_ACT_L | | ||
273 | POLCTRL_EN_ACT_H); | ||
274 | udelay(1000); | ||
275 | |||
276 | switch (lcd->mode) { | ||
277 | case CORGI_LCD_MODE_VGA: | ||
278 | corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_VGA); | ||
279 | break; | ||
280 | case CORGI_LCD_MODE_QVGA: | ||
281 | default: | ||
282 | corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_QVGA); | ||
283 | break; | ||
284 | } | ||
285 | } | ||
286 | |||
287 | static void corgi_lcd_power_off(struct corgi_lcd *lcd) | ||
288 | { | ||
289 | /* 60Hz x 2 frame = 16.7msec x 2 = 33.4 msec */ | ||
290 | msleep(34); | ||
291 | |||
292 | /* (1)VW OFF */ | ||
293 | corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, | ||
294 | POWER1_VW_OFF | POWER1_GVSS_ON | POWER1_VDD_ON); | ||
295 | |||
296 | /* (2)COM OFF */ | ||
297 | corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, PICTRL_COM_SIGNAL_OFF); | ||
298 | corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, | ||
299 | POWER0_DAC_ON | POWER0_COM_OFF | POWER0_VCC5_ON); | ||
300 | |||
301 | /* (3)Set Common Voltage Bias 0V */ | ||
302 | lcdtg_set_common_voltage(lcd, POWER0_DAC_ON | POWER0_COM_OFF | | ||
303 | POWER0_VCC5_ON, 0); | ||
304 | |||
305 | /* (4)GVSS OFF */ | ||
306 | corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, | ||
307 | POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_ON); | ||
308 | |||
309 | /* (5)VCC5 OFF */ | ||
310 | corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, | ||
311 | POWER0_DAC_ON | POWER0_COM_OFF | POWER0_VCC5_OFF); | ||
312 | |||
313 | /* (6)Set PDWN, INIOFF, DACOFF */ | ||
314 | corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, | ||
315 | PICTRL_INIOFF | PICTRL_DAC_SIGNAL_OFF | | ||
316 | PICTRL_POWER_DOWN | PICTRL_COM_SIGNAL_OFF); | ||
317 | |||
318 | /* (7)DAC OFF */ | ||
319 | corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, | ||
320 | POWER0_DAC_OFF | POWER0_COM_OFF | POWER0_VCC5_OFF); | ||
321 | |||
322 | /* (8)VDD OFF */ | ||
323 | corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, | ||
324 | POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_OFF); | ||
325 | } | ||
326 | |||
327 | static int corgi_lcd_set_mode(struct lcd_device *ld, struct fb_videomode *m) | ||
328 | { | ||
329 | struct corgi_lcd *lcd = dev_get_drvdata(&ld->dev); | ||
330 | int mode = CORGI_LCD_MODE_QVGA; | ||
331 | |||
332 | if (m->xres == 640 || m->xres == 480) | ||
333 | mode = CORGI_LCD_MODE_VGA; | ||
334 | |||
335 | if (lcd->mode == mode) | ||
336 | return 0; | ||
337 | |||
338 | lcdtg_set_phadadj(lcd, mode); | ||
339 | |||
340 | switch (mode) { | ||
341 | case CORGI_LCD_MODE_VGA: | ||
342 | corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_VGA); | ||
343 | break; | ||
344 | case CORGI_LCD_MODE_QVGA: | ||
345 | default: | ||
346 | corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_QVGA); | ||
347 | break; | ||
348 | } | ||
349 | |||
350 | lcd->mode = mode; | ||
351 | return 0; | ||
352 | } | ||
353 | |||
354 | static int corgi_lcd_set_power(struct lcd_device *ld, int power) | ||
355 | { | ||
356 | struct corgi_lcd *lcd = dev_get_drvdata(&ld->dev); | ||
357 | |||
358 | if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power)) | ||
359 | corgi_lcd_power_on(lcd); | ||
360 | |||
361 | if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power)) | ||
362 | corgi_lcd_power_off(lcd); | ||
363 | |||
364 | lcd->power = power; | ||
365 | return 0; | ||
366 | } | ||
367 | |||
368 | static int corgi_lcd_get_power(struct lcd_device *ld) | ||
369 | { | ||
370 | struct corgi_lcd *lcd = dev_get_drvdata(&ld->dev); | ||
371 | |||
372 | return lcd->power; | ||
373 | } | ||
374 | |||
375 | static struct lcd_ops corgi_lcd_ops = { | ||
376 | .get_power = corgi_lcd_get_power, | ||
377 | .set_power = corgi_lcd_set_power, | ||
378 | .set_mode = corgi_lcd_set_mode, | ||
379 | }; | ||
380 | |||
381 | static int corgi_bl_get_intensity(struct backlight_device *bd) | ||
382 | { | ||
383 | struct corgi_lcd *lcd = dev_get_drvdata(&bd->dev); | ||
384 | |||
385 | return lcd->intensity; | ||
386 | } | ||
387 | |||
388 | static int corgi_bl_set_intensity(struct corgi_lcd *lcd, int intensity) | ||
389 | { | ||
390 | if (intensity > 0x10) | ||
391 | intensity += 0x10; | ||
392 | |||
393 | corgi_ssp_lcdtg_send(lcd, DUTYCTRL_ADRS, intensity); | ||
394 | lcd->intensity = intensity; | ||
395 | |||
396 | if (lcd->notify) | ||
397 | lcd->notify(intensity); | ||
398 | |||
399 | if (lcd->kick_battery) | ||
400 | lcd->kick_battery(); | ||
401 | |||
402 | return 0; | ||
403 | } | ||
404 | |||
405 | static int corgi_bl_update_status(struct backlight_device *bd) | ||
406 | { | ||
407 | struct corgi_lcd *lcd = dev_get_drvdata(&bd->dev); | ||
408 | int intensity = bd->props.brightness; | ||
409 | |||
410 | if (bd->props.power != FB_BLANK_UNBLANK) | ||
411 | intensity = 0; | ||
412 | |||
413 | if (bd->props.fb_blank != FB_BLANK_UNBLANK) | ||
414 | intensity = 0; | ||
415 | |||
416 | return corgi_bl_set_intensity(lcd, intensity); | ||
417 | } | ||
418 | |||
419 | static struct backlight_ops corgi_bl_ops = { | ||
420 | .get_brightness = corgi_bl_get_intensity, | ||
421 | .update_status = corgi_bl_update_status, | ||
422 | }; | ||
423 | |||
424 | #ifdef CONFIG_PM | ||
425 | static int corgi_lcd_suspend(struct spi_device *spi, pm_message_t state) | ||
426 | { | ||
427 | struct corgi_lcd *lcd = dev_get_drvdata(&spi->dev); | ||
428 | |||
429 | corgi_bl_set_intensity(lcd, 0); | ||
430 | corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN); | ||
431 | return 0; | ||
432 | } | ||
433 | |||
434 | static int corgi_lcd_resume(struct spi_device *spi) | ||
435 | { | ||
436 | struct corgi_lcd *lcd = dev_get_drvdata(&spi->dev); | ||
437 | |||
438 | corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK); | ||
439 | backlight_update_status(lcd->bl_dev); | ||
440 | return 0; | ||
441 | } | ||
442 | #else | ||
443 | #define corgi_lcd_suspend NULL | ||
444 | #define corgi_lcd_resume NULL | ||
445 | #endif | ||
446 | |||
447 | static int __devinit corgi_lcd_probe(struct spi_device *spi) | ||
448 | { | ||
449 | struct corgi_lcd_platform_data *pdata = spi->dev.platform_data; | ||
450 | struct corgi_lcd *lcd; | ||
451 | int ret = 0; | ||
452 | |||
453 | if (pdata == NULL) { | ||
454 | dev_err(&spi->dev, "platform data not available\n"); | ||
455 | return -EINVAL; | ||
456 | } | ||
457 | |||
458 | lcd = kzalloc(sizeof(struct corgi_lcd), GFP_KERNEL); | ||
459 | if (!lcd) { | ||
460 | dev_err(&spi->dev, "failed to allocate memory\n"); | ||
461 | return -ENOMEM; | ||
462 | } | ||
463 | |||
464 | lcd->spi_dev = spi; | ||
465 | |||
466 | lcd->lcd_dev = lcd_device_register("corgi_lcd", &spi->dev, | ||
467 | lcd, &corgi_lcd_ops); | ||
468 | if (IS_ERR(lcd->lcd_dev)) { | ||
469 | ret = PTR_ERR(lcd->lcd_dev); | ||
470 | goto err_free_lcd; | ||
471 | } | ||
472 | lcd->power = FB_BLANK_POWERDOWN; | ||
473 | lcd->mode = (pdata) ? pdata->init_mode : CORGI_LCD_MODE_VGA; | ||
474 | |||
475 | lcd->bl_dev = backlight_device_register("corgi_bl", &spi->dev, | ||
476 | lcd, &corgi_bl_ops); | ||
477 | if (IS_ERR(lcd->bl_dev)) { | ||
478 | ret = PTR_ERR(lcd->bl_dev); | ||
479 | goto err_unregister_lcd; | ||
480 | } | ||
481 | lcd->bl_dev->props.max_brightness = pdata->max_intensity; | ||
482 | lcd->bl_dev->props.brightness = pdata->default_intensity; | ||
483 | lcd->bl_dev->props.power = FB_BLANK_UNBLANK; | ||
484 | |||
485 | lcd->notify = pdata->notify; | ||
486 | lcd->kick_battery = pdata->kick_battery; | ||
487 | |||
488 | dev_set_drvdata(&spi->dev, lcd); | ||
489 | corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK); | ||
490 | backlight_update_status(lcd->bl_dev); | ||
491 | return 0; | ||
492 | |||
493 | err_unregister_lcd: | ||
494 | lcd_device_unregister(lcd->lcd_dev); | ||
495 | err_free_lcd: | ||
496 | kfree(lcd); | ||
497 | return ret; | ||
498 | } | ||
499 | |||
500 | static int __devexit corgi_lcd_remove(struct spi_device *spi) | ||
501 | { | ||
502 | struct corgi_lcd *lcd = dev_get_drvdata(&spi->dev); | ||
503 | |||
504 | lcd->bl_dev->props.power = FB_BLANK_UNBLANK; | ||
505 | lcd->bl_dev->props.brightness = 0; | ||
506 | backlight_update_status(lcd->bl_dev); | ||
507 | backlight_device_unregister(lcd->bl_dev); | ||
508 | |||
509 | corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN); | ||
510 | lcd_device_unregister(lcd->lcd_dev); | ||
511 | kfree(lcd); | ||
512 | |||
513 | return 0; | ||
514 | } | ||
515 | |||
516 | static struct spi_driver corgi_lcd_driver = { | ||
517 | .driver = { | ||
518 | .name = "corgi-lcd", | ||
519 | .owner = THIS_MODULE, | ||
520 | }, | ||
521 | .probe = corgi_lcd_probe, | ||
522 | .remove = __devexit_p(corgi_lcd_remove), | ||
523 | .suspend = corgi_lcd_suspend, | ||
524 | .resume = corgi_lcd_resume, | ||
525 | }; | ||
526 | |||
527 | static int __init corgi_lcd_init(void) | ||
528 | { | ||
529 | return spi_register_driver(&corgi_lcd_driver); | ||
530 | } | ||
531 | module_init(corgi_lcd_init); | ||
532 | |||
533 | static void __exit corgi_lcd_exit(void) | ||
534 | { | ||
535 | spi_unregister_driver(&corgi_lcd_driver); | ||
536 | } | ||
537 | module_exit(corgi_lcd_exit); | ||
538 | |||
539 | MODULE_DESCRIPTION("LCD and backlight driver for SHARP C7x0/Cxx00"); | ||
540 | MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"); | ||
541 | MODULE_LICENSE("GPL"); | ||