Commit | Line | Data |
---|---|---|
a1560f9b HC |
1 | /* |
2 | * FB driver for the UltraChip UC1611 LCD controller | |
3 | * | |
4 | * The display is 4-bit grayscale (16 shades) 240x160. | |
5 | * | |
6 | * Copyright (C) 2015 Henri Chain | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | */ | |
18 | ||
19 | #include <linux/module.h> | |
20 | #include <linux/kernel.h> | |
21 | #include <linux/init.h> | |
22 | #include <linux/gpio.h> | |
23 | #include <linux/spi/spi.h> | |
24 | #include <linux/delay.h> | |
25 | ||
26 | #include "fbtft.h" | |
27 | ||
28 | #define DRVNAME "fb_uc1611" | |
29 | #define WIDTH 240 | |
30 | #define HEIGHT 160 | |
31 | #define BPP 8 | |
32 | #define FPS 40 | |
33 | ||
34 | /* | |
35 | * LCD voltage is a combination of ratio, gain, pot and temp | |
36 | * | |
37 | * V_LCD = V_BIAS * ratio | |
38 | * V_LCD = (C_V0 + C_PM × pot) * (1 + (T - 25) * temp) | |
39 | * C_V0 and C_PM depend on ratio and gain | |
40 | * T is ambient temperature | |
41 | */ | |
42 | ||
43 | /* BR -> actual ratio: 0-3 -> 5, 10, 11, 13 */ | |
1c41494a | 44 | static unsigned int ratio = 2; |
a1560f9b HC |
45 | module_param(ratio, uint, 0); |
46 | MODULE_PARM_DESC(ratio, "BR[1:0] Bias voltage ratio: 0-3 (default: 2)"); | |
47 | ||
1c41494a | 48 | static unsigned int gain = 3; |
a1560f9b HC |
49 | module_param(gain, uint, 0); |
50 | MODULE_PARM_DESC(gain, "GN[1:0] Bias voltage gain: 0-3 (default: 3)"); | |
51 | ||
1c41494a | 52 | static unsigned int pot = 16; |
a1560f9b HC |
53 | module_param(pot, uint, 0); |
54 | MODULE_PARM_DESC(pot, "PM[6:0] Bias voltage pot.: 0-63 (default: 16)"); | |
55 | ||
56 | /* TC -> % compensation per deg C: 0-3 -> -.05, -.10, -.015, -.20 */ | |
1c41494a | 57 | static unsigned int temp; |
a1560f9b HC |
58 | module_param(temp, uint, 0); |
59 | MODULE_PARM_DESC(temp, "TC[1:0] Temperature compensation: 0-3 (default: 0)"); | |
60 | ||
61 | /* PC[1:0] -> LCD capacitance: 0-3 -> <20nF, 20-28 nF, 29-40 nF, 40-56 nF */ | |
1c41494a | 62 | static unsigned int load = 1; |
a1560f9b HC |
63 | module_param(load, uint, 0); |
64 | MODULE_PARM_DESC(load, "PC[1:0] Panel Loading: 0-3 (default: 1)"); | |
65 | ||
66 | /* PC[3:2] -> V_LCD: 0, 1, 3 -> ext., int. with ratio = 5, int. standard */ | |
1c41494a | 67 | static unsigned int pump = 3; |
a1560f9b HC |
68 | module_param(pump, uint, 0); |
69 | MODULE_PARM_DESC(pump, "PC[3:2] Pump control: 0,1,3 (default: 3)"); | |
70 | ||
71 | static int init_display(struct fbtft_par *par) | |
72 | { | |
73 | int ret; | |
74 | ||
a1560f9b HC |
75 | /* Set CS active high */ |
76 | par->spi->mode |= SPI_CS_HIGH; | |
dd3afa57 | 77 | ret = spi_setup(par->spi); |
a1560f9b HC |
78 | if (ret) { |
79 | dev_err(par->info->device, "Could not set SPI_CS_HIGH\n"); | |
80 | return ret; | |
81 | } | |
82 | ||
83 | /* Reset controller */ | |
84 | write_reg(par, 0xE2); | |
85 | ||
86 | /* Set bias ratio */ | |
87 | write_reg(par, 0xE8 | (ratio & 0x03)); | |
88 | ||
89 | /* Set bias gain and potentiometer */ | |
90 | write_reg(par, 0x81); | |
91 | write_reg(par, (gain & 0x03) << 6 | (pot & 0x3F)); | |
92 | ||
93 | /* Set temperature compensation */ | |
94 | write_reg(par, 0x24 | (temp & 0x03)); | |
95 | ||
96 | /* Set panel loading */ | |
97 | write_reg(par, 0x28 | (load & 0x03)); | |
98 | ||
99 | /* Set pump control */ | |
100 | write_reg(par, 0x2C | (pump & 0x03)); | |
101 | ||
102 | /* Set inverse display */ | |
103 | write_reg(par, 0xA6 | (0x01 & 0x01)); | |
104 | ||
105 | /* Set 4-bit grayscale mode */ | |
106 | write_reg(par, 0xD0 | (0x02 & 0x03)); | |
107 | ||
108 | /* Set Display enable */ | |
109 | write_reg(par, 0xA8 | 0x07); | |
110 | ||
111 | return 0; | |
112 | } | |
113 | ||
114 | static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) | |
115 | { | |
a1560f9b HC |
116 | switch (par->info->var.rotate) { |
117 | case 90: | |
118 | case 270: | |
119 | /* Set column address */ | |
120 | write_reg(par, ys & 0x0F); | |
121 | write_reg(par, 0x10 | (ys >> 4)); | |
122 | ||
123 | /* Set page address (divide xs by 2) (not used by driver) */ | |
124 | write_reg(par, 0x60 | ((xs >> 1) & 0x0F)); | |
125 | write_reg(par, 0x70 | (xs >> 5)); | |
126 | break; | |
127 | default: | |
128 | /* Set column address (not used by driver) */ | |
129 | write_reg(par, xs & 0x0F); | |
130 | write_reg(par, 0x10 | (xs >> 4)); | |
131 | ||
132 | /* Set page address (divide ys by 2) */ | |
133 | write_reg(par, 0x60 | ((ys >> 1) & 0x0F)); | |
134 | write_reg(par, 0x70 | (ys >> 5)); | |
135 | break; | |
136 | } | |
137 | } | |
138 | ||
139 | static int blank(struct fbtft_par *par, bool on) | |
140 | { | |
141 | fbtft_par_dbg(DEBUG_BLANK, par, "%s(blank=%s)\n", | |
142 | __func__, on ? "true" : "false"); | |
143 | ||
144 | if (on) | |
145 | write_reg(par, 0xA8 | 0x00); | |
146 | else | |
147 | write_reg(par, 0xA8 | 0x07); | |
148 | return 0; | |
149 | } | |
150 | ||
151 | static int set_var(struct fbtft_par *par) | |
152 | { | |
a1560f9b HC |
153 | /* par->info->fix.visual = FB_VISUAL_PSEUDOCOLOR; */ |
154 | par->info->var.grayscale = 1; | |
155 | par->info->var.red.offset = 0; | |
156 | par->info->var.red.length = 8; | |
157 | par->info->var.green.offset = 0; | |
158 | par->info->var.green.length = 8; | |
159 | par->info->var.blue.offset = 0; | |
160 | par->info->var.blue.length = 8; | |
161 | par->info->var.transp.offset = 0; | |
162 | par->info->var.transp.length = 0; | |
163 | ||
164 | switch (par->info->var.rotate) { | |
165 | case 90: | |
166 | /* Set RAM address control */ | |
167 | write_reg(par, 0x88 | |
168 | | (0x0 & 0x1) << 2 /* Increment positively */ | |
169 | | (0x1 & 0x1) << 1 /* Increment page first */ | |
170 | | (0x1 & 0x1)); /* Wrap around (default) */ | |
171 | ||
172 | /* Set LCD mapping */ | |
173 | write_reg(par, 0xC0 | |
174 | | (0x0 & 0x1) << 2 /* Mirror Y OFF */ | |
175 | | (0x0 & 0x1) << 1 /* Mirror X OFF */ | |
176 | | (0x0 & 0x1)); /* MS nibble last (default) */ | |
177 | break; | |
178 | case 180: | |
179 | /* Set RAM address control */ | |
180 | write_reg(par, 0x88 | |
181 | | (0x0 & 0x1) << 2 /* Increment positively */ | |
182 | | (0x0 & 0x1) << 1 /* Increment column first */ | |
183 | | (0x1 & 0x1)); /* Wrap around (default) */ | |
184 | ||
185 | /* Set LCD mapping */ | |
186 | write_reg(par, 0xC0 | |
187 | | (0x1 & 0x1) << 2 /* Mirror Y ON */ | |
188 | | (0x0 & 0x1) << 1 /* Mirror X OFF */ | |
189 | | (0x0 & 0x1)); /* MS nibble last (default) */ | |
190 | break; | |
191 | case 270: | |
192 | /* Set RAM address control */ | |
193 | write_reg(par, 0x88 | |
194 | | (0x0 & 0x1) << 2 /* Increment positively */ | |
195 | | (0x1 & 0x1) << 1 /* Increment page first */ | |
196 | | (0x1 & 0x1)); /* Wrap around (default) */ | |
197 | ||
198 | /* Set LCD mapping */ | |
199 | write_reg(par, 0xC0 | |
200 | | (0x1 & 0x1) << 2 /* Mirror Y ON */ | |
201 | | (0x1 & 0x1) << 1 /* Mirror X ON */ | |
202 | | (0x0 & 0x1)); /* MS nibble last (default) */ | |
203 | break; | |
204 | default: | |
205 | /* Set RAM address control */ | |
206 | write_reg(par, 0x88 | |
207 | | (0x0 & 0x1) << 2 /* Increment positively */ | |
208 | | (0x0 & 0x1) << 1 /* Increment column first */ | |
209 | | (0x1 & 0x1)); /* Wrap around (default) */ | |
210 | ||
211 | /* Set LCD mapping */ | |
212 | write_reg(par, 0xC0 | |
213 | | (0x0 & 0x1) << 2 /* Mirror Y OFF */ | |
214 | | (0x1 & 0x1) << 1 /* Mirror X ON */ | |
215 | | (0x0 & 0x1)); /* MS nibble last (default) */ | |
216 | break; | |
217 | } | |
218 | ||
219 | return 0; | |
220 | } | |
221 | ||
222 | static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) | |
223 | { | |
4b6dc179 | 224 | u8 *vmem8 = (u8 *)(par->info->screen_buffer); |
5084d7c6 JR |
225 | u8 *buf8 = par->txbuf.buf; |
226 | u16 *buf16 = par->txbuf.buf; | |
a1560f9b HC |
227 | int line_length = par->info->fix.line_length; |
228 | int y_start = (offset / line_length); | |
229 | int y_end = (offset + len - 1) / line_length; | |
230 | int x, y, i; | |
231 | int ret = 0; | |
232 | ||
a1560f9b HC |
233 | switch (par->pdata->display.buswidth) { |
234 | case 8: | |
235 | switch (par->info->var.rotate) { | |
236 | case 90: | |
237 | case 270: | |
238 | i = y_start * line_length; | |
239 | for (y = y_start; y <= y_end; y++) { | |
240 | for (x = 0; x < line_length; x += 2) { | |
241 | *buf8 = vmem8[i] >> 4; | |
242 | *buf8 |= vmem8[i + 1] & 0xF0; | |
243 | buf8++; | |
244 | i += 2; | |
245 | } | |
246 | } | |
247 | break; | |
248 | default: | |
249 | /* Must be even because pages are two lines */ | |
250 | y_start &= 0xFE; | |
251 | i = y_start * line_length; | |
252 | for (y = y_start; y <= y_end; y += 2) { | |
253 | for (x = 0; x < line_length; x++) { | |
254 | *buf8 = vmem8[i] >> 4; | |
255 | *buf8 |= vmem8[i + line_length] & 0xF0; | |
256 | buf8++; | |
257 | i++; | |
258 | } | |
259 | i += line_length; | |
260 | } | |
261 | break; | |
262 | } | |
263 | gpio_set_value(par->gpio.dc, 1); | |
264 | ||
265 | /* Write data */ | |
266 | ret = par->fbtftops.write(par, par->txbuf.buf, len / 2); | |
267 | break; | |
268 | case 9: | |
269 | switch (par->info->var.rotate) { | |
270 | case 90: | |
271 | case 270: | |
272 | i = y_start * line_length; | |
273 | for (y = y_start; y <= y_end; y++) { | |
274 | for (x = 0; x < line_length; x += 2) { | |
275 | *buf16 = 0x100; | |
276 | *buf16 |= vmem8[i] >> 4; | |
277 | *buf16 |= vmem8[i + 1] & 0xF0; | |
278 | buf16++; | |
279 | i += 2; | |
280 | } | |
281 | } | |
282 | break; | |
283 | default: | |
284 | /* Must be even because pages are two lines */ | |
285 | y_start &= 0xFE; | |
286 | i = y_start * line_length; | |
287 | for (y = y_start; y <= y_end; y += 2) { | |
288 | for (x = 0; x < line_length; x++) { | |
289 | *buf16 = 0x100; | |
290 | *buf16 |= vmem8[i] >> 4; | |
291 | *buf16 |= vmem8[i + line_length] & 0xF0; | |
292 | buf16++; | |
293 | i++; | |
294 | } | |
295 | i += line_length; | |
296 | } | |
297 | break; | |
298 | } | |
299 | ||
300 | /* Write data */ | |
301 | ret = par->fbtftops.write(par, par->txbuf.buf, len); | |
302 | break; | |
303 | default: | |
304 | dev_err(par->info->device, "unsupported buswidth %d\n", | |
305 | par->pdata->display.buswidth); | |
306 | } | |
307 | ||
308 | if (ret < 0) | |
309 | dev_err(par->info->device, "write failed and returned: %d\n", | |
310 | ret); | |
311 | ||
312 | return ret; | |
313 | } | |
314 | ||
315 | static struct fbtft_display display = { | |
316 | .txbuflen = -1, | |
317 | .regwidth = 8, | |
318 | .width = WIDTH, | |
319 | .height = HEIGHT, | |
320 | .bpp = BPP, | |
321 | .fps = FPS, | |
322 | .fbtftops = { | |
323 | .write_vmem = write_vmem, | |
324 | .init_display = init_display, | |
325 | .set_addr_win = set_addr_win, | |
326 | .set_var = set_var, | |
327 | .blank = blank, | |
328 | }, | |
329 | }; | |
330 | ||
331 | FBTFT_REGISTER_DRIVER(DRVNAME, "ultrachip,uc1611", &display); | |
332 | ||
333 | MODULE_ALIAS("spi:" DRVNAME); | |
334 | MODULE_ALIAS("platform:" DRVNAME); | |
335 | MODULE_ALIAS("spi:uc1611"); | |
336 | MODULE_ALIAS("platform:uc1611"); | |
337 | ||
338 | MODULE_DESCRIPTION("FB driver for the UC1611 LCD controller"); | |
339 | MODULE_AUTHOR("Henri Chain"); | |
340 | MODULE_LICENSE("GPL"); |