Commit | Line | Data |
---|---|---|
cccb6d3c BD |
1 | /* drivers/video/backlight/ili9320.c |
2 | * | |
3 | * ILI9320 LCD controller driver core. | |
4 | * | |
5 | * Copyright 2007 Simtec Electronics | |
6 | * http://armlinux.simtec.co.uk/ | |
7 | * Ben Dooks <ben@simtec.co.uk> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License version 2 as | |
11 | * published by the Free Software Foundation. | |
12 | */ | |
13 | ||
14 | #include <linux/delay.h> | |
15 | #include <linux/err.h> | |
16 | #include <linux/fb.h> | |
17 | #include <linux/init.h> | |
18 | #include <linux/lcd.h> | |
19 | #include <linux/module.h> | |
5a0e3ad6 | 20 | #include <linux/slab.h> |
cccb6d3c BD |
21 | |
22 | #include <linux/spi/spi.h> | |
23 | ||
24 | #include <video/ili9320.h> | |
25 | ||
26 | #include "ili9320.h" | |
27 | ||
28 | ||
29 | static inline int ili9320_write_spi(struct ili9320 *ili, | |
30 | unsigned int reg, | |
31 | unsigned int value) | |
32 | { | |
33 | struct ili9320_spi *spi = &ili->access.spi; | |
34 | unsigned char *addr = spi->buffer_addr; | |
35 | unsigned char *data = spi->buffer_data; | |
36 | ||
37 | /* spi message consits of: | |
38 | * first byte: ID and operation | |
39 | */ | |
40 | ||
41 | addr[0] = spi->id | ILI9320_SPI_INDEX | ILI9320_SPI_WRITE; | |
42 | addr[1] = reg >> 8; | |
43 | addr[2] = reg; | |
44 | ||
45 | /* second message is the data to transfer */ | |
46 | ||
47 | data[0] = spi->id | ILI9320_SPI_DATA | ILI9320_SPI_WRITE; | |
48 | data[1] = value >> 8; | |
49 | data[2] = value; | |
50 | ||
51 | return spi_sync(spi->dev, &spi->message); | |
52 | } | |
53 | ||
54 | int ili9320_write(struct ili9320 *ili, unsigned int reg, unsigned int value) | |
55 | { | |
56 | dev_dbg(ili->dev, "write: reg=%02x, val=%04x\n", reg, value); | |
57 | return ili->write(ili, reg, value); | |
58 | } | |
59 | ||
60 | EXPORT_SYMBOL_GPL(ili9320_write); | |
61 | ||
62 | int ili9320_write_regs(struct ili9320 *ili, | |
63 | struct ili9320_reg *values, | |
64 | int nr_values) | |
65 | { | |
66 | int index; | |
67 | int ret; | |
68 | ||
69 | for (index = 0; index < nr_values; index++, values++) { | |
70 | ret = ili9320_write(ili, values->address, values->value); | |
71 | if (ret != 0) | |
72 | return ret; | |
73 | } | |
74 | ||
75 | return 0; | |
76 | } | |
77 | ||
78 | EXPORT_SYMBOL_GPL(ili9320_write_regs); | |
79 | ||
80 | static void ili9320_reset(struct ili9320 *lcd) | |
81 | { | |
82 | struct ili9320_platdata *cfg = lcd->platdata; | |
83 | ||
84 | cfg->reset(1); | |
85 | mdelay(50); | |
86 | ||
87 | cfg->reset(0); | |
88 | mdelay(50); | |
89 | ||
90 | cfg->reset(1); | |
91 | mdelay(100); | |
92 | } | |
93 | ||
94 | static inline int ili9320_init_chip(struct ili9320 *lcd) | |
95 | { | |
96 | int ret; | |
97 | ||
98 | ili9320_reset(lcd); | |
99 | ||
100 | ret = lcd->client->init(lcd, lcd->platdata); | |
101 | if (ret != 0) { | |
102 | dev_err(lcd->dev, "failed to initialise display\n"); | |
103 | return ret; | |
104 | } | |
105 | ||
106 | lcd->initialised = 1; | |
107 | return 0; | |
108 | } | |
109 | ||
110 | static inline int ili9320_power_on(struct ili9320 *lcd) | |
111 | { | |
112 | if (!lcd->initialised) | |
113 | ili9320_init_chip(lcd); | |
114 | ||
115 | lcd->display1 |= (ILI9320_DISPLAY1_D(3) | ILI9320_DISPLAY1_BASEE); | |
116 | ili9320_write(lcd, ILI9320_DISPLAY1, lcd->display1); | |
117 | ||
118 | return 0; | |
119 | } | |
120 | ||
121 | static inline int ili9320_power_off(struct ili9320 *lcd) | |
122 | { | |
123 | lcd->display1 &= ~(ILI9320_DISPLAY1_D(3) | ILI9320_DISPLAY1_BASEE); | |
124 | ili9320_write(lcd, ILI9320_DISPLAY1, lcd->display1); | |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
129 | #define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL) | |
130 | ||
131 | static int ili9320_power(struct ili9320 *lcd, int power) | |
132 | { | |
133 | int ret = 0; | |
134 | ||
135 | dev_dbg(lcd->dev, "power %d => %d\n", lcd->power, power); | |
136 | ||
137 | if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power)) | |
138 | ret = ili9320_power_on(lcd); | |
139 | else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power)) | |
140 | ret = ili9320_power_off(lcd); | |
141 | ||
142 | if (ret == 0) | |
143 | lcd->power = power; | |
144 | else | |
145 | dev_warn(lcd->dev, "failed to set power mode %d\n", power); | |
146 | ||
147 | return ret; | |
148 | } | |
149 | ||
150 | static inline struct ili9320 *to_our_lcd(struct lcd_device *lcd) | |
151 | { | |
152 | return lcd_get_data(lcd); | |
153 | } | |
154 | ||
155 | static int ili9320_set_power(struct lcd_device *ld, int power) | |
156 | { | |
157 | struct ili9320 *lcd = to_our_lcd(ld); | |
158 | ||
159 | return ili9320_power(lcd, power); | |
160 | } | |
161 | ||
162 | static int ili9320_get_power(struct lcd_device *ld) | |
163 | { | |
164 | struct ili9320 *lcd = to_our_lcd(ld); | |
165 | ||
166 | return lcd->power; | |
167 | } | |
168 | ||
169 | static struct lcd_ops ili9320_ops = { | |
170 | .get_power = ili9320_get_power, | |
171 | .set_power = ili9320_set_power, | |
172 | }; | |
173 | ||
174 | static void __devinit ili9320_setup_spi(struct ili9320 *ili, | |
175 | struct spi_device *dev) | |
176 | { | |
177 | struct ili9320_spi *spi = &ili->access.spi; | |
178 | ||
179 | ili->write = ili9320_write_spi; | |
180 | spi->dev = dev; | |
181 | ||
182 | /* fill the two messages we are going to use to send the data | |
183 | * with, the first the address followed by the data. The datasheet | |
184 | * says they should be done as two distinct cycles of the SPI CS line. | |
185 | */ | |
186 | ||
187 | spi->xfer[0].tx_buf = spi->buffer_addr; | |
188 | spi->xfer[1].tx_buf = spi->buffer_data; | |
189 | spi->xfer[0].len = 3; | |
190 | spi->xfer[1].len = 3; | |
191 | spi->xfer[0].bits_per_word = 8; | |
192 | spi->xfer[1].bits_per_word = 8; | |
193 | spi->xfer[0].cs_change = 1; | |
194 | ||
195 | spi_message_init(&spi->message); | |
196 | spi_message_add_tail(&spi->xfer[0], &spi->message); | |
197 | spi_message_add_tail(&spi->xfer[1], &spi->message); | |
198 | } | |
199 | ||
200 | int __devinit ili9320_probe_spi(struct spi_device *spi, | |
201 | struct ili9320_client *client) | |
202 | { | |
203 | struct ili9320_platdata *cfg = spi->dev.platform_data; | |
204 | struct device *dev = &spi->dev; | |
205 | struct ili9320 *ili; | |
206 | struct lcd_device *lcd; | |
207 | int ret = 0; | |
208 | ||
209 | /* verify we where given some information */ | |
210 | ||
211 | if (cfg == NULL) { | |
212 | dev_err(dev, "no platform data supplied\n"); | |
213 | return -EINVAL; | |
214 | } | |
215 | ||
216 | if (cfg->hsize <= 0 || cfg->vsize <= 0 || cfg->reset == NULL) { | |
217 | dev_err(dev, "invalid platform data supplied\n"); | |
218 | return -EINVAL; | |
219 | } | |
220 | ||
221 | /* allocate and initialse our state */ | |
222 | ||
9828eb09 | 223 | ili = devm_kzalloc(&spi->dev, sizeof(struct ili9320), GFP_KERNEL); |
cccb6d3c BD |
224 | if (ili == NULL) { |
225 | dev_err(dev, "no memory for device\n"); | |
226 | return -ENOMEM; | |
227 | } | |
228 | ||
229 | ili->access.spi.id = ILI9320_SPI_IDCODE | ILI9320_SPI_ID(1); | |
230 | ||
231 | ili->dev = dev; | |
232 | ili->client = client; | |
233 | ili->power = FB_BLANK_POWERDOWN; | |
234 | ili->platdata = cfg; | |
235 | ||
236 | dev_set_drvdata(&spi->dev, ili); | |
237 | ||
238 | ili9320_setup_spi(ili, spi); | |
239 | ||
240 | lcd = lcd_device_register("ili9320", dev, ili, &ili9320_ops); | |
241 | if (IS_ERR(lcd)) { | |
242 | dev_err(dev, "failed to register lcd device\n"); | |
9828eb09 | 243 | return PTR_ERR(lcd); |
cccb6d3c BD |
244 | } |
245 | ||
246 | ili->lcd = lcd; | |
247 | ||
248 | dev_info(dev, "initialising %s\n", client->name); | |
249 | ||
250 | ret = ili9320_power(ili, FB_BLANK_UNBLANK); | |
251 | if (ret != 0) { | |
252 | dev_err(dev, "failed to set lcd power state\n"); | |
253 | goto err_unregister; | |
254 | } | |
255 | ||
256 | return 0; | |
257 | ||
258 | err_unregister: | |
259 | lcd_device_unregister(lcd); | |
260 | ||
cccb6d3c BD |
261 | return ret; |
262 | } | |
263 | ||
264 | EXPORT_SYMBOL_GPL(ili9320_probe_spi); | |
265 | ||
ff944046 | 266 | int ili9320_remove(struct ili9320 *ili) |
cccb6d3c BD |
267 | { |
268 | ili9320_power(ili, FB_BLANK_POWERDOWN); | |
269 | ||
270 | lcd_device_unregister(ili->lcd); | |
cccb6d3c BD |
271 | |
272 | return 0; | |
273 | } | |
274 | ||
275 | EXPORT_SYMBOL_GPL(ili9320_remove); | |
276 | ||
277 | #ifdef CONFIG_PM | |
278 | int ili9320_suspend(struct ili9320 *lcd, pm_message_t state) | |
279 | { | |
280 | int ret; | |
281 | ||
282 | dev_dbg(lcd->dev, "%s: event %d\n", __func__, state.event); | |
283 | ||
284 | if (state.event == PM_EVENT_SUSPEND) { | |
285 | ret = ili9320_power(lcd, FB_BLANK_POWERDOWN); | |
286 | ||
287 | if (lcd->platdata->suspend == ILI9320_SUSPEND_DEEP) { | |
288 | ili9320_write(lcd, ILI9320_POWER1, lcd->power1 | | |
289 | ILI9320_POWER1_SLP | | |
290 | ILI9320_POWER1_DSTB); | |
291 | lcd->initialised = 0; | |
292 | } | |
293 | ||
294 | return ret; | |
295 | } | |
296 | ||
297 | return 0; | |
298 | } | |
299 | ||
300 | EXPORT_SYMBOL_GPL(ili9320_suspend); | |
301 | ||
302 | int ili9320_resume(struct ili9320 *lcd) | |
303 | { | |
304 | dev_info(lcd->dev, "resuming from power state %d\n", lcd->power); | |
305 | ||
306 | if (lcd->platdata->suspend == ILI9320_SUSPEND_DEEP) { | |
307 | ili9320_write(lcd, ILI9320_POWER1, 0x00); | |
308 | } | |
309 | ||
310 | return ili9320_power(lcd, FB_BLANK_UNBLANK); | |
311 | } | |
312 | ||
313 | EXPORT_SYMBOL_GPL(ili9320_resume); | |
314 | #endif | |
315 | ||
316 | /* Power down all displays on reboot, poweroff or halt */ | |
317 | void ili9320_shutdown(struct ili9320 *lcd) | |
318 | { | |
319 | ili9320_power(lcd, FB_BLANK_POWERDOWN); | |
320 | } | |
321 | ||
322 | EXPORT_SYMBOL_GPL(ili9320_shutdown); | |
323 | ||
324 | MODULE_AUTHOR("Ben Dooks <ben-linux@fluff.org>"); | |
325 | MODULE_DESCRIPTION("ILI9320 LCD Driver"); | |
326 | MODULE_LICENSE("GPL v2"); |