mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
![Scott Matheina](/assets/img/avatar_default.png)
These changes where identified by checkpatch.pl as needed changes to align the code with the linux development coding style. The several lines of text where aligned with the precending parenthesis. Signed-off-by: Scott Matheina <scott@matheina.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
464 lines
11 KiB
C
464 lines
11 KiB
C
/*
|
|
* FB driver for Two KS0108 LCD controllers in AGM1264K-FL display
|
|
*
|
|
* Copyright (C) 2014 ololoshka2871
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "fbtft.h"
|
|
|
|
/* Uncomment text line to use negative image on display */
|
|
/*#define NEGATIVE*/
|
|
|
|
#define WHITE 0xff
|
|
#define BLACK 0
|
|
|
|
#define DRVNAME "fb_agm1264k-fl"
|
|
#define WIDTH 64
|
|
#define HEIGHT 64
|
|
#define TOTALWIDTH (WIDTH * 2) /* because 2 x ks0108 in one display */
|
|
#define FPS 20
|
|
|
|
#define EPIN gpio.wr
|
|
#define RS gpio.dc
|
|
#define RW gpio.aux[2]
|
|
#define CS0 gpio.aux[0]
|
|
#define CS1 gpio.aux[1]
|
|
|
|
/* diffusing error (Floyd-Steinberg) */
|
|
#define DIFFUSING_MATRIX_WIDTH 2
|
|
#define DIFFUSING_MATRIX_HEIGHT 2
|
|
|
|
static const signed char
|
|
diffusing_matrix[DIFFUSING_MATRIX_WIDTH][DIFFUSING_MATRIX_HEIGHT] = {
|
|
{-1, 3},
|
|
{3, 2},
|
|
};
|
|
|
|
static const unsigned char gamma_correction_table[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6,
|
|
6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13,
|
|
13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21,
|
|
22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32,
|
|
33, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45,
|
|
46, 47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
|
|
62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 81,
|
|
82, 83, 84, 85, 87, 88, 89, 90, 91, 93, 94, 95, 97, 98, 99, 100, 102,
|
|
103, 105, 106, 107, 109, 110, 111, 113, 114, 116, 117, 119, 120, 121,
|
|
123, 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 141, 143,
|
|
145, 146, 148, 149, 151, 153, 154, 156, 158, 159, 161, 163, 165, 166,
|
|
168, 170, 172, 173, 175, 177, 179, 181, 182, 184, 186, 188, 190, 192,
|
|
194, 196, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219,
|
|
221, 223, 225, 227, 229, 231, 234, 236, 238, 240, 242, 244, 246, 248,
|
|
251, 253, 255
|
|
};
|
|
|
|
static int init_display(struct fbtft_par *par)
|
|
{
|
|
u8 i;
|
|
|
|
par->fbtftops.reset(par);
|
|
|
|
for (i = 0; i < 2; ++i) {
|
|
write_reg(par, i, 0x3f); /* display on */
|
|
write_reg(par, i, 0x40); /* set x to 0 */
|
|
write_reg(par, i, 0xb0); /* set page to 0 */
|
|
write_reg(par, i, 0xc0); /* set start line to 0 */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void reset(struct fbtft_par *par)
|
|
{
|
|
if (par->gpio.reset == -1)
|
|
return;
|
|
|
|
dev_dbg(par->info->device, "%s()\n", __func__);
|
|
|
|
gpio_set_value(par->gpio.reset, 0);
|
|
udelay(20);
|
|
gpio_set_value(par->gpio.reset, 1);
|
|
mdelay(120);
|
|
}
|
|
|
|
/* Check if all necessary GPIOS defined */
|
|
static int verify_gpios(struct fbtft_par *par)
|
|
{
|
|
int i;
|
|
|
|
dev_dbg(par->info->device,
|
|
"%s()\n", __func__);
|
|
|
|
if (par->EPIN < 0) {
|
|
dev_err(par->info->device,
|
|
"Missing info about 'wr' (aka E) gpio. Aborting.\n");
|
|
return -EINVAL;
|
|
}
|
|
for (i = 0; i < 8; ++i) {
|
|
if (par->gpio.db[i] < 0) {
|
|
dev_err(par->info->device,
|
|
"Missing info about 'db[%i]' gpio. Aborting.\n",
|
|
i);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (par->CS0 < 0) {
|
|
dev_err(par->info->device,
|
|
"Missing info about 'cs0' gpio. Aborting.\n");
|
|
return -EINVAL;
|
|
}
|
|
if (par->CS1 < 0) {
|
|
dev_err(par->info->device,
|
|
"Missing info about 'cs1' gpio. Aborting.\n");
|
|
return -EINVAL;
|
|
}
|
|
if (par->RW < 0) {
|
|
dev_err(par->info->device,
|
|
"Missing info about 'rw' gpio. Aborting.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long
|
|
request_gpios_match(struct fbtft_par *par, const struct fbtft_gpio *gpio)
|
|
{
|
|
dev_dbg(par->info->device,
|
|
"%s('%s')\n", __func__, gpio->name);
|
|
|
|
if (strcasecmp(gpio->name, "wr") == 0) {
|
|
/* left ks0108 E pin */
|
|
par->EPIN = gpio->gpio;
|
|
return GPIOF_OUT_INIT_LOW;
|
|
} else if (strcasecmp(gpio->name, "cs0") == 0) {
|
|
/* left ks0108 controller pin */
|
|
par->CS0 = gpio->gpio;
|
|
return GPIOF_OUT_INIT_HIGH;
|
|
} else if (strcasecmp(gpio->name, "cs1") == 0) {
|
|
/* right ks0108 controller pin */
|
|
par->CS1 = gpio->gpio;
|
|
return GPIOF_OUT_INIT_HIGH;
|
|
}
|
|
|
|
/* if write (rw = 0) e(1->0) perform write */
|
|
/* if read (rw = 1) e(0->1) set data on D0-7*/
|
|
else if (strcasecmp(gpio->name, "rw") == 0) {
|
|
par->RW = gpio->gpio;
|
|
return GPIOF_OUT_INIT_LOW;
|
|
}
|
|
|
|
return FBTFT_GPIO_NO_MATCH;
|
|
}
|
|
|
|
/* This function oses to enter commands
|
|
* first byte - destination controller 0 or 1
|
|
* following - commands
|
|
*/
|
|
static void write_reg8_bus8(struct fbtft_par *par, int len, ...)
|
|
{
|
|
va_list args;
|
|
int i, ret;
|
|
u8 *buf = par->buf;
|
|
|
|
if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) {
|
|
va_start(args, len);
|
|
for (i = 0; i < len; i++)
|
|
buf[i] = (u8)va_arg(args, unsigned int);
|
|
|
|
va_end(args);
|
|
fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, par->info->device,
|
|
u8, buf, len, "%s: ", __func__);
|
|
}
|
|
|
|
va_start(args, len);
|
|
|
|
*buf = (u8)va_arg(args, unsigned int);
|
|
|
|
if (*buf > 1) {
|
|
va_end(args);
|
|
dev_err(par->info->device,
|
|
"Incorrect chip select request (%d)\n", *buf);
|
|
return;
|
|
}
|
|
|
|
/* select chip */
|
|
if (*buf) {
|
|
/* cs1 */
|
|
gpio_set_value(par->CS0, 1);
|
|
gpio_set_value(par->CS1, 0);
|
|
} else {
|
|
/* cs0 */
|
|
gpio_set_value(par->CS0, 0);
|
|
gpio_set_value(par->CS1, 1);
|
|
}
|
|
|
|
gpio_set_value(par->RS, 0); /* RS->0 (command mode) */
|
|
len--;
|
|
|
|
if (len) {
|
|
i = len;
|
|
while (i--)
|
|
*buf++ = (u8)va_arg(args, unsigned int);
|
|
ret = par->fbtftops.write(par, par->buf, len * (sizeof(u8)));
|
|
if (ret < 0) {
|
|
va_end(args);
|
|
dev_err(par->info->device,
|
|
"write() failed and returned %d\n", ret);
|
|
return;
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
static struct
|
|
{
|
|
int xs, ys_page, xe, ye_page;
|
|
} addr_win;
|
|
|
|
/* save display writing zone */
|
|
static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
|
|
{
|
|
addr_win.xs = xs;
|
|
addr_win.ys_page = ys / 8;
|
|
addr_win.xe = xe;
|
|
addr_win.ye_page = ye / 8;
|
|
}
|
|
|
|
static void
|
|
construct_line_bitmap(struct fbtft_par *par, u8 *dest, signed short *src,
|
|
int xs, int xe, int y)
|
|
{
|
|
int x, i;
|
|
|
|
for (x = xs; x < xe; ++x) {
|
|
u8 res = 0;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
if (src[(y * 8 + i) * par->info->var.xres + x])
|
|
res |= 1 << i;
|
|
#ifdef NEGATIVE
|
|
*dest++ = res;
|
|
#else
|
|
*dest++ = ~res;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void iterate_diffusion_matrix(u32 xres, u32 yres, int x,
|
|
int y, signed short *convert_buf,
|
|
signed short pixel, signed short error)
|
|
{
|
|
u16 i, j;
|
|
|
|
/* diffusion matrix row */
|
|
for (i = 0; i < DIFFUSING_MATRIX_WIDTH; ++i)
|
|
/* diffusion matrix column */
|
|
for (j = 0; j < DIFFUSING_MATRIX_HEIGHT; ++j) {
|
|
signed short *write_pos;
|
|
signed char coeff;
|
|
|
|
/* skip pixels out of zone */
|
|
if (x + i < 0 || x + i >= xres || y + j >= yres)
|
|
continue;
|
|
write_pos = &convert_buf[(y + j) * xres + x + i];
|
|
coeff = diffusing_matrix[i][j];
|
|
if (-1 == coeff)
|
|
/* pixel itself */
|
|
*write_pos = pixel;
|
|
else {
|
|
signed short p = *write_pos + error * coeff;
|
|
|
|
if (p > WHITE)
|
|
p = WHITE;
|
|
if (p < BLACK)
|
|
p = BLACK;
|
|
*write_pos = p;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
|
|
{
|
|
u16 *vmem16 = (u16 *)par->info->screen_buffer;
|
|
u8 *buf = par->txbuf.buf;
|
|
int x, y;
|
|
int ret = 0;
|
|
|
|
/* buffer to convert RGB565 -> grayscale16 -> Dithered image 1bpp */
|
|
signed short *convert_buf = kmalloc_array(par->info->var.xres *
|
|
par->info->var.yres, sizeof(signed short), GFP_NOIO);
|
|
|
|
if (!convert_buf)
|
|
return -ENOMEM;
|
|
|
|
/* converting to grayscale16 */
|
|
for (x = 0; x < par->info->var.xres; ++x)
|
|
for (y = 0; y < par->info->var.yres; ++y) {
|
|
u16 pixel = vmem16[y * par->info->var.xres + x];
|
|
u16 b = pixel & 0x1f;
|
|
u16 g = (pixel & (0x3f << 5)) >> 5;
|
|
u16 r = (pixel & (0x1f << (5 + 6))) >> (5 + 6);
|
|
|
|
pixel = (299 * r + 587 * g + 114 * b) / 200;
|
|
if (pixel > 255)
|
|
pixel = 255;
|
|
|
|
/* gamma-correction by table */
|
|
convert_buf[y * par->info->var.xres + x] =
|
|
(signed short)gamma_correction_table[pixel];
|
|
}
|
|
|
|
/* Image Dithering */
|
|
for (x = 0; x < par->info->var.xres; ++x)
|
|
for (y = 0; y < par->info->var.yres; ++y) {
|
|
signed short pixel =
|
|
convert_buf[y * par->info->var.xres + x];
|
|
signed short error_b = pixel - BLACK;
|
|
signed short error_w = pixel - WHITE;
|
|
signed short error;
|
|
|
|
/* what color close? */
|
|
if (abs(error_b) >= abs(error_w)) {
|
|
/* white */
|
|
error = error_w;
|
|
pixel = 0xff;
|
|
} else {
|
|
/* black */
|
|
error = error_b;
|
|
pixel = 0;
|
|
}
|
|
|
|
error /= 8;
|
|
|
|
iterate_diffusion_matrix(par->info->var.xres,
|
|
par->info->var.yres,
|
|
x, y, convert_buf,
|
|
pixel, error);
|
|
}
|
|
|
|
/* 1 string = 2 pages */
|
|
for (y = addr_win.ys_page; y <= addr_win.ye_page; ++y) {
|
|
/* left half of display */
|
|
if (addr_win.xs < par->info->var.xres / 2) {
|
|
construct_line_bitmap(par, buf, convert_buf,
|
|
addr_win.xs,
|
|
par->info->var.xres / 2, y);
|
|
|
|
len = par->info->var.xres / 2 - addr_win.xs;
|
|
|
|
/* select left side (sc0)
|
|
* set addr
|
|
*/
|
|
write_reg(par, 0x00, (1 << 6) | (u8)addr_win.xs);
|
|
write_reg(par, 0x00, (0x17 << 3) | (u8)y);
|
|
|
|
/* write bitmap */
|
|
gpio_set_value(par->RS, 1); /* RS->1 (data mode) */
|
|
ret = par->fbtftops.write(par, buf, len);
|
|
if (ret < 0)
|
|
dev_err(par->info->device,
|
|
"write failed and returned: %d\n",
|
|
ret);
|
|
}
|
|
/* right half of display */
|
|
if (addr_win.xe >= par->info->var.xres / 2) {
|
|
construct_line_bitmap(par, buf,
|
|
convert_buf,
|
|
par->info->var.xres / 2,
|
|
addr_win.xe + 1, y);
|
|
|
|
len = addr_win.xe + 1 - par->info->var.xres / 2;
|
|
|
|
/* select right side (sc1)
|
|
* set addr
|
|
*/
|
|
write_reg(par, 0x01, 1 << 6);
|
|
write_reg(par, 0x01, (0x17 << 3) | (u8)y);
|
|
|
|
/* write bitmap */
|
|
gpio_set_value(par->RS, 1); /* RS->1 (data mode) */
|
|
par->fbtftops.write(par, buf, len);
|
|
if (ret < 0)
|
|
dev_err(par->info->device,
|
|
"write failed and returned: %d\n",
|
|
ret);
|
|
}
|
|
}
|
|
kfree(convert_buf);
|
|
|
|
gpio_set_value(par->CS0, 1);
|
|
gpio_set_value(par->CS1, 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int write(struct fbtft_par *par, void *buf, size_t len)
|
|
{
|
|
fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
|
|
"%s(len=%d): ", __func__, len);
|
|
|
|
gpio_set_value(par->RW, 0); /* set write mode */
|
|
|
|
while (len--) {
|
|
u8 i, data;
|
|
|
|
data = *(u8 *)buf++;
|
|
|
|
/* set data bus */
|
|
for (i = 0; i < 8; ++i)
|
|
gpio_set_value(par->gpio.db[i], data & (1 << i));
|
|
/* set E */
|
|
gpio_set_value(par->EPIN, 1);
|
|
udelay(5);
|
|
/* unset E - write */
|
|
gpio_set_value(par->EPIN, 0);
|
|
udelay(1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct fbtft_display display = {
|
|
.regwidth = 8,
|
|
.width = TOTALWIDTH,
|
|
.height = HEIGHT,
|
|
.fps = FPS,
|
|
.fbtftops = {
|
|
.init_display = init_display,
|
|
.set_addr_win = set_addr_win,
|
|
.verify_gpios = verify_gpios,
|
|
.request_gpios_match = request_gpios_match,
|
|
.reset = reset,
|
|
.write = write,
|
|
.write_register = write_reg8_bus8,
|
|
.write_vmem = write_vmem,
|
|
},
|
|
};
|
|
|
|
FBTFT_REGISTER_DRIVER(DRVNAME, "displaytronic,fb_agm1264k-fl", &display);
|
|
|
|
MODULE_ALIAS("platform:" DRVNAME);
|
|
|
|
MODULE_DESCRIPTION("Two KS0108 LCD controllers in AGM1264K-FL display");
|
|
MODULE_AUTHOR("ololoshka2871");
|
|
MODULE_LICENSE("GPL");
|