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