Commit | Line | Data |
---|---|---|
1e7d51c6 TP |
1 | /* |
2 | * FB driver for the Watterott LCD Controller | |
3 | * | |
4 | * Copyright (C) 2013 Noralf Tronnes | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
1e7d51c6 TP |
15 | */ |
16 | ||
17 | #include <linux/module.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/gpio.h> | |
21 | #include <linux/delay.h> | |
22 | ||
23 | #include "fbtft.h" | |
24 | ||
25 | #define DRVNAME "fb_watterott" | |
26 | #define WIDTH 320 | |
27 | #define HEIGHT 240 | |
28 | #define FPS 5 | |
29 | #define TXBUFLEN 1024 | |
30 | #define DEFAULT_BRIGHTNESS 50 | |
31 | ||
32 | #define CMD_VERSION 0x01 | |
33 | #define CMD_LCD_LED 0x10 | |
34 | #define CMD_LCD_RESET 0x11 | |
35 | #define CMD_LCD_ORIENTATION 0x20 | |
36 | #define CMD_LCD_DRAWIMAGE 0x27 | |
37 | #define COLOR_RGB323 8 | |
38 | #define COLOR_RGB332 9 | |
39 | #define COLOR_RGB233 10 | |
40 | #define COLOR_RGB565 16 | |
41 | ||
1e7d51c6 TP |
42 | static short mode = 565; |
43 | module_param(mode, short, 0); | |
44 | MODULE_PARM_DESC(mode, "RGB color transfer mode: 332, 565 (default)"); | |
45 | ||
46 | static void write_reg8_bus8(struct fbtft_par *par, int len, ...) | |
47 | { | |
48 | va_list args; | |
49 | int i, ret; | |
50 | u8 *buf = par->buf; | |
51 | ||
52 | va_start(args, len); | |
53 | for (i = 0; i < len; i++) | |
54 | *buf++ = (u8)va_arg(args, unsigned int); | |
55 | va_end(args); | |
56 | ||
57 | fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, | |
58 | par->info->device, u8, par->buf, len, "%s: ", __func__); | |
59 | ||
60 | ret = par->fbtftops.write(par, par->buf, len); | |
61 | if (ret < 0) { | |
62 | dev_err(par->info->device, | |
aed1c72e | 63 | "write() failed and returned %d\n", ret); |
1e7d51c6 TP |
64 | return; |
65 | } | |
66 | } | |
67 | ||
68 | static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) | |
69 | { | |
1c41494a | 70 | unsigned int start_line, end_line; |
4b6dc179 | 71 | u16 *vmem16 = (u16 *)(par->info->screen_buffer + offset); |
1e7d51c6 TP |
72 | u16 *pos = par->txbuf.buf + 1; |
73 | u16 *buf16 = par->txbuf.buf + 10; | |
74 | int i, j; | |
75 | int ret = 0; | |
76 | ||
1e7d51c6 TP |
77 | start_line = offset / par->info->fix.line_length; |
78 | end_line = start_line + (len / par->info->fix.line_length) - 1; | |
79 | ||
80 | /* Set command header. pos: x, y, w, h */ | |
81 | ((u8 *)par->txbuf.buf)[0] = CMD_LCD_DRAWIMAGE; | |
82 | pos[0] = 0; | |
83 | pos[2] = cpu_to_be16(par->info->var.xres); | |
84 | pos[3] = cpu_to_be16(1); | |
85 | ((u8 *)par->txbuf.buf)[9] = COLOR_RGB565; | |
86 | ||
87 | for (i = start_line; i <= end_line; i++) { | |
88 | pos[1] = cpu_to_be16(i); | |
89 | for (j = 0; j < par->info->var.xres; j++) | |
90 | buf16[j] = cpu_to_be16(*vmem16++); | |
91 | ret = par->fbtftops.write(par, | |
92 | par->txbuf.buf, 10 + par->info->fix.line_length); | |
93 | if (ret < 0) | |
94 | return ret; | |
95 | udelay(300); | |
96 | } | |
97 | ||
98 | return 0; | |
99 | } | |
100 | ||
101 | #define RGB565toRGB323(c) (((c&0xE000)>>8) | ((c&0600)>>6) | ((c&0x001C)>>2)) | |
102 | #define RGB565toRGB332(c) (((c&0xE000)>>8) | ((c&0700)>>6) | ((c&0x0018)>>3)) | |
103 | #define RGB565toRGB233(c) (((c&0xC000)>>8) | ((c&0700)>>5) | ((c&0x001C)>>2)) | |
104 | ||
105 | static int write_vmem_8bit(struct fbtft_par *par, size_t offset, size_t len) | |
106 | { | |
1c41494a | 107 | unsigned int start_line, end_line; |
4b6dc179 | 108 | u16 *vmem16 = (u16 *)(par->info->screen_buffer + offset); |
1e7d51c6 TP |
109 | u16 *pos = par->txbuf.buf + 1; |
110 | u8 *buf8 = par->txbuf.buf + 10; | |
111 | int i, j; | |
112 | int ret = 0; | |
113 | ||
1e7d51c6 TP |
114 | start_line = offset / par->info->fix.line_length; |
115 | end_line = start_line + (len / par->info->fix.line_length) - 1; | |
116 | ||
117 | /* Set command header. pos: x, y, w, h */ | |
118 | ((u8 *)par->txbuf.buf)[0] = CMD_LCD_DRAWIMAGE; | |
119 | pos[0] = 0; | |
120 | pos[2] = cpu_to_be16(par->info->var.xres); | |
121 | pos[3] = cpu_to_be16(1); | |
122 | ((u8 *)par->txbuf.buf)[9] = COLOR_RGB332; | |
123 | ||
124 | for (i = start_line; i <= end_line; i++) { | |
125 | pos[1] = cpu_to_be16(i); | |
126 | for (j = 0; j < par->info->var.xres; j++) { | |
127 | buf8[j] = RGB565toRGB332(*vmem16); | |
128 | vmem16++; | |
129 | } | |
130 | ret = par->fbtftops.write(par, | |
131 | par->txbuf.buf, 10 + par->info->var.xres); | |
132 | if (ret < 0) | |
133 | return ret; | |
134 | udelay(700); | |
135 | } | |
136 | ||
137 | return 0; | |
138 | } | |
139 | ||
1c41494a | 140 | static unsigned int firmware_version(struct fbtft_par *par) |
1e7d51c6 TP |
141 | { |
142 | u8 rxbuf[4] = {0, }; | |
143 | ||
144 | write_reg(par, CMD_VERSION); | |
145 | par->fbtftops.read(par, rxbuf, 4); | |
146 | if (rxbuf[1] != '.') | |
147 | return 0; | |
148 | ||
149 | return (rxbuf[0] - '0') << 8 | (rxbuf[2] - '0') << 4 | (rxbuf[3] - '0'); | |
150 | } | |
151 | ||
152 | static int init_display(struct fbtft_par *par) | |
153 | { | |
154 | int ret; | |
1c41494a | 155 | unsigned int version; |
1e7d51c6 TP |
156 | u8 save_mode; |
157 | ||
1e7d51c6 TP |
158 | /* enable SPI interface by having CS and MOSI low during reset */ |
159 | save_mode = par->spi->mode; | |
160 | par->spi->mode |= SPI_CS_HIGH; | |
dd3afa57 | 161 | ret = spi_setup(par->spi); /* set CS inactive low */ |
1e7d51c6 TP |
162 | if (ret) { |
163 | dev_err(par->info->device, "Could not set SPI_CS_HIGH\n"); | |
164 | return ret; | |
165 | } | |
166 | write_reg(par, 0x00); /* make sure mode is set */ | |
167 | ||
168 | mdelay(50); | |
169 | par->fbtftops.reset(par); | |
170 | mdelay(1000); | |
171 | par->spi->mode = save_mode; | |
dd3afa57 | 172 | ret = spi_setup(par->spi); |
1e7d51c6 TP |
173 | if (ret) { |
174 | dev_err(par->info->device, "Could not restore SPI mode\n"); | |
175 | return ret; | |
176 | } | |
177 | write_reg(par, 0x00); | |
178 | ||
179 | version = firmware_version(par); | |
180 | fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "Firmware version: %x.%02x\n", | |
181 | version >> 8, version & 0xFF); | |
182 | ||
183 | if (mode == 332) | |
184 | par->fbtftops.write_vmem = write_vmem_8bit; | |
185 | return 0; | |
186 | } | |
187 | ||
188 | static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) | |
189 | { | |
190 | /* not used on this controller */ | |
191 | } | |
192 | ||
193 | static int set_var(struct fbtft_par *par) | |
194 | { | |
195 | u8 rotate; | |
196 | ||
1e7d51c6 TP |
197 | /* this controller rotates clock wise */ |
198 | switch (par->info->var.rotate) { | |
199 | case 90: | |
200 | rotate = 27; | |
201 | break; | |
202 | case 180: | |
203 | rotate = 18; | |
204 | break; | |
205 | case 270: | |
206 | rotate = 9; | |
207 | break; | |
208 | default: | |
209 | rotate = 0; | |
210 | } | |
211 | write_reg(par, CMD_LCD_ORIENTATION, rotate); | |
212 | ||
213 | return 0; | |
214 | } | |
215 | ||
216 | static int verify_gpios(struct fbtft_par *par) | |
217 | { | |
218 | if (par->gpio.reset < 0) { | |
219 | dev_err(par->info->device, "Missing 'reset' gpio. Aborting.\n"); | |
220 | return -EINVAL; | |
221 | } | |
222 | return 0; | |
223 | } | |
224 | ||
225 | #ifdef CONFIG_FB_BACKLIGHT | |
226 | static int backlight_chip_update_status(struct backlight_device *bd) | |
227 | { | |
228 | struct fbtft_par *par = bl_get_data(bd); | |
229 | int brightness = bd->props.brightness; | |
230 | ||
231 | fbtft_par_dbg(DEBUG_BACKLIGHT, par, | |
232 | "%s: brightness=%d, power=%d, fb_blank=%d\n", | |
233 | __func__, bd->props.brightness, bd->props.power, | |
234 | bd->props.fb_blank); | |
235 | ||
236 | if (bd->props.power != FB_BLANK_UNBLANK) | |
237 | brightness = 0; | |
238 | ||
239 | if (bd->props.fb_blank != FB_BLANK_UNBLANK) | |
240 | brightness = 0; | |
241 | ||
242 | write_reg(par, CMD_LCD_LED, brightness); | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
6d657dac MR |
247 | static const struct backlight_ops bl_ops = { |
248 | .update_status = backlight_chip_update_status, | |
249 | }; | |
250 | ||
1e7d51c6 TP |
251 | static void register_chip_backlight(struct fbtft_par *par) |
252 | { | |
253 | struct backlight_device *bd; | |
254 | struct backlight_properties bl_props = { 0, }; | |
1e7d51c6 | 255 | |
1e7d51c6 TP |
256 | bl_props.type = BACKLIGHT_RAW; |
257 | bl_props.power = FB_BLANK_POWERDOWN; | |
258 | bl_props.max_brightness = 100; | |
259 | bl_props.brightness = DEFAULT_BRIGHTNESS; | |
260 | ||
261 | bd = backlight_device_register(dev_driver_string(par->info->device), | |
6d657dac | 262 | par->info->device, par, &bl_ops, &bl_props); |
1e7d51c6 TP |
263 | if (IS_ERR(bd)) { |
264 | dev_err(par->info->device, | |
265 | "cannot register backlight device (%ld)\n", | |
266 | PTR_ERR(bd)); | |
267 | return; | |
268 | } | |
269 | par->info->bl_dev = bd; | |
270 | ||
271 | if (!par->fbtftops.unregister_backlight) | |
272 | par->fbtftops.unregister_backlight = fbtft_unregister_backlight; | |
273 | } | |
274 | #else | |
275 | #define register_chip_backlight NULL | |
276 | #endif | |
277 | ||
1e7d51c6 TP |
278 | static struct fbtft_display display = { |
279 | .regwidth = 8, | |
280 | .buswidth = 8, | |
281 | .width = WIDTH, | |
282 | .height = HEIGHT, | |
283 | .fps = FPS, | |
284 | .txbuflen = TXBUFLEN, | |
285 | .fbtftops = { | |
286 | .write_register = write_reg8_bus8, | |
287 | .write_vmem = write_vmem, | |
288 | .init_display = init_display, | |
289 | .set_addr_win = set_addr_win, | |
290 | .set_var = set_var, | |
291 | .verify_gpios = verify_gpios, | |
292 | .register_backlight = register_chip_backlight, | |
293 | }, | |
294 | }; | |
1014c2ce | 295 | |
1e7d51c6 TP |
296 | FBTFT_REGISTER_DRIVER(DRVNAME, "watterott,openlcd", &display); |
297 | ||
298 | MODULE_ALIAS("spi:" DRVNAME); | |
299 | ||
300 | MODULE_DESCRIPTION("FB driver for the Watterott LCD Controller"); | |
301 | MODULE_AUTHOR("Noralf Tronnes"); | |
302 | MODULE_LICENSE("GPL"); |