1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * FB driver for Two KS0108 LCD controllers in AGM1264K-FL display |
4 | * |
5 | * Copyright (C) 2014 ololoshka2871 |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/init.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/slab.h> |
14 | |
15 | #include "fbtft.h" |
16 | |
17 | /* Uncomment text line to use negative image on display */ |
18 | /*#define NEGATIVE*/ |
19 | |
20 | #define WHITE 0xff |
21 | #define BLACK 0 |
22 | |
23 | #define DRVNAME "fb_agm1264k-fl" |
24 | #define WIDTH 64 |
25 | #define HEIGHT 64 |
26 | #define TOTALWIDTH (WIDTH * 2) /* because 2 x ks0108 in one display */ |
27 | #define FPS 20 |
28 | |
29 | #define EPIN gpio.wr |
30 | #define RS gpio.dc |
31 | #define RW gpio.aux[2] |
32 | #define CS0 gpio.aux[0] |
33 | #define CS1 gpio.aux[1] |
34 | |
35 | /* diffusing error (Floyd-Steinberg) */ |
36 | #define DIFFUSING_MATRIX_WIDTH 2 |
37 | #define DIFFUSING_MATRIX_HEIGHT 2 |
38 | |
39 | static const signed char |
40 | diffusing_matrix[DIFFUSING_MATRIX_WIDTH][DIFFUSING_MATRIX_HEIGHT] = { |
41 | {-1, 3}, |
42 | {3, 2}, |
43 | }; |
44 | |
45 | static const unsigned char gamma_correction_table[] = { |
46 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, |
47 | 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, |
48 | 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, |
49 | 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, |
50 | 22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, |
51 | 33, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, |
52 | 46, 47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, |
53 | 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 81, |
54 | 82, 83, 84, 85, 87, 88, 89, 90, 91, 93, 94, 95, 97, 98, 99, 100, 102, |
55 | 103, 105, 106, 107, 109, 110, 111, 113, 114, 116, 117, 119, 120, 121, |
56 | 123, 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 141, 143, |
57 | 145, 146, 148, 149, 151, 153, 154, 156, 158, 159, 161, 163, 165, 166, |
58 | 168, 170, 172, 173, 175, 177, 179, 181, 182, 184, 186, 188, 190, 192, |
59 | 194, 196, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, |
60 | 221, 223, 225, 227, 229, 231, 234, 236, 238, 240, 242, 244, 246, 248, |
61 | 251, 253, 255 |
62 | }; |
63 | |
64 | static int init_display(struct fbtft_par *par) |
65 | { |
66 | u8 i; |
67 | |
68 | par->fbtftops.reset(par); |
69 | |
70 | for (i = 0; i < 2; ++i) { |
71 | write_reg(par, i, 0x3f); /* display on */ |
72 | write_reg(par, i, 0x40); /* set x to 0 */ |
73 | write_reg(par, i, 0xb0); /* set page to 0 */ |
74 | write_reg(par, i, 0xc0); /* set start line to 0 */ |
75 | } |
76 | |
77 | return 0; |
78 | } |
79 | |
80 | /* Check if all necessary GPIOS defined */ |
81 | static int verify_gpios(struct fbtft_par *par) |
82 | { |
83 | int i; |
84 | |
85 | dev_dbg(par->info->device, |
86 | "%s()\n" , __func__); |
87 | |
88 | if (!par->EPIN) { |
89 | dev_err(par->info->device, |
90 | "Missing info about 'wr' (aka E) gpio. Aborting.\n" ); |
91 | return -EINVAL; |
92 | } |
93 | for (i = 0; i < 8; ++i) { |
94 | if (!par->gpio.db[i]) { |
95 | dev_err(par->info->device, |
96 | "Missing info about 'db[%i]' gpio. Aborting.\n" , |
97 | i); |
98 | return -EINVAL; |
99 | } |
100 | } |
101 | if (!par->CS0) { |
102 | dev_err(par->info->device, |
103 | "Missing info about 'cs0' gpio. Aborting.\n" ); |
104 | return -EINVAL; |
105 | } |
106 | if (!par->CS1) { |
107 | dev_err(par->info->device, |
108 | "Missing info about 'cs1' gpio. Aborting.\n" ); |
109 | return -EINVAL; |
110 | } |
111 | if (!par->RW) { |
112 | dev_err(par->info->device, |
113 | "Missing info about 'rw' gpio. Aborting.\n" ); |
114 | return -EINVAL; |
115 | } |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | static unsigned long |
121 | request_gpios_match(struct fbtft_par *par, const struct fbtft_gpio *gpio) |
122 | { |
123 | dev_dbg(par->info->device, |
124 | "%s('%s')\n" , __func__, gpio->name); |
125 | |
126 | if (strcasecmp(s1: gpio->name, s2: "wr" ) == 0) { |
127 | /* left ks0108 E pin */ |
128 | par->EPIN = gpio->gpio; |
129 | return GPIOD_OUT_LOW; |
130 | } else if (strcasecmp(s1: gpio->name, s2: "cs0" ) == 0) { |
131 | /* left ks0108 controller pin */ |
132 | par->CS0 = gpio->gpio; |
133 | return GPIOD_OUT_HIGH; |
134 | } else if (strcasecmp(s1: gpio->name, s2: "cs1" ) == 0) { |
135 | /* right ks0108 controller pin */ |
136 | par->CS1 = gpio->gpio; |
137 | return GPIOD_OUT_HIGH; |
138 | } |
139 | |
140 | /* if write (rw = 0) e(1->0) perform write */ |
141 | /* if read (rw = 1) e(0->1) set data on D0-7*/ |
142 | else if (strcasecmp(s1: gpio->name, s2: "rw" ) == 0) { |
143 | par->RW = gpio->gpio; |
144 | return GPIOD_OUT_LOW; |
145 | } |
146 | |
147 | return FBTFT_GPIO_NO_MATCH; |
148 | } |
149 | |
150 | /* This function oses to enter commands |
151 | * first byte - destination controller 0 or 1 |
152 | * following - commands |
153 | */ |
154 | static void write_reg8_bus8(struct fbtft_par *par, int len, ...) |
155 | { |
156 | va_list args; |
157 | int i, ret; |
158 | u8 *buf = par->buf; |
159 | |
160 | if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { |
161 | va_start(args, len); |
162 | for (i = 0; i < len; i++) |
163 | buf[i] = (u8)va_arg(args, unsigned int); |
164 | |
165 | va_end(args); |
166 | fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, par->info->device, |
167 | u8, buf, len, "%s: " , __func__); |
168 | } |
169 | |
170 | va_start(args, len); |
171 | |
172 | *buf = (u8)va_arg(args, unsigned int); |
173 | |
174 | if (*buf > 1) { |
175 | va_end(args); |
176 | dev_err(par->info->device, |
177 | "Incorrect chip select request (%d)\n" , *buf); |
178 | return; |
179 | } |
180 | |
181 | /* select chip */ |
182 | if (*buf) { |
183 | /* cs1 */ |
184 | gpiod_set_value(desc: par->CS0, value: 0); |
185 | gpiod_set_value(desc: par->CS1, value: 1); |
186 | } else { |
187 | /* cs0 */ |
188 | gpiod_set_value(desc: par->CS0, value: 1); |
189 | gpiod_set_value(desc: par->CS1, value: 0); |
190 | } |
191 | |
192 | gpiod_set_value(desc: par->RS, value: 0); /* RS->0 (command mode) */ |
193 | len--; |
194 | |
195 | if (len) { |
196 | i = len; |
197 | while (i--) |
198 | *buf++ = (u8)va_arg(args, unsigned int); |
199 | ret = par->fbtftops.write(par, par->buf, len * (sizeof(u8))); |
200 | if (ret < 0) { |
201 | va_end(args); |
202 | dev_err(par->info->device, |
203 | "write() failed and returned %d\n" , ret); |
204 | return; |
205 | } |
206 | } |
207 | |
208 | va_end(args); |
209 | } |
210 | |
211 | static struct |
212 | { |
213 | int xs, ys_page, xe, ye_page; |
214 | } addr_win; |
215 | |
216 | /* save display writing zone */ |
217 | static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) |
218 | { |
219 | addr_win.xs = xs; |
220 | addr_win.ys_page = ys / 8; |
221 | addr_win.xe = xe; |
222 | addr_win.ye_page = ye / 8; |
223 | } |
224 | |
225 | static void |
226 | construct_line_bitmap(struct fbtft_par *par, u8 *dest, signed short *src, |
227 | int xs, int xe, int y) |
228 | { |
229 | int x, i; |
230 | |
231 | for (x = xs; x < xe; ++x) { |
232 | u8 res = 0; |
233 | |
234 | for (i = 0; i < 8; i++) |
235 | if (src[(y * 8 + i) * par->info->var.xres + x]) |
236 | res |= 1 << i; |
237 | #ifdef NEGATIVE |
238 | *dest++ = res; |
239 | #else |
240 | *dest++ = ~res; |
241 | #endif |
242 | } |
243 | } |
244 | |
245 | static void iterate_diffusion_matrix(u32 xres, u32 yres, int x, |
246 | int y, signed short *convert_buf, |
247 | signed short pixel, signed short error) |
248 | { |
249 | u16 i, j; |
250 | |
251 | /* diffusion matrix row */ |
252 | for (i = 0; i < DIFFUSING_MATRIX_WIDTH; ++i) |
253 | /* diffusion matrix column */ |
254 | for (j = 0; j < DIFFUSING_MATRIX_HEIGHT; ++j) { |
255 | signed short *write_pos; |
256 | signed char coeff; |
257 | |
258 | /* skip pixels out of zone */ |
259 | if (x + i < 0 || x + i >= xres || y + j >= yres) |
260 | continue; |
261 | write_pos = &convert_buf[(y + j) * xres + x + i]; |
262 | coeff = diffusing_matrix[i][j]; |
263 | if (-1 == coeff) { |
264 | /* pixel itself */ |
265 | *write_pos = pixel; |
266 | } else { |
267 | signed short p = *write_pos + error * coeff; |
268 | |
269 | if (p > WHITE) |
270 | p = WHITE; |
271 | if (p < BLACK) |
272 | p = BLACK; |
273 | *write_pos = p; |
274 | } |
275 | } |
276 | } |
277 | |
278 | static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) |
279 | { |
280 | u16 *vmem16 = (u16 *)par->info->screen_buffer; |
281 | u8 *buf = par->txbuf.buf; |
282 | int x, y; |
283 | int ret = 0; |
284 | |
285 | /* buffer to convert RGB565 -> grayscale16 -> Dithered image 1bpp */ |
286 | signed short *convert_buf = kmalloc_array(n: par->info->var.xres * |
287 | par->info->var.yres, size: sizeof(signed short), GFP_NOIO); |
288 | |
289 | if (!convert_buf) |
290 | return -ENOMEM; |
291 | |
292 | /* converting to grayscale16 */ |
293 | for (x = 0; x < par->info->var.xres; ++x) |
294 | for (y = 0; y < par->info->var.yres; ++y) { |
295 | u16 pixel = vmem16[y * par->info->var.xres + x]; |
296 | u16 b = pixel & 0x1f; |
297 | u16 g = (pixel & (0x3f << 5)) >> 5; |
298 | u16 r = (pixel & (0x1f << (5 + 6))) >> (5 + 6); |
299 | |
300 | pixel = (299 * r + 587 * g + 114 * b) / 200; |
301 | if (pixel > 255) |
302 | pixel = 255; |
303 | |
304 | /* gamma-correction by table */ |
305 | convert_buf[y * par->info->var.xres + x] = |
306 | (signed short)gamma_correction_table[pixel]; |
307 | } |
308 | |
309 | /* Image Dithering */ |
310 | for (x = 0; x < par->info->var.xres; ++x) |
311 | for (y = 0; y < par->info->var.yres; ++y) { |
312 | signed short pixel = |
313 | convert_buf[y * par->info->var.xres + x]; |
314 | signed short error_b = pixel - BLACK; |
315 | signed short error_w = pixel - WHITE; |
316 | signed short error; |
317 | |
318 | /* what color close? */ |
319 | if (abs(error_b) >= abs(error_w)) { |
320 | /* white */ |
321 | error = error_w; |
322 | pixel = 0xff; |
323 | } else { |
324 | /* black */ |
325 | error = error_b; |
326 | pixel = 0; |
327 | } |
328 | |
329 | error /= 8; |
330 | |
331 | iterate_diffusion_matrix(xres: par->info->var.xres, |
332 | yres: par->info->var.yres, |
333 | x, y, convert_buf, |
334 | pixel, error); |
335 | } |
336 | |
337 | /* 1 string = 2 pages */ |
338 | for (y = addr_win.ys_page; y <= addr_win.ye_page; ++y) { |
339 | /* left half of display */ |
340 | if (addr_win.xs < par->info->var.xres / 2) { |
341 | construct_line_bitmap(par, dest: buf, src: convert_buf, |
342 | xs: addr_win.xs, |
343 | xe: par->info->var.xres / 2, y); |
344 | |
345 | len = par->info->var.xres / 2 - addr_win.xs; |
346 | |
347 | /* select left side (sc0) |
348 | * set addr |
349 | */ |
350 | write_reg(par, 0x00, BIT(6) | (u8)addr_win.xs); |
351 | write_reg(par, 0x00, (0x17 << 3) | (u8)y); |
352 | |
353 | /* write bitmap */ |
354 | gpiod_set_value(desc: par->RS, value: 1); /* RS->1 (data mode) */ |
355 | ret = par->fbtftops.write(par, buf, len); |
356 | if (ret < 0) |
357 | dev_err(par->info->device, |
358 | "write failed and returned: %d\n" , |
359 | ret); |
360 | } |
361 | /* right half of display */ |
362 | if (addr_win.xe >= par->info->var.xres / 2) { |
363 | construct_line_bitmap(par, dest: buf, |
364 | src: convert_buf, |
365 | xs: par->info->var.xres / 2, |
366 | xe: addr_win.xe + 1, y); |
367 | |
368 | len = addr_win.xe + 1 - par->info->var.xres / 2; |
369 | |
370 | /* select right side (sc1) |
371 | * set addr |
372 | */ |
373 | write_reg(par, 0x01, BIT(6)); |
374 | write_reg(par, 0x01, (0x17 << 3) | (u8)y); |
375 | |
376 | /* write bitmap */ |
377 | gpiod_set_value(desc: par->RS, value: 1); /* RS->1 (data mode) */ |
378 | par->fbtftops.write(par, buf, len); |
379 | if (ret < 0) |
380 | dev_err(par->info->device, |
381 | "write failed and returned: %d\n" , |
382 | ret); |
383 | } |
384 | } |
385 | kfree(objp: convert_buf); |
386 | |
387 | gpiod_set_value(desc: par->CS0, value: 0); |
388 | gpiod_set_value(desc: par->CS1, value: 0); |
389 | |
390 | return ret; |
391 | } |
392 | |
393 | static int write(struct fbtft_par *par, void *buf, size_t len) |
394 | { |
395 | fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, |
396 | "%s(len=%zu): " , __func__, len); |
397 | |
398 | gpiod_set_value(desc: par->RW, value: 0); /* set write mode */ |
399 | |
400 | while (len--) { |
401 | u8 i, data; |
402 | |
403 | data = *(u8 *)buf++; |
404 | |
405 | /* set data bus */ |
406 | for (i = 0; i < 8; ++i) |
407 | gpiod_set_value(desc: par->gpio.db[i], value: data & (1 << i)); |
408 | /* set E */ |
409 | gpiod_set_value(desc: par->EPIN, value: 0); |
410 | udelay(5); |
411 | /* unset E - write */ |
412 | gpiod_set_value(desc: par->EPIN, value: 1); |
413 | udelay(1); |
414 | } |
415 | |
416 | return 0; |
417 | } |
418 | |
419 | static struct fbtft_display display = { |
420 | .regwidth = 8, |
421 | .width = TOTALWIDTH, |
422 | .height = HEIGHT, |
423 | .fps = FPS, |
424 | .fbtftops = { |
425 | .init_display = init_display, |
426 | .set_addr_win = set_addr_win, |
427 | .verify_gpios = verify_gpios, |
428 | .request_gpios_match = request_gpios_match, |
429 | .write = write, |
430 | .write_register = write_reg8_bus8, |
431 | .write_vmem = write_vmem, |
432 | }, |
433 | }; |
434 | |
435 | FBTFT_REGISTER_DRIVER(DRVNAME, "displaytronic,fb_agm1264k-fl" , &display); |
436 | |
437 | MODULE_ALIAS("platform:" DRVNAME); |
438 | |
439 | MODULE_DESCRIPTION("Two KS0108 LCD controllers in AGM1264K-FL display" ); |
440 | MODULE_AUTHOR("ololoshka2871" ); |
441 | MODULE_LICENSE("GPL" ); |
442 | |