notro / fbtft

Linux Framebuffer drivers for small TFT LCD display modules. Development has moved to https://git.kernel.org/cgit/linux/kernel/git/gregkh/staging.git/tree/drivers/staging/fbtft?h=staging-testing
1.85k stars 494 forks source link

Add support AGM1264K-FL monochrme display 128x64 #186

Closed ololoshka2871 closed 9 years ago

ololoshka2871 commented 9 years ago

This display contains two samsung ks0108 compatible controllers 64x64 pixels. Controllers are selected using the CS pins, others pins are common. Translating from RGB565 to 'bitmap' uses defusing error “Floyd-Steinberg” algorithm. My bord is Beaglebone Black

img_20141119_160348

diff --git a/Kconfig b/Kconfig
index a4cb559..5d2fe7a 100644
--- a/Kconfig
+++ b/Kconfig
@@ -8,6 +8,12 @@ menuconfig FB_TFT
    select FB_DEFERRED_IO
    select FB_BACKLIGHT

+config FB_TFT_AGM1264K_FL
+   tristate "FB driver for the AGM1264K-FL LCD display"
+   depends on FB_TFT
+   help
+     Framebuffer support for the AGM1264K-FL LCD display (two Samsung KS0108 compatable chips)
+
 config FB_TFT_BD663474
    tristate "FB driver for the BD663474 LCD Controller"
    depends on FB_TFT
diff --git a/Makefile b/Makefile
index 54377d3..ac0afd3 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_FB_TFT)             += fbtft.o
 fbtft-y                          += fbtft-core.o fbtft-sysfs.o fbtft-bus.o fbtft-io.o

 # drivers
+obj-$(CONFIG_FB_TFT_AGM1264K_FL) += fb_agm1264k-fl.o
 obj-$(CONFIG_FB_TFT_BD663474)    += fb_bd663474.o
 obj-$(CONFIG_FB_TFT_HX8340BN)    += fb_hx8340bn.o
 obj-$(CONFIG_FB_TFT_HX8347D)     += fb_hx8347d.o
diff --git a/fb_agm1264k-fl.c b/fb_agm1264k-fl.c
new file mode 100644
index 0000000..30a6895
--- /dev/null
+++ b/fb_agm1264k-fl.c
@@ -0,0 +1,463 @@
+/*
+ * 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;
+
+   fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__);
+
+   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;
+}
+
+void reset(struct fbtft_par *par)
+{
+   if (par->gpio.reset == -1)
+       return;
+
+   fbtft_dev_dbg(DEBUG_RESET, par, 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;
+
+   fbtft_dev_dbg(DEBUG_VERIFY_GPIOS, par, 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)
+{
+   fbtft_dev_dbg(DEBUG_REQUEST_GPIOS_MATCH, par, 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
+ * folowing - commands
+ */
+static void write_reg8_bus8(struct fbtft_par *par, int len, ...)
+{
+   va_list args;
+   int i, ret;
+   u8 *buf = (u8 *)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, "%s: Incorrect chip sellect request (%d)\n",
+           __func__, *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, "%s: write() failed and returned %d\n",
+               __func__, 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;
+
+   fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par,
+       "%s(xs=%d, ys_page=%d, xe=%d, ye_page=%d)\n", __func__,
+       addr_win.xs, addr_win.ys_page, addr_win.xe, addr_win.ye_page);
+}
+
+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 int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
+{
+   u16 *vmem16 = (u16 *)par->info->screen_base;
+   u8 *buf = par->txbuf.buf;
+   int x, y;
+   int ret = 0;
+
+   /* buffer to convert RGB565 -> grayscale16 -> Ditherd image 1bpp */
+   signed short *convert_buf = kmalloc(par->info->var.xres *
+       par->info->var.yres * sizeof(signed short), GFP_NOIO);
+
+   fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__);
+
+   /* 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;
+           u16 i, j;
+
+           /* 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;
+
+           /* 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 >= par->info->var.xres
+                       || y + j >= par->info->var.yres)
+                       continue;
+                   write_pos = &convert_buf[
+                       (y + j) * par->info->var.xres +
+                       x + i];
+                   coeff = diffusing_matrix[i][j];
+                   if (coeff == -1)
+                       /* 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;
+                   }
+               }
+       }
+
+    /* 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,
+                   "%s: write failed and returned: %d\n",
+                   __func__, 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,
+                   "%s: write failed and returned: %d\n",
+                   __func__, 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");
+
diff --git a/fbtft_device.c b/fbtft_device.c
index 19ddd8f..7396a0a 100644
--- a/fbtft_device.c
+++ b/fbtft_device.c
@@ -286,6 +286,39 @@ static struct fbtft_device_display displays[] = {
            }
        }
    }, {
+       .name = "agm1264k-fl",
+       .pdev = &(struct platform_device) {
+           .name = "fb_agm1264k-fl",
+           .id = 0,
+           .dev = {
+           .release = fbtft_device_pdev_release,
+           .platform_data = &(struct fbtft_platform_data) {
+               .display = {
+                   .buswidth = 8,
+                   .backlight = FBTFT_ONBOARD_BACKLIGHT,
+               },
+               .gpios = (const struct fbtft_gpio []) {
+                   /* for my Beaglebone Black
+                   { "reset", 48 },
+                   { "wr", 60 },  // aka E
+                   { "dc", 112 }, // aka D/I
+                   { "rw", 113 },
+                   { "cs0", 49 },
+                   { "cs1", 117 },
+                   { "db00", 66 },
+                   { "db01", 69 },
+                   { "db02", 45 },
+                   { "db03", 67 },
+                   { "db04", 68 },
+                   { "db05", 44 },
+                   { "db06", 26 },
+                   { "db07", 65 },*/
+                   {},
+               },
+           },
+           }
+       }
+   }, {
        .name = "er_tftm050_2",
        .spi = &(struct spi_board_info) {
            .modalias = "fb_ra8875",
notro commented 9 years ago

Committted, thanks: https://github.com/notro/fbtft/commit/de1c2adfbb7b04b90a02d0e4cf6d7d923bab3656