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