File Index
Files and directories
Sign in
FILE

fb_agm1264k-fl.c

File preview with syntax highlighting.

linux-6.19.14/drivers/staging/fbtft/fb_agm1264k-fl.c C
  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
 39static const signed char
 40diffusing_matrix[DIFFUSING_MATRIX_WIDTH][DIFFUSING_MATRIX_HEIGHT] = {
 41	{-1, 3},
 42	{3, 2},
 43};
 44
 45static const unsigned char gamma_correction_table[] = {
 460, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
 471, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6,
 486, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13,
 4913, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21,
 5022, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32,
 5133, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45,
 5246, 47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
 5362, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 81,
 5482, 83, 84, 85, 87, 88, 89, 90, 91, 93, 94, 95, 97, 98, 99, 100, 102,
 55103, 105, 106, 107, 109, 110, 111, 113, 114, 116, 117, 119, 120, 121,
 56123, 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 141, 143,
 57145, 146, 148, 149, 151, 153, 154, 156, 158, 159, 161, 163, 165, 166,
 58168, 170, 172, 173, 175, 177, 179, 181, 182, 184, 186, 188, 190, 192,
 59194, 196, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219,
 60221, 223, 225, 227, 229, 231, 234, 236, 238, 240, 242, 244, 246, 248,
 61251, 253, 255
 62};
 63
 64static 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 */
 81static 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
120static unsigned long
121request_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(gpio->name, "wr") == 0) {
127		/* left ks0108 E pin */
128		par->EPIN = gpio->gpio;
129		return GPIOD_OUT_LOW;
130	} else if (strcasecmp(gpio->name, "cs0") == 0) {
131		/* left ks0108 controller pin */
132		par->CS0 = gpio->gpio;
133		return GPIOD_OUT_HIGH;
134	} else if (strcasecmp(gpio->name, "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(gpio->name, "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 */
154static 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(par->CS0, 0);
185		gpiod_set_value(par->CS1, 1);
186	} else {
187		/* cs0 */
188		gpiod_set_value(par->CS0, 1);
189		gpiod_set_value(par->CS1, 0);
190	}
191
192	gpiod_set_value(par->RS, 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
211static struct
212{
213	int xs, ys_page, xe, ye_page;
214} addr_win;
215
216/* save display writing zone */
217static 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
225static void
226construct_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
245static 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
278static 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(par->info->var.xres *
287		par->info->var.yres, 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(par->info->var.xres,
332						 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, buf, convert_buf,
342					      addr_win.xs,
343					      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(par->RS, 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, buf,
364					      convert_buf,
365					      par->info->var.xres / 2,
366					      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(par->RS, 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(convert_buf);
386
387	gpiod_set_value(par->CS0, 0);
388	gpiod_set_value(par->CS1, 0);
389
390	return ret;
391}
392
393static 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(par->RW, 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(par->gpio.db[i], data & (1 << i));
408		/* set E */
409		gpiod_set_value(par->EPIN, 0);
410		udelay(5);
411		/* unset E - write */
412		gpiod_set_value(par->EPIN, 1);
413		udelay(1);
414	}
415
416	return 0;
417}
418
419static 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
435FBTFT_REGISTER_DRIVER(DRVNAME, "displaytronic,fb_agm1264k-fl", &display);
436
437MODULE_ALIAS("platform:" DRVNAME);
438
439MODULE_DESCRIPTION("Two KS0108 LCD controllers in AGM1264K-FL display");
440MODULE_AUTHOR("ololoshka2871");
441MODULE_LICENSE("GPL");
~/ Read-only file preview.