summaryrefslogtreecommitdiffstats
path: root/drivers/staging/fbtft
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/fbtft')
-rw-r--r--drivers/staging/fbtft/Kconfig202
-rw-r--r--drivers/staging/fbtft/Makefile38
-rw-r--r--drivers/staging/fbtft/README32
-rw-r--r--drivers/staging/fbtft/TODO3
-rw-r--r--drivers/staging/fbtft/fb_agm1264k-fl.c441
-rw-r--r--drivers/staging/fbtft/fb_bd663474.c171
-rw-r--r--drivers/staging/fbtft/fb_hx8340bn.c225
-rw-r--r--drivers/staging/fbtft/fb_hx8347d.c183
-rw-r--r--drivers/staging/fbtft/fb_hx8353d.c148
-rw-r--r--drivers/staging/fbtft/fb_hx8357d.c201
-rw-r--r--drivers/staging/fbtft/fb_hx8357d.h71
-rw-r--r--drivers/staging/fbtft/fb_ili9163.c260
-rw-r--r--drivers/staging/fbtft/fb_ili9320.c267
-rw-r--r--drivers/staging/fbtft/fb_ili9325.c261
-rw-r--r--drivers/staging/fbtft/fb_ili9340.c139
-rw-r--r--drivers/staging/fbtft/fb_ili9341.c157
-rw-r--r--drivers/staging/fbtft/fb_ili9481.c103
-rw-r--r--drivers/staging/fbtft/fb_ili9486.c103
-rw-r--r--drivers/staging/fbtft/fb_pcd8544.c167
-rw-r--r--drivers/staging/fbtft/fb_ra8875.c305
-rw-r--r--drivers/staging/fbtft/fb_s6d02a1.c166
-rw-r--r--drivers/staging/fbtft/fb_s6d1121.c182
-rw-r--r--drivers/staging/fbtft/fb_seps525.c213
-rw-r--r--drivers/staging/fbtft/fb_sh1106.c180
-rw-r--r--drivers/staging/fbtft/fb_ssd1289.c179
-rw-r--r--drivers/staging/fbtft/fb_ssd1305.c207
-rw-r--r--drivers/staging/fbtft/fb_ssd1306.c228
-rw-r--r--drivers/staging/fbtft/fb_ssd1325.c194
-rw-r--r--drivers/staging/fbtft/fb_ssd1331.c204
-rw-r--r--drivers/staging/fbtft/fb_ssd1351.c242
-rw-r--r--drivers/staging/fbtft/fb_st7735r.c186
-rw-r--r--drivers/staging/fbtft/fb_st7789v.c394
-rw-r--r--drivers/staging/fbtft/fb_tinylcd.c103
-rw-r--r--drivers/staging/fbtft/fb_tls8204.c160
-rw-r--r--drivers/staging/fbtft/fb_uc1611.c337
-rw-r--r--drivers/staging/fbtft/fb_uc1701.c170
-rw-r--r--drivers/staging/fbtft/fb_upd161704.c184
-rw-r--r--drivers/staging/fbtft/fbtft-bus.c243
-rw-r--r--drivers/staging/fbtft/fbtft-core.c1327
-rw-r--r--drivers/staging/fbtft/fbtft-io.c236
-rw-r--r--drivers/staging/fbtft/fbtft-sysfs.c220
-rw-r--r--drivers/staging/fbtft/fbtft.h444
-rw-r--r--drivers/staging/fbtft/internal.h13
43 files changed, 9489 insertions, 0 deletions
diff --git a/drivers/staging/fbtft/Kconfig b/drivers/staging/fbtft/Kconfig
new file mode 100644
index 000000000..4d29e8c10
--- /dev/null
+++ b/drivers/staging/fbtft/Kconfig
@@ -0,0 +1,202 @@
+# SPDX-License-Identifier: GPL-2.0
+menuconfig FB_TFT
+ tristate "Support for small TFT LCD display modules"
+ depends on FB && SPI
+ depends on GPIOLIB || COMPILE_TEST
+ select FB_SYS_FILLRECT
+ select FB_SYS_COPYAREA
+ select FB_SYS_IMAGEBLIT
+ select FB_SYS_FOPS
+ 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 compatible chips)
+
+config FB_TFT_BD663474
+ tristate "FB driver for the BD663474 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for BD663474
+
+config FB_TFT_HX8340BN
+ tristate "FB driver for the HX8340BN LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for HX8340BN
+
+config FB_TFT_HX8347D
+ tristate "FB driver for the HX8347D LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for HX8347D
+
+config FB_TFT_HX8353D
+ tristate "FB driver for the HX8353D LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for HX8353D
+
+config FB_TFT_HX8357D
+ tristate "FB driver for the HX8357D LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for HX8357D
+
+config FB_TFT_ILI9163
+ tristate "FB driver for the ILI9163 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for ILI9163
+
+config FB_TFT_ILI9320
+ tristate "FB driver for the ILI9320 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for ILI9320
+
+config FB_TFT_ILI9325
+ tristate "FB driver for the ILI9325 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for ILI9325
+
+config FB_TFT_ILI9340
+ tristate "FB driver for the ILI9340 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for ILI9340
+
+config FB_TFT_ILI9341
+ tristate "FB driver for the ILI9341 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for ILI9341
+
+config FB_TFT_ILI9481
+ tristate "FB driver for the ILI9481 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for ILI9481
+
+config FB_TFT_ILI9486
+ tristate "FB driver for the ILI9486 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for ILI9486
+
+config FB_TFT_PCD8544
+ tristate "FB driver for the PCD8544 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for PCD8544
+
+config FB_TFT_RA8875
+ tristate "FB driver for the RA8875 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for RA8875
+
+config FB_TFT_S6D02A1
+ tristate "FB driver for the S6D02A1 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for S6D02A1
+
+config FB_TFT_S6D1121
+ tristate "FB driver for the S6D1211 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for S6D1121
+
+config FB_TFT_SEPS525
+ tristate "FB driver for the SEPS525 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for SEPS525
+ Say Y if you have such a display that utilizes this controller.
+
+config FB_TFT_SH1106
+ tristate "FB driver for the SH1106 OLED Controller"
+ depends on FB_TFT
+ help
+ Framebuffer support for SH1106
+
+config FB_TFT_SSD1289
+ tristate "FB driver for the SSD1289 LCD Controller"
+ depends on FB_TFT
+ help
+ Framebuffer support for SSD1289
+
+config FB_TFT_SSD1305
+ tristate "FB driver for the SSD1305 OLED Controller"
+ depends on FB_TFT
+ help
+ Framebuffer support for SSD1305
+
+config FB_TFT_SSD1306
+ tristate "FB driver for the SSD1306 OLED Controller"
+ depends on FB_TFT
+ help
+ Framebuffer support for SSD1306
+
+config FB_TFT_SSD1331
+ tristate "FB driver for the SSD1331 LCD Controller"
+ depends on FB_TFT
+ help
+ Framebuffer support for SSD1331
+
+config FB_TFT_SSD1351
+ tristate "FB driver for the SSD1351 LCD Controller"
+ depends on FB_TFT
+ help
+ Framebuffer support for SSD1351
+
+config FB_TFT_ST7735R
+ tristate "FB driver for the ST7735R LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for ST7735R
+
+config FB_TFT_ST7789V
+ tristate "FB driver for the ST7789V LCD Controller"
+ depends on FB_TFT
+ help
+ This enables generic framebuffer support for the Sitronix ST7789V
+ display controller. The controller is intended for small color
+ displays with a resolution of up to 320x240 pixels.
+
+ Say Y if you have such a display that utilizes this controller.
+
+config FB_TFT_TINYLCD
+ tristate "FB driver for tinylcd.com display"
+ depends on FB_TFT
+ help
+ Custom Framebuffer support for tinylcd.com display
+
+config FB_TFT_TLS8204
+ tristate "FB driver for the TLS8204 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for TLS8204
+
+config FB_TFT_UC1611
+ tristate "FB driver for the UC1611 LCD controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for UC1611
+
+config FB_TFT_UC1701
+ tristate "FB driver for the UC1701 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for UC1701
+
+config FB_TFT_UPD161704
+ tristate "FB driver for the uPD161704 LCD Controller"
+ depends on FB_TFT
+ help
+ Generic Framebuffer support for uPD161704
diff --git a/drivers/staging/fbtft/Makefile b/drivers/staging/fbtft/Makefile
new file mode 100644
index 000000000..e9cdf0f0a
--- /dev/null
+++ b/drivers/staging/fbtft/Makefile
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0
+# Core module
+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
+obj-$(CONFIG_FB_TFT_HX8353D) += fb_hx8353d.o
+obj-$(CONFIG_FB_TFT_HX8357D) += fb_hx8357d.o
+obj-$(CONFIG_FB_TFT_ILI9163) += fb_ili9163.o
+obj-$(CONFIG_FB_TFT_ILI9320) += fb_ili9320.o
+obj-$(CONFIG_FB_TFT_ILI9325) += fb_ili9325.o
+obj-$(CONFIG_FB_TFT_ILI9340) += fb_ili9340.o
+obj-$(CONFIG_FB_TFT_ILI9341) += fb_ili9341.o
+obj-$(CONFIG_FB_TFT_ILI9481) += fb_ili9481.o
+obj-$(CONFIG_FB_TFT_ILI9486) += fb_ili9486.o
+obj-$(CONFIG_FB_TFT_PCD8544) += fb_pcd8544.o
+obj-$(CONFIG_FB_TFT_RA8875) += fb_ra8875.o
+obj-$(CONFIG_FB_TFT_S6D02A1) += fb_s6d02a1.o
+obj-$(CONFIG_FB_TFT_S6D1121) += fb_s6d1121.o
+obj-$(CONFIG_FB_TFT_SEPS525) += fb_seps525.o
+obj-$(CONFIG_FB_TFT_SH1106) += fb_sh1106.o
+obj-$(CONFIG_FB_TFT_SSD1289) += fb_ssd1289.o
+obj-$(CONFIG_FB_TFT_SSD1305) += fb_ssd1305.o
+obj-$(CONFIG_FB_TFT_SSD1306) += fb_ssd1306.o
+obj-$(CONFIG_FB_TFT_SSD1305) += fb_ssd1325.o
+obj-$(CONFIG_FB_TFT_SSD1331) += fb_ssd1331.o
+obj-$(CONFIG_FB_TFT_SSD1351) += fb_ssd1351.o
+obj-$(CONFIG_FB_TFT_ST7735R) += fb_st7735r.o
+obj-$(CONFIG_FB_TFT_ST7789V) += fb_st7789v.o
+obj-$(CONFIG_FB_TFT_TINYLCD) += fb_tinylcd.o
+obj-$(CONFIG_FB_TFT_TLS8204) += fb_tls8204.o
+obj-$(CONFIG_FB_TFT_UC1611) += fb_uc1611.o
+obj-$(CONFIG_FB_TFT_UC1701) += fb_uc1701.o
+obj-$(CONFIG_FB_TFT_UPD161704) += fb_upd161704.o
diff --git a/drivers/staging/fbtft/README b/drivers/staging/fbtft/README
new file mode 100644
index 000000000..ba4c74c92
--- /dev/null
+++ b/drivers/staging/fbtft/README
@@ -0,0 +1,32 @@
+ FBTFT
+=========
+
+Linux Framebuffer drivers for small TFT LCD display modules.
+The module 'fbtft' makes writing drivers for some of these displays very easy.
+
+Development is done on a Raspberry Pi running the Raspbian "wheezy" distribution.
+
+INSTALLATION
+ Download kernel sources
+
+ From Linux 3.15
+ cd drivers/video/fbdev/fbtft
+ git clone https://github.com/notro/fbtft.git
+
+ Add to drivers/video/fbdev/Kconfig: source "drivers/video/fbdev/fbtft/Kconfig"
+ Add to drivers/video/fbdev/Makefile: obj-y += fbtft/
+
+ Before Linux 3.15
+ cd drivers/video
+ git clone https://github.com/notro/fbtft.git
+
+ Add to drivers/video/Kconfig: source "drivers/video/fbtft/Kconfig"
+ Add to drivers/video/Makefile: obj-y += fbtft/
+
+ Enable driver(s) in menuconfig and build the kernel
+
+
+See wiki for more information: https://github.com/notro/fbtft/wiki
+
+
+Source: https://github.com/notro/fbtft/
diff --git a/drivers/staging/fbtft/TODO b/drivers/staging/fbtft/TODO
new file mode 100644
index 000000000..e72a08bf2
--- /dev/null
+++ b/drivers/staging/fbtft/TODO
@@ -0,0 +1,3 @@
+* convert all these over to drm_simple_display_pipe and submit for inclusion
+ into the DRM subsystem under drivers/gpu/drm - fbdev doesn't take any new
+ drivers anymore.
diff --git a/drivers/staging/fbtft/fb_agm1264k-fl.c b/drivers/staging/fbtft/fb_agm1264k-fl.c
new file mode 100644
index 000000000..207d57854
--- /dev/null
+++ b/drivers/staging/fbtft/fb_agm1264k-fl.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for Two KS0108 LCD controllers in AGM1264K-FL display
+ *
+ * Copyright (C) 2014 ololoshka2871
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.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;
+}
+
+/* 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) {
+ 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]) {
+ dev_err(par->info->device,
+ "Missing info about 'db[%i]' gpio. Aborting.\n",
+ i);
+ return -EINVAL;
+ }
+ }
+ if (!par->CS0) {
+ dev_err(par->info->device,
+ "Missing info about 'cs0' gpio. Aborting.\n");
+ return -EINVAL;
+ }
+ if (!par->CS1) {
+ dev_err(par->info->device,
+ "Missing info about 'cs1' gpio. Aborting.\n");
+ return -EINVAL;
+ }
+ if (!par->RW) {
+ 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 GPIOD_OUT_LOW;
+ } else if (strcasecmp(gpio->name, "cs0") == 0) {
+ /* left ks0108 controller pin */
+ par->CS0 = gpio->gpio;
+ return GPIOD_OUT_HIGH;
+ } else if (strcasecmp(gpio->name, "cs1") == 0) {
+ /* right ks0108 controller pin */
+ par->CS1 = gpio->gpio;
+ return GPIOD_OUT_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 GPIOD_OUT_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 */
+ gpiod_set_value(par->CS0, 0);
+ gpiod_set_value(par->CS1, 1);
+ } else {
+ /* cs0 */
+ gpiod_set_value(par->CS0, 1);
+ gpiod_set_value(par->CS1, 0);
+ }
+
+ gpiod_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, BIT(6) | (u8)addr_win.xs);
+ write_reg(par, 0x00, (0x17 << 3) | (u8)y);
+
+ /* write bitmap */
+ gpiod_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, BIT(6));
+ write_reg(par, 0x01, (0x17 << 3) | (u8)y);
+
+ /* write bitmap */
+ gpiod_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);
+
+ gpiod_set_value(par->CS0, 0);
+ gpiod_set_value(par->CS1, 0);
+
+ 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=%zu): ", __func__, len);
+
+ gpiod_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)
+ gpiod_set_value(par->gpio.db[i], data & (1 << i));
+ /* set E */
+ gpiod_set_value(par->EPIN, 0);
+ udelay(5);
+ /* unset E - write */
+ gpiod_set_value(par->EPIN, 1);
+ 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,
+ .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/drivers/staging/fbtft/fb_bd663474.c b/drivers/staging/fbtft/fb_bd663474.c
new file mode 100644
index 000000000..1629c2c44
--- /dev/null
+++ b/drivers/staging/fbtft/fb_bd663474.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the uPD161704 LCD Controller
+ *
+ * Copyright (C) 2014 Seong-Woo Kim
+ *
+ * Based on fb_ili9325.c by Noralf Tronnes
+ * Based on ili9325.c by Jeroen Domburg
+ * Init code from UTFT library by Henning Karlsen
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_bd663474"
+#define WIDTH 240
+#define HEIGHT 320
+#define BPP 16
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* Initialization sequence from Lib_UTFT */
+
+ /* oscillator start */
+ write_reg(par, 0x000, 0x0001); /*oscillator 0: stop, 1: operation */
+ mdelay(10);
+
+ /* Power settings */
+ write_reg(par, 0x100, 0x0000); /* power supply setup */
+ write_reg(par, 0x101, 0x0000);
+ write_reg(par, 0x102, 0x3110);
+ write_reg(par, 0x103, 0xe200);
+ write_reg(par, 0x110, 0x009d);
+ write_reg(par, 0x111, 0x0022);
+ write_reg(par, 0x100, 0x0120);
+ mdelay(20);
+
+ write_reg(par, 0x100, 0x3120);
+ mdelay(80);
+ /* Display control */
+ write_reg(par, 0x001, 0x0100);
+ write_reg(par, 0x002, 0x0000);
+ write_reg(par, 0x003, 0x1230);
+ write_reg(par, 0x006, 0x0000);
+ write_reg(par, 0x007, 0x0101);
+ write_reg(par, 0x008, 0x0808);
+ write_reg(par, 0x009, 0x0000);
+ write_reg(par, 0x00b, 0x0000);
+ write_reg(par, 0x00c, 0x0000);
+ write_reg(par, 0x00d, 0x0018);
+ /* LTPS control settings */
+ write_reg(par, 0x012, 0x0000);
+ write_reg(par, 0x013, 0x0000);
+ write_reg(par, 0x018, 0x0000);
+ write_reg(par, 0x019, 0x0000);
+
+ write_reg(par, 0x203, 0x0000);
+ write_reg(par, 0x204, 0x0000);
+
+ write_reg(par, 0x210, 0x0000);
+ write_reg(par, 0x211, 0x00ef);
+ write_reg(par, 0x212, 0x0000);
+ write_reg(par, 0x213, 0x013f);
+ write_reg(par, 0x214, 0x0000);
+ write_reg(par, 0x215, 0x0000);
+ write_reg(par, 0x216, 0x0000);
+ write_reg(par, 0x217, 0x0000);
+
+ /* Gray scale settings */
+ write_reg(par, 0x300, 0x5343);
+ write_reg(par, 0x301, 0x1021);
+ write_reg(par, 0x302, 0x0003);
+ write_reg(par, 0x303, 0x0011);
+ write_reg(par, 0x304, 0x050a);
+ write_reg(par, 0x305, 0x4342);
+ write_reg(par, 0x306, 0x1100);
+ write_reg(par, 0x307, 0x0003);
+ write_reg(par, 0x308, 0x1201);
+ write_reg(par, 0x309, 0x050a);
+
+ /* RAM access settings */
+ write_reg(par, 0x400, 0x4027);
+ write_reg(par, 0x401, 0x0000);
+ write_reg(par, 0x402, 0x0000); /* First screen drive position (1) */
+ write_reg(par, 0x403, 0x013f); /* First screen drive position (2) */
+ write_reg(par, 0x404, 0x0000);
+
+ write_reg(par, 0x200, 0x0000);
+ write_reg(par, 0x201, 0x0000);
+ write_reg(par, 0x100, 0x7120);
+ write_reg(par, 0x007, 0x0103);
+ mdelay(10);
+ write_reg(par, 0x007, 0x0113);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ switch (par->info->var.rotate) {
+ /* R200h = Horizontal GRAM Start Address */
+ /* R201h = Vertical GRAM Start Address */
+ case 0:
+ write_reg(par, 0x0200, xs);
+ write_reg(par, 0x0201, ys);
+ break;
+ case 180:
+ write_reg(par, 0x0200, WIDTH - 1 - xs);
+ write_reg(par, 0x0201, HEIGHT - 1 - ys);
+ break;
+ case 270:
+ write_reg(par, 0x0200, WIDTH - 1 - ys);
+ write_reg(par, 0x0201, xs);
+ break;
+ case 90:
+ write_reg(par, 0x0200, ys);
+ write_reg(par, 0x0201, HEIGHT - 1 - xs);
+ break;
+ }
+ write_reg(par, 0x202); /* Write Data to GRAM */
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ /* AM: GRAM update direction */
+ case 0:
+ write_reg(par, 0x003, 0x1230);
+ break;
+ case 180:
+ write_reg(par, 0x003, 0x1200);
+ break;
+ case 270:
+ write_reg(par, 0x003, 0x1228);
+ break;
+ case 90:
+ write_reg(par, 0x003, 0x1218);
+ break;
+ }
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 16,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .bpp = BPP,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "hitachi,bd663474", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:bd663474");
+MODULE_ALIAS("platform:bd663474");
+
+MODULE_DESCRIPTION("FB driver for the uPD161704 LCD Controller");
+MODULE_AUTHOR("Seong-Woo Kim");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_hx8340bn.c b/drivers/staging/fbtft/fb_hx8340bn.c
new file mode 100644
index 000000000..2fd7b87ea
--- /dev/null
+++ b/drivers/staging/fbtft/fb_hx8340bn.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the HX8340BN LCD Controller
+ *
+ * This display uses 9-bit SPI: Data/Command bit + 8 data bits
+ * For platforms that doesn't support 9-bit, the driver is capable
+ * of emulating this using 8-bit transfer.
+ * This is done by transferring eight 9-bit words in 9 bytes.
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_hx8340bn"
+#define WIDTH 176
+#define HEIGHT 220
+#define TXBUFLEN (4 * PAGE_SIZE)
+#define DEFAULT_GAMMA "1 3 0E 5 0 2 09 0 6 1 7 1 0 2 2\n" \
+ "3 3 17 8 4 7 05 7 6 0 3 1 6 0 0 "
+
+static bool emulate;
+module_param(emulate, bool, 0000);
+MODULE_PARM_DESC(emulate, "Force emulation in 9-bit mode");
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* BTL221722-276L startup sequence, from datasheet */
+
+ /*
+ * SETEXTCOM: Set extended command set (C1h)
+ * This command is used to set extended command set access enable.
+ * Enable: After command (C1h), must write: ffh,83h,40h
+ */
+ write_reg(par, 0xC1, 0xFF, 0x83, 0x40);
+
+ /*
+ * Sleep out
+ * This command turns off sleep mode.
+ * In this mode the DC/DC converter is enabled, Internal oscillator
+ * is started, and panel scanning is started.
+ */
+ write_reg(par, 0x11);
+ mdelay(150);
+
+ /* Undoc'd register? */
+ write_reg(par, 0xCA, 0x70, 0x00, 0xD9);
+
+ /*
+ * SETOSC: Set Internal Oscillator (B0h)
+ * This command is used to set internal oscillator related settings
+ * OSC_EN: Enable internal oscillator
+ * Internal oscillator frequency: 125% x 2.52MHz
+ */
+ write_reg(par, 0xB0, 0x01, 0x11);
+
+ /* Drive ability setting */
+ write_reg(par, 0xC9, 0x90, 0x49, 0x10, 0x28, 0x28, 0x10, 0x00, 0x06);
+ mdelay(20);
+
+ /*
+ * SETPWCTR5: Set Power Control 5(B5h)
+ * This command is used to set VCOM Low and VCOM High Voltage
+ * VCOMH 0110101 : 3.925
+ * VCOML 0100000 : -1.700
+ * 45h=69 VCOMH: "VMH" + 5d VCOML: "VMH" + 5d
+ */
+ write_reg(par, 0xB5, 0x35, 0x20, 0x45);
+
+ /*
+ * SETPWCTR4: Set Power Control 4(B4h)
+ * VRH[4:0]: Specify the VREG1 voltage adjusting.
+ * VREG1 voltage is for gamma voltage setting.
+ * BT[2:0]: Switch the output factor of step-up circuit 2
+ * for VGH and VGL voltage generation.
+ */
+ write_reg(par, 0xB4, 0x33, 0x25, 0x4C);
+ mdelay(10);
+
+ /*
+ * Interface Pixel Format (3Ah)
+ * This command is used to define the format of RGB picture data,
+ * which is to be transfer via the system and RGB interface.
+ * RGB interface: 16 Bit/Pixel
+ */
+ write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT);
+
+ /*
+ * Display on (29h)
+ * This command is used to recover from DISPLAY OFF mode.
+ * Output from the Frame Memory is enabled.
+ */
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
+ mdelay(10);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, xs, 0x00, xe);
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, ys, 0x00, ye);
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ /* MADCTL - Memory data access control */
+ /* RGB/BGR can be set with H/W pin SRGB and MADCTL BGR bit */
+#define MY BIT(7)
+#define MX BIT(6)
+#define MV BIT(5)
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, par->bgr << 3);
+ break;
+ case 270:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MX | MV | (par->bgr << 3));
+ break;
+ case 180:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MX | MY | (par->bgr << 3));
+ break;
+ case 90:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MY | MV | (par->bgr << 3));
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Gamma Curve selection, GC (only GC0 can be customized):
+ * 0 = 2.2, 1 = 1.8, 2 = 2.5, 3 = 1.0
+ * Gamma string format:
+ * OP0 OP1 CP0 CP1 CP2 CP3 CP4 MP0 MP1 MP2 MP3 MP4 MP5 CGM0 CGM1
+ * ON0 ON1 CN0 CN1 CN2 CN3 CN4 MN0 MN1 MN2 MN3 MN4 MN5 XXXX GC
+ */
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ static const unsigned long mask[] = {
+ 0x0f, 0x0f, 0x1f, 0x0f, 0x0f, 0x0f, 0x1f, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x03, 0x03, 0x0f, 0x0f, 0x1f, 0x0f, 0x0f,
+ 0x0f, 0x1f, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x00, 0x00,
+ };
+ int i, j;
+
+ /* apply mask */
+ for (i = 0; i < par->gamma.num_curves; i++)
+ for (j = 0; j < par->gamma.num_values; j++)
+ CURVE(i, j) &= mask[i * par->gamma.num_values + j];
+
+ /* Gamma Set (26h) */
+ write_reg(par, MIPI_DCS_SET_GAMMA_CURVE, 1 << CURVE(1, 14));
+
+ if (CURVE(1, 14))
+ return 0; /* only GC0 can be customized */
+
+ write_reg(par, 0xC2,
+ (CURVE(0, 8) << 4) | CURVE(0, 7),
+ (CURVE(0, 10) << 4) | CURVE(0, 9),
+ (CURVE(0, 12) << 4) | CURVE(0, 11),
+ CURVE(0, 2),
+ (CURVE(0, 4) << 4) | CURVE(0, 3),
+ CURVE(0, 5),
+ CURVE(0, 6),
+ (CURVE(0, 1) << 4) | CURVE(0, 0),
+ (CURVE(0, 14) << 2) | CURVE(0, 13));
+
+ write_reg(par, 0xC3,
+ (CURVE(1, 8) << 4) | CURVE(1, 7),
+ (CURVE(1, 10) << 4) | CURVE(1, 9),
+ (CURVE(1, 12) << 4) | CURVE(1, 11),
+ CURVE(1, 2),
+ (CURVE(1, 4) << 4) | CURVE(1, 3),
+ CURVE(1, 5),
+ CURVE(1, 6),
+ (CURVE(1, 1) << 4) | CURVE(1, 0));
+
+ mdelay(10);
+
+ return 0;
+}
+
+#undef CURVE
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .txbuflen = TXBUFLEN,
+ .gamma_num = 2,
+ .gamma_len = 15,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "himax,hx8340bn", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:hx8340bn");
+MODULE_ALIAS("platform:hx8340bn");
+
+MODULE_DESCRIPTION("FB driver for the HX8340BN LCD Controller");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_hx8347d.c b/drivers/staging/fbtft/fb_hx8347d.c
new file mode 100644
index 000000000..a9b72a8b4
--- /dev/null
+++ b/drivers/staging/fbtft/fb_hx8347d.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the HX8347D LCD Controller
+ *
+ * Copyright (C) 2013 Christian Vogelgsang
+ *
+ * Based on driver code found here: https://github.com/watterott/r61505u-Adapter
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_hx8347d"
+#define WIDTH 320
+#define HEIGHT 240
+#define DEFAULT_GAMMA "0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" \
+ "0 0 0 0 0 0 0 0 0 0 0 0 0 0"
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* driving ability */
+ write_reg(par, 0xEA, 0x00);
+ write_reg(par, 0xEB, 0x20);
+ write_reg(par, 0xEC, 0x0C);
+ write_reg(par, 0xED, 0xC4);
+ write_reg(par, 0xE8, 0x40);
+ write_reg(par, 0xE9, 0x38);
+ write_reg(par, 0xF1, 0x01);
+ write_reg(par, 0xF2, 0x10);
+ write_reg(par, 0x27, 0xA3);
+
+ /* power voltage */
+ write_reg(par, 0x1B, 0x1B);
+ write_reg(par, 0x1A, 0x01);
+ write_reg(par, 0x24, 0x2F);
+ write_reg(par, 0x25, 0x57);
+
+ /* VCOM offset */
+ write_reg(par, 0x23, 0x8D); /* for flicker adjust */
+
+ /* power on */
+ write_reg(par, 0x18, 0x36);
+ write_reg(par, 0x19, 0x01); /* start osc */
+ write_reg(par, 0x01, 0x00); /* wakeup */
+ write_reg(par, 0x1F, 0x88);
+ mdelay(5);
+ write_reg(par, 0x1F, 0x80);
+ mdelay(5);
+ write_reg(par, 0x1F, 0x90);
+ mdelay(5);
+ write_reg(par, 0x1F, 0xD0);
+ mdelay(5);
+
+ /* color selection */
+ write_reg(par, 0x17, 0x05); /* 65k */
+
+ /*panel characteristic */
+ write_reg(par, 0x36, 0x00);
+
+ /*display on */
+ write_reg(par, 0x28, 0x38);
+ mdelay(40);
+ write_reg(par, 0x28, 0x3C);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, 0x02, (xs >> 8) & 0xFF);
+ write_reg(par, 0x03, xs & 0xFF);
+ write_reg(par, 0x04, (xe >> 8) & 0xFF);
+ write_reg(par, 0x05, xe & 0xFF);
+ write_reg(par, 0x06, (ys >> 8) & 0xFF);
+ write_reg(par, 0x07, ys & 0xFF);
+ write_reg(par, 0x08, (ye >> 8) & 0xFF);
+ write_reg(par, 0x09, ye & 0xFF);
+ write_reg(par, 0x22);
+}
+
+#define MEM_Y BIT(7) /* MY row address order */
+#define MEM_X BIT(6) /* MX column address order */
+#define MEM_V BIT(5) /* MV row / column exchange */
+#define MEM_L BIT(4) /* ML vertical refresh order */
+#define MEM_BGR (3) /* RGB-BGR Order */
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, 0x16, MEM_V | MEM_X | (par->bgr << MEM_BGR));
+ break;
+ case 270:
+ write_reg(par, 0x16, par->bgr << MEM_BGR);
+ break;
+ case 180:
+ write_reg(par, 0x16, MEM_V | MEM_Y | (par->bgr << MEM_BGR));
+ break;
+ case 90:
+ write_reg(par, 0x16, MEM_X | MEM_Y | (par->bgr << MEM_BGR));
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Gamma string format:
+ * VRP0 VRP1 VRP2 VRP3 VRP4 VRP5 PRP0 PRP1 PKP0 PKP1 PKP2 PKP3 PKP4 CGM
+ * VRN0 VRN1 VRN2 VRN3 VRN4 VRN5 PRN0 PRN1 PKN0 PKN1 PKN2 PKN3 PKN4 CGM
+ */
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ static const unsigned long mask[] = {
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x0f,
+ };
+ int i, j;
+ int acc = 0;
+
+ /* apply mask */
+ for (i = 0; i < par->gamma.num_curves; i++)
+ for (j = 0; j < par->gamma.num_values; j++) {
+ acc += CURVE(i, j);
+ CURVE(i, j) &= mask[j];
+ }
+
+ if (acc == 0) /* skip if all values are zero */
+ return 0;
+
+ for (i = 0; i < par->gamma.num_curves; i++) {
+ write_reg(par, 0x40 + (i * 0x10), CURVE(i, 0));
+ write_reg(par, 0x41 + (i * 0x10), CURVE(i, 1));
+ write_reg(par, 0x42 + (i * 0x10), CURVE(i, 2));
+ write_reg(par, 0x43 + (i * 0x10), CURVE(i, 3));
+ write_reg(par, 0x44 + (i * 0x10), CURVE(i, 4));
+ write_reg(par, 0x45 + (i * 0x10), CURVE(i, 5));
+ write_reg(par, 0x46 + (i * 0x10), CURVE(i, 6));
+ write_reg(par, 0x47 + (i * 0x10), CURVE(i, 7));
+ write_reg(par, 0x48 + (i * 0x10), CURVE(i, 8));
+ write_reg(par, 0x49 + (i * 0x10), CURVE(i, 9));
+ write_reg(par, 0x4A + (i * 0x10), CURVE(i, 10));
+ write_reg(par, 0x4B + (i * 0x10), CURVE(i, 11));
+ write_reg(par, 0x4C + (i * 0x10), CURVE(i, 12));
+ }
+ write_reg(par, 0x5D, (CURVE(1, 0) << 4) | CURVE(0, 0));
+
+ return 0;
+}
+
+#undef CURVE
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .gamma_num = 2,
+ .gamma_len = 14,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "himax,hx8347d", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:hx8347d");
+MODULE_ALIAS("platform:hx8347d");
+
+MODULE_DESCRIPTION("FB driver for the HX8347D LCD Controller");
+MODULE_AUTHOR("Christian Vogelgsang");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_hx8353d.c b/drivers/staging/fbtft/fb_hx8353d.c
new file mode 100644
index 000000000..3e73b69b6
--- /dev/null
+++ b/drivers/staging/fbtft/fb_hx8353d.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the HX8353D LCD Controller
+ *
+ * Copyright (c) 2014 Petr Olivka
+ * Copyright (c) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_hx8353d"
+#define DEFAULT_GAMMA "50 77 40 08 BF 00 03 0F 00 01 73 00 72 03 B0 0F 08 00 0F"
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+ mdelay(150);
+
+ /* SETEXTC */
+ write_reg(par, 0xB9, 0xFF, 0x83, 0x53);
+
+ /* RADJ */
+ write_reg(par, 0xB0, 0x3C, 0x01);
+
+ /* VCOM */
+ write_reg(par, 0xB6, 0x94, 0x6C, 0x50);
+
+ /* PWR */
+ write_reg(par, 0xB1, 0x00, 0x01, 0x1B, 0x03, 0x01, 0x08, 0x77, 0x89);
+
+ /* COLMOD */
+ write_reg(par, 0x3A, 0x05);
+
+ /* MEM ACCESS */
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0xC0);
+
+ /* SLPOUT - Sleep out & booster on */
+ write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
+ mdelay(150);
+
+ /* DISPON - Display On */
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
+
+ /* RGBSET */
+ write_reg(par, MIPI_DCS_WRITE_LUT,
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
+ 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
+ 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62);
+
+ return 0;
+};
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ /* column address */
+ write_reg(par, 0x2a, xs >> 8, xs & 0xff, xe >> 8, xe & 0xff);
+
+ /* Row address */
+ write_reg(par, 0x2b, ys >> 8, ys & 0xff, ye >> 8, ye & 0xff);
+
+ /* memory write */
+ write_reg(par, 0x2c);
+}
+
+#define my BIT(7)
+#define mx BIT(6)
+#define mv BIT(5)
+static int set_var(struct fbtft_par *par)
+{
+ /*
+ * madctl - memory data access control
+ * rgb/bgr:
+ * 1. mode selection pin srgb
+ * rgb h/w pin for color filter setting: 0=rgb, 1=bgr
+ * 2. madctl rgb bit
+ * rgb-bgr order color filter panel: 0=rgb, 1=bgr
+ */
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ mx | my | (par->bgr << 3));
+ break;
+ case 270:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ my | mv | (par->bgr << 3));
+ break;
+ case 180:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ par->bgr << 3);
+ break;
+ case 90:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ mx | mv | (par->bgr << 3));
+ break;
+ }
+
+ return 0;
+}
+
+/* gamma string format: */
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ write_reg(par, 0xE0,
+ curves[0], curves[1], curves[2], curves[3],
+ curves[4], curves[5], curves[6], curves[7],
+ curves[8], curves[9], curves[10], curves[11],
+ curves[12], curves[13], curves[14], curves[15],
+ curves[16], curves[17], curves[18]);
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = 128,
+ .height = 160,
+ .gamma_num = 1,
+ .gamma_len = 19,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "himax,hx8353d", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:hx8353d");
+MODULE_ALIAS("platform:hx8353d");
+
+MODULE_DESCRIPTION("FB driver for the HX8353D LCD Controller");
+MODULE_AUTHOR("Petr Olivka");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_hx8357d.c b/drivers/staging/fbtft/fb_hx8357d.c
new file mode 100644
index 000000000..94a357e8f
--- /dev/null
+++ b/drivers/staging/fbtft/fb_hx8357d.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the HX8357D LCD Controller
+ * Copyright (C) 2015 Adafruit Industries
+ *
+ * Based on the HX8347D FB driver
+ * Copyright (C) 2013 Christian Vogelgsang
+ *
+ * Based on driver code found here: https://github.com/watterott/r61505u-Adapter
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+#include "fb_hx8357d.h"
+
+#define DRVNAME "fb_hx8357d"
+#define WIDTH 320
+#define HEIGHT 480
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* Reset things like Gamma */
+ write_reg(par, MIPI_DCS_SOFT_RESET);
+ usleep_range(5000, 7000);
+
+ /* setextc */
+ write_reg(par, HX8357D_SETC, 0xFF, 0x83, 0x57);
+ msleep(150);
+
+ /* setRGB which also enables SDO */
+ write_reg(par, HX8357_SETRGB, 0x00, 0x00, 0x06, 0x06);
+
+ /* -1.52V */
+ write_reg(par, HX8357D_SETCOM, 0x25);
+
+ /* Normal mode 70Hz, Idle mode 55 Hz */
+ write_reg(par, HX8357_SETOSC, 0x68);
+
+ /* Set Panel - BGR, Gate direction swapped */
+ write_reg(par, HX8357_SETPANEL, 0x05);
+
+ write_reg(par, HX8357_SETPWR1,
+ 0x00, /* Not deep standby */
+ 0x15, /* BT */
+ 0x1C, /* VSPR */
+ 0x1C, /* VSNR */
+ 0x83, /* AP */
+ 0xAA); /* FS */
+
+ write_reg(par, HX8357D_SETSTBA,
+ 0x50, /* OPON normal */
+ 0x50, /* OPON idle */
+ 0x01, /* STBA */
+ 0x3C, /* STBA */
+ 0x1E, /* STBA */
+ 0x08); /* GEN */
+
+ write_reg(par, HX8357D_SETCYC,
+ 0x02, /* NW 0x02 */
+ 0x40, /* RTN */
+ 0x00, /* DIV */
+ 0x2A, /* DUM */
+ 0x2A, /* DUM */
+ 0x0D, /* GDON */
+ 0x78); /* GDOFF */
+
+ write_reg(par, HX8357D_SETGAMMA,
+ 0x02,
+ 0x0A,
+ 0x11,
+ 0x1d,
+ 0x23,
+ 0x35,
+ 0x41,
+ 0x4b,
+ 0x4b,
+ 0x42,
+ 0x3A,
+ 0x27,
+ 0x1B,
+ 0x08,
+ 0x09,
+ 0x03,
+ 0x02,
+ 0x0A,
+ 0x11,
+ 0x1d,
+ 0x23,
+ 0x35,
+ 0x41,
+ 0x4b,
+ 0x4b,
+ 0x42,
+ 0x3A,
+ 0x27,
+ 0x1B,
+ 0x08,
+ 0x09,
+ 0x03,
+ 0x00,
+ 0x01);
+
+ /* 16 bit */
+ write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
+
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0xC0);
+
+ /* TE off */
+ write_reg(par, MIPI_DCS_SET_TEAR_ON, 0x00);
+
+ /* tear line */
+ write_reg(par, MIPI_DCS_SET_TEAR_SCANLINE, 0x00, 0x02);
+
+ /* Exit Sleep */
+ write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
+ msleep(150);
+
+ /* display on */
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
+ usleep_range(5000, 7000);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xff, /* XSTART */
+ xe >> 8, xe & 0xff); /* XEND */
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xff, /* YSTART */
+ ye >> 8, ye & 0xff); /* YEND */
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+#define HX8357D_MADCTL_MY 0x80
+#define HX8357D_MADCTL_MX 0x40
+#define HX8357D_MADCTL_MV 0x20
+#define HX8357D_MADCTL_ML 0x10
+#define HX8357D_MADCTL_RGB 0x00
+#define HX8357D_MADCTL_BGR 0x08
+#define HX8357D_MADCTL_MH 0x04
+static int set_var(struct fbtft_par *par)
+{
+ u8 val;
+
+ switch (par->info->var.rotate) {
+ case 270:
+ val = HX8357D_MADCTL_MV | HX8357D_MADCTL_MX;
+ break;
+ case 180:
+ val = 0;
+ break;
+ case 90:
+ val = HX8357D_MADCTL_MV | HX8357D_MADCTL_MY;
+ break;
+ default:
+ val = HX8357D_MADCTL_MX | HX8357D_MADCTL_MY;
+ break;
+ }
+
+ val |= (par->bgr ? HX8357D_MADCTL_RGB : HX8357D_MADCTL_BGR);
+
+ /* Memory Access Control */
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, val);
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .gamma_num = 2,
+ .gamma_len = 14,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "himax,hx8357d", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:hx8357d");
+MODULE_ALIAS("platform:hx8357d");
+
+MODULE_DESCRIPTION("FB driver for the HX8357D LCD Controller");
+MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_hx8357d.h b/drivers/staging/fbtft/fb_hx8357d.h
new file mode 100644
index 000000000..6180b093f
--- /dev/null
+++ b/drivers/staging/fbtft/fb_hx8357d.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * This is our library for the Adafruit ILI9341 Breakout and Shield
+ * ----> http://www.adafruit.com/products/1651
+ *
+ * Check out the links above for our tutorials and wiring diagrams
+ * These displays use SPI to communicate, 4 or 5 pins are required to
+ * interface (RST is optional)
+ * Adafruit invests time and resources providing this open source code,
+ * please support Adafruit and open-source hardware by purchasing
+ * products from Adafruit!
+ *
+ * Written by Limor Fried/Ladyada for Adafruit Industries.
+ * MIT license, all text above must be included in any redistribution
+ */
+
+#ifndef __HX8357_H__
+#define __HX8357_H__
+
+#define HX8357D 0xD
+#define HX8357B 0xB
+
+#define HX8357_TFTWIDTH 320
+#define HX8357_TFTHEIGHT 480
+
+#define HX8357_SETOSC 0xB0
+#define HX8357_SETPWR1 0xB1
+#define HX8357B_SETDISPLAY 0xB2
+#define HX8357_SETRGB 0xB3
+#define HX8357D_SETCOM 0xB6
+
+#define HX8357B_SETDISPMODE 0xB4
+#define HX8357D_SETCYC 0xB4
+#define HX8357B_SETOTP 0xB7
+#define HX8357D_SETC 0xB9
+
+#define HX8357B_SET_PANEL_DRIVING 0xC0
+#define HX8357D_SETSTBA 0xC0
+#define HX8357B_SETDGC 0xC1
+#define HX8357B_SETID 0xC3
+#define HX8357B_SETDDB 0xC4
+#define HX8357B_SETDISPLAYFRAME 0xC5
+#define HX8357B_GAMMASET 0xC8
+#define HX8357B_SETCABC 0xC9
+#define HX8357_SETPANEL 0xCC
+
+#define HX8357B_SETPOWER 0xD0
+#define HX8357B_SETVCOM 0xD1
+#define HX8357B_SETPWRNORMAL 0xD2
+
+#define HX8357B_RDID1 0xDA
+#define HX8357B_RDID2 0xDB
+#define HX8357B_RDID3 0xDC
+#define HX8357B_RDID4 0xDD
+
+#define HX8357D_SETGAMMA 0xE0
+
+#define HX8357B_SETGAMMA 0xC8
+#define HX8357B_SETPANELRELATED 0xE9
+
+/* Color definitions */
+#define HX8357_BLACK 0x0000
+#define HX8357_BLUE 0x001F
+#define HX8357_RED 0xF800
+#define HX8357_GREEN 0x07E0
+#define HX8357_CYAN 0x07FF
+#define HX8357_MAGENTA 0xF81F
+#define HX8357_YELLOW 0xFFE0
+#define HX8357_WHITE 0xFFFF
+
+#endif /* __HX8357_H__ */
diff --git a/drivers/staging/fbtft/fb_ili9163.c b/drivers/staging/fbtft/fb_ili9163.c
new file mode 100644
index 000000000..6582a2c90
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ili9163.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ILI9163 LCD Controller
+ *
+ * Copyright (C) 2015 Kozhevnikov Anatoly
+ *
+ * Based on ili9325.c by Noralf Tronnes and
+ * .S.U.M.O.T.O.Y. by Max MC Costa (https://github.com/sumotoy/TFT_ILI9163C).
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ili9163"
+#define WIDTH 128
+#define HEIGHT 128
+#define BPP 16
+#define FPS 30
+
+#ifdef GAMMA_ADJ
+#define GAMMA_LEN 15
+#define GAMMA_NUM 1
+#define DEFAULT_GAMMA "36 29 12 22 1C 15 42 B7 2F 13 12 0A 11 0B 06\n"
+#endif
+
+/* ILI9163C commands */
+#define CMD_FRMCTR1 0xB1 /* Frame Rate Control */
+ /* (In normal mode/Full colors) */
+#define CMD_FRMCTR2 0xB2 /* Frame Rate Control (In Idle mode/8-colors) */
+#define CMD_FRMCTR3 0xB3 /* Frame Rate Control */
+ /* (In Partial mode/full colors) */
+#define CMD_DINVCTR 0xB4 /* Display Inversion Control */
+#define CMD_RGBBLK 0xB5 /* RGB Interface Blanking Porch setting */
+#define CMD_DFUNCTR 0xB6 /* Display Function set 5 */
+#define CMD_SDRVDIR 0xB7 /* Source Driver Direction Control */
+#define CMD_GDRVDIR 0xB8 /* Gate Driver Direction Control */
+
+#define CMD_PWCTR1 0xC0 /* Power_Control1 */
+#define CMD_PWCTR2 0xC1 /* Power_Control2 */
+#define CMD_PWCTR3 0xC2 /* Power_Control3 */
+#define CMD_PWCTR4 0xC3 /* Power_Control4 */
+#define CMD_PWCTR5 0xC4 /* Power_Control5 */
+#define CMD_VCOMCTR1 0xC5 /* VCOM_Control 1 */
+#define CMD_VCOMCTR2 0xC6 /* VCOM_Control 2 */
+#define CMD_VCOMOFFS 0xC7 /* VCOM Offset Control */
+#define CMD_PGAMMAC 0xE0 /* Positive Gamma Correction Setting */
+#define CMD_NGAMMAC 0xE1 /* Negative Gamma Correction Setting */
+#define CMD_GAMRSEL 0xF2 /* GAM_R_SEL */
+
+/*
+ * This display:
+ * http://www.ebay.com/itm/Replace-Nokia-5110-LCD-1-44-Red-Serial-128X128-SPI-
+ * Color-TFT-LCD-Display-Module-/271422122271
+ * This particular display has a design error! The controller has 3 pins to
+ * configure to constrain the memory and resolution to a fixed dimension (in
+ * that case 128x128) but they leaved those pins configured for 128x160 so
+ * there was several pixel memory addressing problems.
+ * I solved by setup several parameters that dinamically fix the resolution as
+ * needit so below the parameters for this display. If you have a strain or a
+ * correct display (can happen with chinese) you can copy those parameters and
+ * create setup for different displays.
+ */
+
+#ifdef RED
+#define __OFFSET 32 /*see note 2 - this is the red version */
+#else
+#define __OFFSET 0 /*see note 2 - this is the black version */
+#endif
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ write_reg(par, MIPI_DCS_SOFT_RESET); /* software reset */
+ mdelay(500);
+ write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE); /* exit sleep */
+ mdelay(5);
+ write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT);
+ /* default gamma curve 3 */
+ write_reg(par, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
+#ifdef GAMMA_ADJ
+ write_reg(par, CMD_GAMRSEL, 0x01); /* Enable Gamma adj */
+#endif
+ write_reg(par, MIPI_DCS_ENTER_NORMAL_MODE);
+ write_reg(par, CMD_DFUNCTR, 0xff, 0x06);
+ /* Frame Rate Control (In normal mode/Full colors) */
+ write_reg(par, CMD_FRMCTR1, 0x08, 0x02);
+ write_reg(par, CMD_DINVCTR, 0x07); /* display inversion */
+ /* Set VRH1[4:0] & VC[2:0] for VCI1 & GVDD */
+ write_reg(par, CMD_PWCTR1, 0x0A, 0x02);
+ /* Set BT[2:0] for AVDD & VCL & VGH & VGL */
+ write_reg(par, CMD_PWCTR2, 0x02);
+ /* Set VMH[6:0] & VML[6:0] for VOMH & VCOML */
+ write_reg(par, CMD_VCOMCTR1, 0x50, 0x63);
+ write_reg(par, CMD_VCOMOFFS, 0);
+
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS, 0, 0, 0, WIDTH);
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS, 0, 0, 0, HEIGHT);
+
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON); /* display ON */
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START); /* Memory Write */
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys,
+ int xe, int ye)
+{
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xff, xe >> 8, xe & 0xff);
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ (ys + __OFFSET) >> 8, (ys + __OFFSET) & 0xff,
+ (ye + __OFFSET) >> 8, (ye + __OFFSET) & 0xff);
+ break;
+ case 90:
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ (xs + __OFFSET) >> 8, (xs + __OFFSET) & 0xff,
+ (xe + __OFFSET) >> 8, (xe + __OFFSET) & 0xff);
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xff, ye >> 8, ye & 0xff);
+ break;
+ case 180:
+ case 270:
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xff, xe >> 8, xe & 0xff);
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xff, ye >> 8, ye & 0xff);
+ break;
+ default:
+ /* Fix incorrect setting */
+ par->info->var.rotate = 0;
+ }
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+/*
+ * 7) MY: 1(bottom to top), 0(top to bottom) Row Address Order
+ * 6) MX: 1(R to L), 0(L to R) Column Address Order
+ * 5) MV: 1(Exchanged), 0(normal) Row/Column exchange
+ * 4) ML: 1(bottom to top), 0(top to bottom) Vertical Refresh Order
+ * 3) RGB: 1(BGR), 0(RGB) Color Space
+ * 2) MH: 1(R to L), 0(L to R) Horizontal Refresh Order
+ * 1)
+ * 0)
+ *
+ * MY, MX, MV, ML,RGB, MH, D1, D0
+ * 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 //normal
+ * 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 //Y-Mirror
+ * 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 //X-Mirror
+ * 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 //X-Y-Mirror
+ * 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 //X-Y Exchange
+ * 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 //X-Y Exchange, Y-Mirror
+ * 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 //XY exchange
+ * 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0
+ */
+static int set_var(struct fbtft_par *par)
+{
+ u8 mactrl_data = 0; /* Avoid compiler warning */
+
+ switch (par->info->var.rotate) {
+ case 0:
+ mactrl_data = 0x08;
+ break;
+ case 180:
+ mactrl_data = 0xC8;
+ break;
+ case 270:
+ mactrl_data = 0xA8;
+ break;
+ case 90:
+ mactrl_data = 0x68;
+ break;
+ }
+
+ /* Colorspcae */
+ if (par->bgr)
+ mactrl_data |= BIT(2);
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, mactrl_data);
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+ return 0;
+}
+
+#ifdef GAMMA_ADJ
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int gamma_adj(struct fbtft_par *par, u32 *curves)
+{
+ static const unsigned long mask[] = {
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x1f, 0x3f, 0x0f, 0x0f, 0x7f, 0x1f,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F};
+ int i, j;
+
+ for (i = 0; i < GAMMA_NUM; i++)
+ for (j = 0; j < GAMMA_LEN; j++)
+ CURVE(i, j) &= mask[i * par->gamma.num_values + j];
+
+ write_reg(par, CMD_PGAMMAC,
+ CURVE(0, 0),
+ CURVE(0, 1),
+ CURVE(0, 2),
+ CURVE(0, 3),
+ CURVE(0, 4),
+ CURVE(0, 5),
+ CURVE(0, 6),
+ (CURVE(0, 7) << 4) | CURVE(0, 8),
+ CURVE(0, 9),
+ CURVE(0, 10),
+ CURVE(0, 11),
+ CURVE(0, 12),
+ CURVE(0, 13),
+ CURVE(0, 14),
+ CURVE(0, 15));
+
+ /* Write Data to GRAM mode */
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+
+ return 0;
+}
+
+#undef CURVE
+#endif
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .bpp = BPP,
+ .fps = FPS,
+#ifdef GAMMA_ADJ
+ .gamma_num = GAMMA_NUM,
+ .gamma_len = GAMMA_LEN,
+ .gamma = DEFAULT_GAMMA,
+#endif
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+#ifdef GAMMA_ADJ
+ .set_gamma = gamma_adj,
+#endif
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9163", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ili9163");
+MODULE_ALIAS("platform:ili9163");
+
+MODULE_DESCRIPTION("FB driver for the ILI9163 LCD Controller");
+MODULE_AUTHOR("Kozhevnikov Anatoly");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ili9320.c b/drivers/staging/fbtft/fb_ili9320.c
new file mode 100644
index 000000000..0be7c2d51
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ili9320.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ILI9320 LCD Controller
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ili9320"
+#define WIDTH 240
+#define HEIGHT 320
+#define DEFAULT_GAMMA "07 07 6 0 0 0 5 5 4 0\n" \
+ "07 08 4 7 5 1 2 0 7 7"
+
+static unsigned int read_devicecode(struct fbtft_par *par)
+{
+ u8 rxbuf[8] = {0, };
+
+ write_reg(par, 0x0000);
+ par->fbtftops.read(par, rxbuf, 4);
+ return (rxbuf[2] << 8) | rxbuf[3];
+}
+
+static int init_display(struct fbtft_par *par)
+{
+ unsigned int devcode;
+
+ par->fbtftops.reset(par);
+
+ devcode = read_devicecode(par);
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "Device code: 0x%04X\n",
+ devcode);
+ if ((devcode != 0x0000) && (devcode != 0x9320))
+ dev_warn(par->info->device,
+ "Unrecognized Device code: 0x%04X (expected 0x9320)\n",
+ devcode);
+
+ /* Initialization sequence from ILI9320 Application Notes */
+
+ /* *********** Start Initial Sequence ********* */
+ /* Set the Vcore voltage and this setting is must. */
+ write_reg(par, 0x00E5, 0x8000);
+
+ /* Start internal OSC. */
+ write_reg(par, 0x0000, 0x0001);
+
+ /* set SS and SM bit */
+ write_reg(par, 0x0001, 0x0100);
+
+ /* set 1 line inversion */
+ write_reg(par, 0x0002, 0x0700);
+
+ /* Resize register */
+ write_reg(par, 0x0004, 0x0000);
+
+ /* set the back and front porch */
+ write_reg(par, 0x0008, 0x0202);
+
+ /* set non-display area refresh cycle */
+ write_reg(par, 0x0009, 0x0000);
+
+ /* FMARK function */
+ write_reg(par, 0x000A, 0x0000);
+
+ /* RGB interface setting */
+ write_reg(par, 0x000C, 0x0000);
+
+ /* Frame marker Position */
+ write_reg(par, 0x000D, 0x0000);
+
+ /* RGB interface polarity */
+ write_reg(par, 0x000F, 0x0000);
+
+ /* ***********Power On sequence *************** */
+ /* SAP, BT[3:0], AP, DSTB, SLP, STB */
+ write_reg(par, 0x0010, 0x0000);
+
+ /* DC1[2:0], DC0[2:0], VC[2:0] */
+ write_reg(par, 0x0011, 0x0007);
+
+ /* VREG1OUT voltage */
+ write_reg(par, 0x0012, 0x0000);
+
+ /* VDV[4:0] for VCOM amplitude */
+ write_reg(par, 0x0013, 0x0000);
+
+ /* Dis-charge capacitor power voltage */
+ mdelay(200);
+
+ /* SAP, BT[3:0], AP, DSTB, SLP, STB */
+ write_reg(par, 0x0010, 0x17B0);
+
+ /* R11h=0x0031 at VCI=3.3V DC1[2:0], DC0[2:0], VC[2:0] */
+ write_reg(par, 0x0011, 0x0031);
+ mdelay(50);
+
+ /* R12h=0x0138 at VCI=3.3V VREG1OUT voltage */
+ write_reg(par, 0x0012, 0x0138);
+ mdelay(50);
+
+ /* R13h=0x1800 at VCI=3.3V VDV[4:0] for VCOM amplitude */
+ write_reg(par, 0x0013, 0x1800);
+
+ /* R29h=0x0008 at VCI=3.3V VCM[4:0] for VCOMH */
+ write_reg(par, 0x0029, 0x0008);
+ mdelay(50);
+
+ /* GRAM horizontal Address */
+ write_reg(par, 0x0020, 0x0000);
+
+ /* GRAM Vertical Address */
+ write_reg(par, 0x0021, 0x0000);
+
+ /* ------------------ Set GRAM area --------------- */
+ /* Horizontal GRAM Start Address */
+ write_reg(par, 0x0050, 0x0000);
+
+ /* Horizontal GRAM End Address */
+ write_reg(par, 0x0051, 0x00EF);
+
+ /* Vertical GRAM Start Address */
+ write_reg(par, 0x0052, 0x0000);
+
+ /* Vertical GRAM End Address */
+ write_reg(par, 0x0053, 0x013F);
+
+ /* Gate Scan Line */
+ write_reg(par, 0x0060, 0x2700);
+
+ /* NDL,VLE, REV */
+ write_reg(par, 0x0061, 0x0001);
+
+ /* set scrolling line */
+ write_reg(par, 0x006A, 0x0000);
+
+ /* -------------- Partial Display Control --------- */
+ write_reg(par, 0x0080, 0x0000);
+ write_reg(par, 0x0081, 0x0000);
+ write_reg(par, 0x0082, 0x0000);
+ write_reg(par, 0x0083, 0x0000);
+ write_reg(par, 0x0084, 0x0000);
+ write_reg(par, 0x0085, 0x0000);
+
+ /* -------------- Panel Control ------------------- */
+ write_reg(par, 0x0090, 0x0010);
+ write_reg(par, 0x0092, 0x0000);
+ write_reg(par, 0x0093, 0x0003);
+ write_reg(par, 0x0095, 0x0110);
+ write_reg(par, 0x0097, 0x0000);
+ write_reg(par, 0x0098, 0x0000);
+ write_reg(par, 0x0007, 0x0173); /* 262K color and display ON */
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ switch (par->info->var.rotate) {
+ /* R20h = Horizontal GRAM Start Address */
+ /* R21h = Vertical GRAM Start Address */
+ case 0:
+ write_reg(par, 0x0020, xs);
+ write_reg(par, 0x0021, ys);
+ break;
+ case 180:
+ write_reg(par, 0x0020, WIDTH - 1 - xs);
+ write_reg(par, 0x0021, HEIGHT - 1 - ys);
+ break;
+ case 270:
+ write_reg(par, 0x0020, WIDTH - 1 - ys);
+ write_reg(par, 0x0021, xs);
+ break;
+ case 90:
+ write_reg(par, 0x0020, ys);
+ write_reg(par, 0x0021, HEIGHT - 1 - xs);
+ break;
+ }
+ write_reg(par, 0x0022); /* Write Data to GRAM */
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, 0x3, (par->bgr << 12) | 0x30);
+ break;
+ case 270:
+ write_reg(par, 0x3, (par->bgr << 12) | 0x28);
+ break;
+ case 180:
+ write_reg(par, 0x3, (par->bgr << 12) | 0x00);
+ break;
+ case 90:
+ write_reg(par, 0x3, (par->bgr << 12) | 0x18);
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Gamma string format:
+ * VRP0 VRP1 RP0 RP1 KP0 KP1 KP2 KP3 KP4 KP5
+ * VRN0 VRN1 RN0 RN1 KN0 KN1 KN2 KN3 KN4 KN5
+ */
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ static const unsigned long mask[] = {
+ 0x1f, 0x1f, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x1f, 0x1f, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ };
+ int i, j;
+
+ /* apply mask */
+ for (i = 0; i < 2; i++)
+ for (j = 0; j < 10; j++)
+ CURVE(i, j) &= mask[i * par->gamma.num_values + j];
+
+ write_reg(par, 0x0030, CURVE(0, 5) << 8 | CURVE(0, 4));
+ write_reg(par, 0x0031, CURVE(0, 7) << 8 | CURVE(0, 6));
+ write_reg(par, 0x0032, CURVE(0, 9) << 8 | CURVE(0, 8));
+ write_reg(par, 0x0035, CURVE(0, 3) << 8 | CURVE(0, 2));
+ write_reg(par, 0x0036, CURVE(0, 1) << 8 | CURVE(0, 0));
+
+ write_reg(par, 0x0037, CURVE(1, 5) << 8 | CURVE(1, 4));
+ write_reg(par, 0x0038, CURVE(1, 7) << 8 | CURVE(1, 6));
+ write_reg(par, 0x0039, CURVE(1, 9) << 8 | CURVE(1, 8));
+ write_reg(par, 0x003C, CURVE(1, 3) << 8 | CURVE(1, 2));
+ write_reg(par, 0x003D, CURVE(1, 1) << 8 | CURVE(1, 0));
+
+ return 0;
+}
+
+#undef CURVE
+
+static struct fbtft_display display = {
+ .regwidth = 16,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .gamma_num = 2,
+ .gamma_len = 10,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9320", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ili9320");
+MODULE_ALIAS("platform:ili9320");
+
+MODULE_DESCRIPTION("FB driver for the ILI9320 LCD Controller");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ili9325.c b/drivers/staging/fbtft/fb_ili9325.c
new file mode 100644
index 000000000..16d3b17ca
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ili9325.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ILI9325 LCD Controller
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ *
+ * Based on ili9325.c by Jeroen Domburg
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ili9325"
+#define WIDTH 240
+#define HEIGHT 320
+#define BPP 16
+#define FPS 20
+#define DEFAULT_GAMMA "0F 00 7 2 0 0 6 5 4 1\n" \
+ "04 16 2 7 6 3 2 1 7 7"
+
+static unsigned int bt = 6; /* VGL=Vci*4 , VGH=Vci*4 */
+module_param(bt, uint, 0000);
+MODULE_PARM_DESC(bt, "Sets the factor used in the step-up circuits");
+
+static unsigned int vc = 0x03; /* Vci1=Vci*0.80 */
+module_param(vc, uint, 0000);
+MODULE_PARM_DESC(vc, "Sets the ratio factor of Vci to generate the reference voltages Vci1");
+
+static unsigned int vrh = 0x0d; /* VREG1OUT=Vci*1.85 */
+module_param(vrh, uint, 0000);
+MODULE_PARM_DESC(vrh, "Set the amplifying rate (1.6 ~ 1.9) of Vci applied to output the VREG1OUT");
+
+static unsigned int vdv = 0x12; /* VCOMH amplitude=VREG1OUT*0.98 */
+module_param(vdv, uint, 0000);
+MODULE_PARM_DESC(vdv, "Select the factor of VREG1OUT to set the amplitude of Vcom");
+
+static unsigned int vcm = 0x0a; /* VCOMH=VREG1OUT*0.735 */
+module_param(vcm, uint, 0000);
+MODULE_PARM_DESC(vcm, "Set the internal VcomH voltage");
+
+/*
+ * Verify that this configuration is within the Voltage limits
+ *
+ * Display module configuration: Vcc = IOVcc = Vci = 3.3V
+ *
+ * Voltages
+ * ----------
+ * Vci = 3.3
+ * Vci1 = Vci * 0.80 = 2.64
+ * DDVDH = Vci1 * 2 = 5.28
+ * VCL = -Vci1 = -2.64
+ * VREG1OUT = Vci * 1.85 = 4.88
+ * VCOMH = VREG1OUT * 0.735 = 3.59
+ * VCOM amplitude = VREG1OUT * 0.98 = 4.79
+ * VGH = Vci * 4 = 13.2
+ * VGL = -Vci * 4 = -13.2
+ *
+ * Limits
+ * --------
+ * Power supplies
+ * 1.65 < IOVcc < 3.30 => 1.65 < 3.3 < 3.30
+ * 2.40 < Vcc < 3.30 => 2.40 < 3.3 < 3.30
+ * 2.50 < Vci < 3.30 => 2.50 < 3.3 < 3.30
+ *
+ * Source/VCOM power supply voltage
+ * 4.50 < DDVDH < 6.0 => 4.50 < 5.28 < 6.0
+ * -3.0 < VCL < -2.0 => -3.0 < -2.64 < -2.0
+ * VCI - VCL < 6.0 => 5.94 < 6.0
+ *
+ * Gate driver output voltage
+ * 10 < VGH < 20 => 10 < 13.2 < 20
+ * -15 < VGL < -5 => -15 < -13.2 < -5
+ * VGH - VGL < 32 => 26.4 < 32
+ *
+ * VCOM driver output voltage
+ * VCOMH - VCOML < 6.0 => 4.79 < 6.0
+ */
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ bt &= 0x07;
+ vc &= 0x07;
+ vrh &= 0x0f;
+ vdv &= 0x1f;
+ vcm &= 0x3f;
+
+ /* Initialization sequence from ILI9325 Application Notes */
+
+ /* ----------- Start Initial Sequence ----------- */
+ write_reg(par, 0x00E3, 0x3008); /* Set internal timing */
+ write_reg(par, 0x00E7, 0x0012); /* Set internal timing */
+ write_reg(par, 0x00EF, 0x1231); /* Set internal timing */
+ write_reg(par, 0x0001, 0x0100); /* set SS and SM bit */
+ write_reg(par, 0x0002, 0x0700); /* set 1 line inversion */
+ write_reg(par, 0x0004, 0x0000); /* Resize register */
+ write_reg(par, 0x0008, 0x0207); /* set the back porch and front porch */
+ write_reg(par, 0x0009, 0x0000); /* set non-display area refresh cycle */
+ write_reg(par, 0x000A, 0x0000); /* FMARK function */
+ write_reg(par, 0x000C, 0x0000); /* RGB interface setting */
+ write_reg(par, 0x000D, 0x0000); /* Frame marker Position */
+ write_reg(par, 0x000F, 0x0000); /* RGB interface polarity */
+
+ /* ----------- Power On sequence ----------- */
+ write_reg(par, 0x0010, 0x0000); /* SAP, BT[3:0], AP, DSTB, SLP, STB */
+ write_reg(par, 0x0011, 0x0007); /* DC1[2:0], DC0[2:0], VC[2:0] */
+ write_reg(par, 0x0012, 0x0000); /* VREG1OUT voltage */
+ write_reg(par, 0x0013, 0x0000); /* VDV[4:0] for VCOM amplitude */
+ mdelay(200); /* Dis-charge capacitor power voltage */
+ write_reg(par, 0x0010, /* SAP, BT[3:0], AP, DSTB, SLP, STB */
+ BIT(12) | (bt << 8) | BIT(7) | BIT(4));
+ write_reg(par, 0x0011, 0x220 | vc); /* DC1[2:0], DC0[2:0], VC[2:0] */
+ mdelay(50); /* Delay 50ms */
+ write_reg(par, 0x0012, vrh); /* Internal reference voltage= Vci; */
+ mdelay(50); /* Delay 50ms */
+ write_reg(par, 0x0013, vdv << 8); /* Set VDV[4:0] for VCOM amplitude */
+ write_reg(par, 0x0029, vcm); /* Set VCM[5:0] for VCOMH */
+ write_reg(par, 0x002B, 0x000C); /* Set Frame Rate */
+ mdelay(50); /* Delay 50ms */
+ write_reg(par, 0x0020, 0x0000); /* GRAM horizontal Address */
+ write_reg(par, 0x0021, 0x0000); /* GRAM Vertical Address */
+
+ /*------------------ Set GRAM area --------------- */
+ write_reg(par, 0x0050, 0x0000); /* Horizontal GRAM Start Address */
+ write_reg(par, 0x0051, 0x00EF); /* Horizontal GRAM End Address */
+ write_reg(par, 0x0052, 0x0000); /* Vertical GRAM Start Address */
+ write_reg(par, 0x0053, 0x013F); /* Vertical GRAM Start Address */
+ write_reg(par, 0x0060, 0xA700); /* Gate Scan Line */
+ write_reg(par, 0x0061, 0x0001); /* NDL,VLE, REV */
+ write_reg(par, 0x006A, 0x0000); /* set scrolling line */
+
+ /*-------------- Partial Display Control --------- */
+ write_reg(par, 0x0080, 0x0000);
+ write_reg(par, 0x0081, 0x0000);
+ write_reg(par, 0x0082, 0x0000);
+ write_reg(par, 0x0083, 0x0000);
+ write_reg(par, 0x0084, 0x0000);
+ write_reg(par, 0x0085, 0x0000);
+
+ /*-------------- Panel Control ------------------- */
+ write_reg(par, 0x0090, 0x0010);
+ write_reg(par, 0x0092, 0x0600);
+ write_reg(par, 0x0007, 0x0133); /* 262K color and display ON */
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ switch (par->info->var.rotate) {
+ /* R20h = Horizontal GRAM Start Address */
+ /* R21h = Vertical GRAM Start Address */
+ case 0:
+ write_reg(par, 0x0020, xs);
+ write_reg(par, 0x0021, ys);
+ break;
+ case 180:
+ write_reg(par, 0x0020, WIDTH - 1 - xs);
+ write_reg(par, 0x0021, HEIGHT - 1 - ys);
+ break;
+ case 270:
+ write_reg(par, 0x0020, WIDTH - 1 - ys);
+ write_reg(par, 0x0021, xs);
+ break;
+ case 90:
+ write_reg(par, 0x0020, ys);
+ write_reg(par, 0x0021, HEIGHT - 1 - xs);
+ break;
+ }
+ write_reg(par, 0x0022); /* Write Data to GRAM */
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ /* AM: GRAM update direction */
+ case 0:
+ write_reg(par, 0x03, 0x0030 | (par->bgr << 12));
+ break;
+ case 180:
+ write_reg(par, 0x03, 0x0000 | (par->bgr << 12));
+ break;
+ case 270:
+ write_reg(par, 0x03, 0x0028 | (par->bgr << 12));
+ break;
+ case 90:
+ write_reg(par, 0x03, 0x0018 | (par->bgr << 12));
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Gamma string format:
+ * VRP0 VRP1 RP0 RP1 KP0 KP1 KP2 KP3 KP4 KP5
+ * VRN0 VRN1 RN0 RN1 KN0 KN1 KN2 KN3 KN4 KN5
+ */
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ static const unsigned long mask[] = {
+ 0x1f, 0x1f, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x1f, 0x1f, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ };
+ int i, j;
+
+ /* apply mask */
+ for (i = 0; i < 2; i++)
+ for (j = 0; j < 10; j++)
+ CURVE(i, j) &= mask[i * par->gamma.num_values + j];
+
+ write_reg(par, 0x0030, CURVE(0, 5) << 8 | CURVE(0, 4));
+ write_reg(par, 0x0031, CURVE(0, 7) << 8 | CURVE(0, 6));
+ write_reg(par, 0x0032, CURVE(0, 9) << 8 | CURVE(0, 8));
+ write_reg(par, 0x0035, CURVE(0, 3) << 8 | CURVE(0, 2));
+ write_reg(par, 0x0036, CURVE(0, 1) << 8 | CURVE(0, 0));
+
+ write_reg(par, 0x0037, CURVE(1, 5) << 8 | CURVE(1, 4));
+ write_reg(par, 0x0038, CURVE(1, 7) << 8 | CURVE(1, 6));
+ write_reg(par, 0x0039, CURVE(1, 9) << 8 | CURVE(1, 8));
+ write_reg(par, 0x003C, CURVE(1, 3) << 8 | CURVE(1, 2));
+ write_reg(par, 0x003D, CURVE(1, 1) << 8 | CURVE(1, 0));
+
+ return 0;
+}
+
+#undef CURVE
+
+static struct fbtft_display display = {
+ .regwidth = 16,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .bpp = BPP,
+ .fps = FPS,
+ .gamma_num = 2,
+ .gamma_len = 10,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9325", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ili9325");
+MODULE_ALIAS("platform:ili9325");
+
+MODULE_DESCRIPTION("FB driver for the ILI9325 LCD Controller");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ili9340.c b/drivers/staging/fbtft/fb_ili9340.c
new file mode 100644
index 000000000..704236bca
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ili9340.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ILI9340 LCD Controller
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ili9340"
+#define WIDTH 240
+#define HEIGHT 320
+
+/* Init sequence taken from: Arduino Library for the Adafruit 2.2" display */
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ write_reg(par, 0xEF, 0x03, 0x80, 0x02);
+ write_reg(par, 0xCF, 0x00, 0XC1, 0X30);
+ write_reg(par, 0xED, 0x64, 0x03, 0X12, 0X81);
+ write_reg(par, 0xE8, 0x85, 0x00, 0x78);
+ write_reg(par, 0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02);
+ write_reg(par, 0xF7, 0x20);
+ write_reg(par, 0xEA, 0x00, 0x00);
+
+ /* Power Control 1 */
+ write_reg(par, 0xC0, 0x23);
+
+ /* Power Control 2 */
+ write_reg(par, 0xC1, 0x10);
+
+ /* VCOM Control 1 */
+ write_reg(par, 0xC5, 0x3e, 0x28);
+
+ /* VCOM Control 2 */
+ write_reg(par, 0xC7, 0x86);
+
+ /* COLMOD: Pixel Format Set */
+ /* 16 bits/pixel */
+ write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
+
+ /* Frame Rate Control */
+ /* Division ratio = fosc, Frame Rate = 79Hz */
+ write_reg(par, 0xB1, 0x00, 0x18);
+
+ /* Display Function Control */
+ write_reg(par, 0xB6, 0x08, 0x82, 0x27);
+
+ /* Gamma Function Disable */
+ write_reg(par, 0xF2, 0x00);
+
+ /* Gamma curve selection */
+ write_reg(par, MIPI_DCS_SET_GAMMA_CURVE, 0x01);
+
+ /* Positive Gamma Correction */
+ write_reg(par, 0xE0,
+ 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1,
+ 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00);
+
+ /* Negative Gamma Correction */
+ write_reg(par, 0xE1,
+ 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1,
+ 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F);
+
+ write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
+
+ mdelay(120);
+
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF);
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+#define ILI9340_MADCTL_MV 0x20
+#define ILI9340_MADCTL_MX 0x40
+#define ILI9340_MADCTL_MY 0x80
+static int set_var(struct fbtft_par *par)
+{
+ u8 val;
+
+ switch (par->info->var.rotate) {
+ case 270:
+ val = ILI9340_MADCTL_MV;
+ break;
+ case 180:
+ val = ILI9340_MADCTL_MY;
+ break;
+ case 90:
+ val = ILI9340_MADCTL_MV | ILI9340_MADCTL_MY | ILI9340_MADCTL_MX;
+ break;
+ default:
+ val = ILI9340_MADCTL_MX;
+ break;
+ }
+ /* Memory Access Control */
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, val | (par->bgr << 3));
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9340", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ili9340");
+MODULE_ALIAS("platform:ili9340");
+
+MODULE_DESCRIPTION("FB driver for the ILI9340 LCD Controller");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ili9341.c b/drivers/staging/fbtft/fb_ili9341.c
new file mode 100644
index 000000000..47e72b87d
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ili9341.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ILI9341 LCD display controller
+ *
+ * This display uses 9-bit SPI: Data/Command bit + 8 data bits
+ * For platforms that doesn't support 9-bit, the driver is capable
+ * of emulating this using 8-bit transfer.
+ * This is done by transferring eight 9-bit words in 9 bytes.
+ *
+ * Copyright (C) 2013 Christian Vogelgsang
+ * Based on adafruit22fb.c by Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ili9341"
+#define WIDTH 240
+#define HEIGHT 320
+#define TXBUFLEN (4 * PAGE_SIZE)
+#define DEFAULT_GAMMA "1F 1A 18 0A 0F 06 45 87 32 0A 07 02 07 05 00\n" \
+ "00 25 27 05 10 09 3A 78 4D 05 18 0D 38 3A 1F"
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* startup sequence for MI0283QT-9A */
+ write_reg(par, MIPI_DCS_SOFT_RESET);
+ mdelay(5);
+ write_reg(par, MIPI_DCS_SET_DISPLAY_OFF);
+ /* --------------------------------------------------------- */
+ write_reg(par, 0xCF, 0x00, 0x83, 0x30);
+ write_reg(par, 0xED, 0x64, 0x03, 0x12, 0x81);
+ write_reg(par, 0xE8, 0x85, 0x01, 0x79);
+ write_reg(par, 0xCB, 0x39, 0X2C, 0x00, 0x34, 0x02);
+ write_reg(par, 0xF7, 0x20);
+ write_reg(par, 0xEA, 0x00, 0x00);
+ /* ------------power control-------------------------------- */
+ write_reg(par, 0xC0, 0x26);
+ write_reg(par, 0xC1, 0x11);
+ /* ------------VCOM --------- */
+ write_reg(par, 0xC5, 0x35, 0x3E);
+ write_reg(par, 0xC7, 0xBE);
+ /* ------------memory access control------------------------ */
+ write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, 0x55); /* 16bit pixel */
+ /* ------------frame rate----------------------------------- */
+ write_reg(par, 0xB1, 0x00, 0x1B);
+ /* ------------Gamma---------------------------------------- */
+ /* write_reg(par, 0xF2, 0x08); */ /* Gamma Function Disable */
+ write_reg(par, MIPI_DCS_SET_GAMMA_CURVE, 0x01);
+ /* ------------display-------------------------------------- */
+ write_reg(par, 0xB7, 0x07); /* entry mode set */
+ write_reg(par, 0xB6, 0x0A, 0x82, 0x27, 0x00);
+ write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
+ mdelay(100);
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
+ mdelay(20);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ (xs >> 8) & 0xFF, xs & 0xFF, (xe >> 8) & 0xFF, xe & 0xFF);
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ (ys >> 8) & 0xFF, ys & 0xFF, (ye >> 8) & 0xFF, ye & 0xFF);
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+#define MEM_Y BIT(7) /* MY row address order */
+#define MEM_X BIT(6) /* MX column address order */
+#define MEM_V BIT(5) /* MV row / column exchange */
+#define MEM_L BIT(4) /* ML vertical refresh order */
+#define MEM_H BIT(2) /* MH horizontal refresh order */
+#define MEM_BGR (3) /* RGB-BGR Order */
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MEM_X | (par->bgr << MEM_BGR));
+ break;
+ case 270:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MEM_V | MEM_L | (par->bgr << MEM_BGR));
+ break;
+ case 180:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MEM_Y | (par->bgr << MEM_BGR));
+ break;
+ case 90:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MEM_Y | MEM_X | MEM_V | (par->bgr << MEM_BGR));
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Gamma string format:
+ * Positive: Par1 Par2 [...] Par15
+ * Negative: Par1 Par2 [...] Par15
+ */
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ int i;
+
+ for (i = 0; i < par->gamma.num_curves; i++)
+ write_reg(par, 0xE0 + i,
+ CURVE(i, 0), CURVE(i, 1), CURVE(i, 2),
+ CURVE(i, 3), CURVE(i, 4), CURVE(i, 5),
+ CURVE(i, 6), CURVE(i, 7), CURVE(i, 8),
+ CURVE(i, 9), CURVE(i, 10), CURVE(i, 11),
+ CURVE(i, 12), CURVE(i, 13), CURVE(i, 14));
+
+ return 0;
+}
+
+#undef CURVE
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .txbuflen = TXBUFLEN,
+ .gamma_num = 2,
+ .gamma_len = 15,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_SPI_DRIVER(DRVNAME, "ilitek", "ili9341", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ili9341");
+MODULE_ALIAS("platform:ili9341");
+
+MODULE_DESCRIPTION("FB driver for the ILI9341 LCD display controller");
+MODULE_AUTHOR("Christian Vogelgsang");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ili9481.c b/drivers/staging/fbtft/fb_ili9481.c
new file mode 100644
index 000000000..19eba085e
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ili9481.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ILI9481 LCD Controller
+ *
+ * Copyright (c) 2014 Petr Olivka
+ * Copyright (c) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ili9481"
+#define WIDTH 320
+#define HEIGHT 480
+
+static const s16 default_init_sequence[] = {
+ /* SLP_OUT - Sleep out */
+ -1, MIPI_DCS_EXIT_SLEEP_MODE,
+ -2, 50,
+ /* Power setting */
+ -1, 0xD0, 0x07, 0x42, 0x18,
+ /* VCOM */
+ -1, 0xD1, 0x00, 0x07, 0x10,
+ /* Power setting for norm. mode */
+ -1, 0xD2, 0x01, 0x02,
+ /* Panel driving setting */
+ -1, 0xC0, 0x10, 0x3B, 0x00, 0x02, 0x11,
+ /* Frame rate & inv. */
+ -1, 0xC5, 0x03,
+ /* Pixel format */
+ -1, MIPI_DCS_SET_PIXEL_FORMAT, 0x55,
+ /* Gamma */
+ -1, 0xC8, 0x00, 0x32, 0x36, 0x45, 0x06, 0x16,
+ 0x37, 0x75, 0x77, 0x54, 0x0C, 0x00,
+ /* DISP_ON */
+ -1, MIPI_DCS_SET_DISPLAY_ON,
+ -3
+};
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xff, xe >> 8, xe & 0xff);
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xff, ye >> 8, ye & 0xff);
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+#define HFLIP 0x01
+#define VFLIP 0x02
+#define ROW_X_COL 0x20
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ case 270:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ ROW_X_COL | HFLIP | VFLIP | (par->bgr << 3));
+ break;
+ case 180:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ VFLIP | (par->bgr << 3));
+ break;
+ case 90:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ ROW_X_COL | (par->bgr << 3));
+ break;
+ default:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ HFLIP | (par->bgr << 3));
+ break;
+ }
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .init_sequence = default_init_sequence,
+ .fbtftops = {
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9481", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ili9481");
+MODULE_ALIAS("platform:ili9481");
+
+MODULE_DESCRIPTION("FB driver for the ILI9481 LCD Controller");
+MODULE_AUTHOR("Petr Olivka");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ili9486.c b/drivers/staging/fbtft/fb_ili9486.c
new file mode 100644
index 000000000..66210a713
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ili9486.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ILI9486 LCD Controller
+ *
+ * Copyright (C) 2014 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ili9486"
+#define WIDTH 320
+#define HEIGHT 480
+
+/* this init sequence matches PiScreen */
+static const s16 default_init_sequence[] = {
+ /* Interface Mode Control */
+ -1, 0xb0, 0x0,
+ -1, MIPI_DCS_EXIT_SLEEP_MODE,
+ -2, 250,
+ /* Interface Pixel Format */
+ -1, MIPI_DCS_SET_PIXEL_FORMAT, 0x55,
+ /* Power Control 3 */
+ -1, 0xC2, 0x44,
+ /* VCOM Control 1 */
+ -1, 0xC5, 0x00, 0x00, 0x00, 0x00,
+ /* PGAMCTRL(Positive Gamma Control) */
+ -1, 0xE0, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98,
+ 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00,
+ /* NGAMCTRL(Negative Gamma Control) */
+ -1, 0xE1, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75,
+ 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00,
+ /* Digital Gamma Control 1 */
+ -1, 0xE2, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75,
+ 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00,
+ -1, MIPI_DCS_EXIT_SLEEP_MODE,
+ -1, MIPI_DCS_SET_DISPLAY_ON,
+ /* end marker */
+ -3
+};
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF);
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ 0x80 | (par->bgr << 3));
+ break;
+ case 90:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ 0x20 | (par->bgr << 3));
+ break;
+ case 180:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ 0x40 | (par->bgr << 3));
+ break;
+ case 270:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ 0xE0 | (par->bgr << 3));
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .init_sequence = default_init_sequence,
+ .fbtftops = {
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9486", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ili9486");
+MODULE_ALIAS("platform:ili9486");
+
+MODULE_DESCRIPTION("FB driver for the ILI9486 LCD Controller");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_pcd8544.c b/drivers/staging/fbtft/fb_pcd8544.c
new file mode 100644
index 000000000..08f8a4bb8
--- /dev/null
+++ b/drivers/staging/fbtft/fb_pcd8544.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the PCD8544 LCD Controller
+ *
+ * The display is monochrome and the video memory is RGB565.
+ * Any pixel value except 0 turns the pixel on.
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_pcd8544"
+#define WIDTH 84
+#define HEIGHT 48
+#define TXBUFLEN (84 * 6)
+#define DEFAULT_GAMMA "40" /* gamma controls the contrast in this driver */
+
+static unsigned int tc;
+module_param(tc, uint, 0000);
+MODULE_PARM_DESC(tc, "TC[1:0] Temperature coefficient: 0-3 (default: 0)");
+
+static unsigned int bs = 4;
+module_param(bs, uint, 0000);
+MODULE_PARM_DESC(bs, "BS[2:0] Bias voltage level: 0-7 (default: 4)");
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* Function set
+ *
+ * 5:1 1
+ * 2:0 PD - Powerdown control: chip is active
+ * 1:0 V - Entry mode: horizontal addressing
+ * 0:1 H - Extended instruction set control: extended
+ */
+ write_reg(par, 0x21);
+
+ /* H=1 Temperature control
+ *
+ * 2:1 1
+ * 1:x TC1 - Temperature Coefficient: 0x10
+ * 0:x TC0
+ */
+ write_reg(par, 0x04 | (tc & 0x3));
+
+ /* H=1 Bias system
+ *
+ * 4:1 1
+ * 3:0 0
+ * 2:x BS2 - Bias System
+ * 1:x BS1
+ * 0:x BS0
+ */
+ write_reg(par, 0x10 | (bs & 0x7));
+
+ /* Function set
+ *
+ * 5:1 1
+ * 2:0 PD - Powerdown control: chip is active
+ * 1:1 V - Entry mode: vertical addressing
+ * 0:0 H - Extended instruction set control: basic
+ */
+ write_reg(par, 0x22);
+
+ /* H=0 Display control
+ *
+ * 3:1 1
+ * 2:1 D - DE: 10=normal mode
+ * 1:0 0
+ * 0:0 E
+ */
+ write_reg(par, 0x08 | 4);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ /* H=0 Set X address of RAM
+ *
+ * 7:1 1
+ * 6-0: X[6:0] - 0x00
+ */
+ write_reg(par, 0x80);
+
+ /* H=0 Set Y address of RAM
+ *
+ * 7:0 0
+ * 6:1 1
+ * 2-0: Y[2:0] - 0x0
+ */
+ write_reg(par, 0x40);
+}
+
+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, i;
+ int ret = 0;
+
+ for (x = 0; x < 84; x++) {
+ for (y = 0; y < 6; y++) {
+ *buf = 0x00;
+ for (i = 0; i < 8; i++)
+ *buf |= (vmem16[(y * 8 + i) * 84 + x] ?
+ 1 : 0) << i;
+ buf++;
+ }
+ }
+
+ /* Write data */
+ gpiod_set_value(par->gpio.dc, 1);
+ ret = par->fbtftops.write(par, par->txbuf.buf, 6 * 84);
+ if (ret < 0)
+ dev_err(par->info->device, "write failed and returned: %d\n",
+ ret);
+
+ return ret;
+}
+
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ /* apply mask */
+ curves[0] &= 0x7F;
+
+ write_reg(par, 0x23); /* turn on extended instruction set */
+ write_reg(par, 0x80 | curves[0]);
+ write_reg(par, 0x22); /* turn off extended instruction set */
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .txbuflen = TXBUFLEN,
+ .gamma_num = 1,
+ .gamma_len = 1,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .write_vmem = write_vmem,
+ .set_gamma = set_gamma,
+ },
+ .backlight = 1,
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "philips,pcd8544", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("spi:pcd8544");
+
+MODULE_DESCRIPTION("FB driver for the PCD8544 LCD Controller");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ra8875.c b/drivers/staging/fbtft/fb_ra8875.c
new file mode 100644
index 000000000..398bdbf53
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ra8875.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FBTFT driver for the RA8875 LCD Controller
+ * Copyright by Pf@nne & NOTRO
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <linux/gpio/consumer.h>
+#include "fbtft.h"
+
+#define DRVNAME "fb_ra8875"
+
+static int write_spi(struct fbtft_par *par, void *buf, size_t len)
+{
+ struct spi_transfer t = {
+ .tx_buf = buf,
+ .len = len,
+ .speed_hz = 1000000,
+ };
+ struct spi_message m;
+
+ fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
+ "%s(len=%zu): ", __func__, len);
+
+ if (!par->spi) {
+ dev_err(par->info->device,
+ "%s: par->spi is unexpectedly NULL\n", __func__);
+ return -1;
+ }
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ return spi_sync(par->spi, &m);
+}
+
+static int init_display(struct fbtft_par *par)
+{
+ gpiod_set_value(par->gpio.dc, 1);
+
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
+ "%s()\n", __func__);
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
+ "display size %dx%d\n",
+ par->info->var.xres,
+ par->info->var.yres);
+
+ par->fbtftops.reset(par);
+
+ if ((par->info->var.xres == 320) && (par->info->var.yres == 240)) {
+ /* PLL clock frequency */
+ write_reg(par, 0x88, 0x0A);
+ write_reg(par, 0x89, 0x02);
+ mdelay(10);
+ /* color deep / MCU Interface */
+ write_reg(par, 0x10, 0x0C);
+ /* pixel clock period */
+ write_reg(par, 0x04, 0x03);
+ mdelay(1);
+ /* horizontal settings */
+ write_reg(par, 0x14, 0x27);
+ write_reg(par, 0x15, 0x00);
+ write_reg(par, 0x16, 0x05);
+ write_reg(par, 0x17, 0x04);
+ write_reg(par, 0x18, 0x03);
+ /* vertical settings */
+ write_reg(par, 0x19, 0xEF);
+ write_reg(par, 0x1A, 0x00);
+ write_reg(par, 0x1B, 0x05);
+ write_reg(par, 0x1C, 0x00);
+ write_reg(par, 0x1D, 0x0E);
+ write_reg(par, 0x1E, 0x00);
+ write_reg(par, 0x1F, 0x02);
+ } else if ((par->info->var.xres == 480) &&
+ (par->info->var.yres == 272)) {
+ /* PLL clock frequency */
+ write_reg(par, 0x88, 0x0A);
+ write_reg(par, 0x89, 0x02);
+ mdelay(10);
+ /* color deep / MCU Interface */
+ write_reg(par, 0x10, 0x0C);
+ /* pixel clock period */
+ write_reg(par, 0x04, 0x82);
+ mdelay(1);
+ /* horizontal settings */
+ write_reg(par, 0x14, 0x3B);
+ write_reg(par, 0x15, 0x00);
+ write_reg(par, 0x16, 0x01);
+ write_reg(par, 0x17, 0x00);
+ write_reg(par, 0x18, 0x05);
+ /* vertical settings */
+ write_reg(par, 0x19, 0x0F);
+ write_reg(par, 0x1A, 0x01);
+ write_reg(par, 0x1B, 0x02);
+ write_reg(par, 0x1C, 0x00);
+ write_reg(par, 0x1D, 0x07);
+ write_reg(par, 0x1E, 0x00);
+ write_reg(par, 0x1F, 0x09);
+ } else if ((par->info->var.xres == 640) &&
+ (par->info->var.yres == 480)) {
+ /* PLL clock frequency */
+ write_reg(par, 0x88, 0x0B);
+ write_reg(par, 0x89, 0x02);
+ mdelay(10);
+ /* color deep / MCU Interface */
+ write_reg(par, 0x10, 0x0C);
+ /* pixel clock period */
+ write_reg(par, 0x04, 0x01);
+ mdelay(1);
+ /* horizontal settings */
+ write_reg(par, 0x14, 0x4F);
+ write_reg(par, 0x15, 0x05);
+ write_reg(par, 0x16, 0x0F);
+ write_reg(par, 0x17, 0x01);
+ write_reg(par, 0x18, 0x00);
+ /* vertical settings */
+ write_reg(par, 0x19, 0xDF);
+ write_reg(par, 0x1A, 0x01);
+ write_reg(par, 0x1B, 0x0A);
+ write_reg(par, 0x1C, 0x00);
+ write_reg(par, 0x1D, 0x0E);
+ write_reg(par, 0x1E, 0x00);
+ write_reg(par, 0x1F, 0x01);
+ } else if ((par->info->var.xres == 800) &&
+ (par->info->var.yres == 480)) {
+ /* PLL clock frequency */
+ write_reg(par, 0x88, 0x0B);
+ write_reg(par, 0x89, 0x02);
+ mdelay(10);
+ /* color deep / MCU Interface */
+ write_reg(par, 0x10, 0x0C);
+ /* pixel clock period */
+ write_reg(par, 0x04, 0x81);
+ mdelay(1);
+ /* horizontal settings */
+ write_reg(par, 0x14, 0x63);
+ write_reg(par, 0x15, 0x03);
+ write_reg(par, 0x16, 0x03);
+ write_reg(par, 0x17, 0x02);
+ write_reg(par, 0x18, 0x00);
+ /* vertical settings */
+ write_reg(par, 0x19, 0xDF);
+ write_reg(par, 0x1A, 0x01);
+ write_reg(par, 0x1B, 0x14);
+ write_reg(par, 0x1C, 0x00);
+ write_reg(par, 0x1D, 0x06);
+ write_reg(par, 0x1E, 0x00);
+ write_reg(par, 0x1F, 0x01);
+ } else {
+ dev_err(par->info->device, "display size is not supported!!");
+ return -1;
+ }
+
+ /* PWM clock */
+ write_reg(par, 0x8a, 0x81);
+ write_reg(par, 0x8b, 0xFF);
+ mdelay(10);
+
+ /* Display ON */
+ write_reg(par, 0x01, 0x80);
+ mdelay(10);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ /* Set_Active_Window */
+ write_reg(par, 0x30, xs & 0x00FF);
+ write_reg(par, 0x31, (xs & 0xFF00) >> 8);
+ write_reg(par, 0x32, ys & 0x00FF);
+ write_reg(par, 0x33, (ys & 0xFF00) >> 8);
+ write_reg(par, 0x34, (xs + xe) & 0x00FF);
+ write_reg(par, 0x35, ((xs + xe) & 0xFF00) >> 8);
+ write_reg(par, 0x36, (ys + ye) & 0x00FF);
+ write_reg(par, 0x37, ((ys + ye) & 0xFF00) >> 8);
+
+ /* Set_Memory_Write_Cursor */
+ write_reg(par, 0x46, xs & 0xff);
+ write_reg(par, 0x47, (xs >> 8) & 0x03);
+ write_reg(par, 0x48, ys & 0xff);
+ write_reg(par, 0x49, (ys >> 8) & 0x01);
+
+ write_reg(par, 0x02);
+}
+
+static void write_reg8_bus8(struct fbtft_par *par, int len, ...)
+{
+ va_list args;
+ int i, ret;
+ u8 *buf = par->buf;
+
+ /* slow down spi-speed for writing registers */
+ par->fbtftops.write = write_spi;
+
+ 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++ = 0x80;
+ *buf = (u8)va_arg(args, unsigned int);
+ ret = par->fbtftops.write(par, par->buf, 2);
+ if (ret < 0) {
+ va_end(args);
+ dev_err(par->info->device, "write() failed and returned %dn",
+ ret);
+ return;
+ }
+ len--;
+
+ udelay(100);
+
+ if (len) {
+ buf = (u8 *)par->buf;
+ *buf++ = 0x00;
+ i = len;
+ while (i--)
+ *buf++ = (u8)va_arg(args, unsigned int);
+
+ ret = par->fbtftops.write(par, par->buf, len + 1);
+ if (ret < 0) {
+ va_end(args);
+ dev_err(par->info->device,
+ "write() failed and returned %dn", ret);
+ return;
+ }
+ }
+ va_end(args);
+
+ /* restore user spi-speed */
+ par->fbtftops.write = fbtft_write_spi;
+ udelay(100);
+}
+
+static int write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u16 *vmem16;
+ __be16 *txbuf16;
+ size_t remain;
+ size_t to_copy;
+ size_t tx_array_size;
+ int i;
+ int ret = 0;
+ size_t startbyte_size = 0;
+
+ fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n",
+ __func__, offset, len);
+
+ remain = len / 2;
+ vmem16 = (u16 *)(par->info->screen_buffer + offset);
+ tx_array_size = par->txbuf.len / 2;
+ txbuf16 = par->txbuf.buf + 1;
+ tx_array_size -= 2;
+ *(u8 *)(par->txbuf.buf) = 0x00;
+ startbyte_size = 1;
+
+ while (remain) {
+ to_copy = min(tx_array_size, remain);
+ dev_dbg(par->info->device, " to_copy=%zu, remain=%zu\n",
+ to_copy, remain - to_copy);
+
+ for (i = 0; i < to_copy; i++)
+ txbuf16[i] = cpu_to_be16(vmem16[i]);
+
+ vmem16 = vmem16 + to_copy;
+ ret = par->fbtftops.write(par, par->txbuf.buf,
+ startbyte_size + to_copy * 2);
+ if (ret < 0)
+ return ret;
+ remain -= to_copy;
+ }
+
+ return ret;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .write_register = write_reg8_bus8,
+ .write_vmem = write_vmem16_bus8,
+ .write = write_spi,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "raio,ra8875", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ra8875");
+MODULE_ALIAS("platform:ra8875");
+
+MODULE_DESCRIPTION("FB driver for the RA8875 LCD Controller");
+MODULE_AUTHOR("Pf@nne");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_s6d02a1.c b/drivers/staging/fbtft/fb_s6d02a1.c
new file mode 100644
index 000000000..d3d6871d8
--- /dev/null
+++ b/drivers/staging/fbtft/fb_s6d02a1.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the S6D02A1 LCD Controller
+ *
+ * Based on fb_st7735r.c by Noralf Tronnes
+ * Init code from UTFT library by Henning Karlsen
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_s6d02a1"
+
+static const s16 default_init_sequence[] = {
+ -1, 0xf0, 0x5a, 0x5a,
+
+ -1, 0xfc, 0x5a, 0x5a,
+
+ -1, 0xfa, 0x02, 0x1f, 0x00, 0x10, 0x22, 0x30, 0x38,
+ 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3d, 0x02, 0x01,
+
+ -1, 0xfb, 0x21, 0x00, 0x02, 0x04, 0x07, 0x0a, 0x0b,
+ 0x0c, 0x0c, 0x16, 0x1e, 0x30, 0x3f, 0x01, 0x02,
+
+ /* power setting sequence */
+ -1, 0xfd, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, 0x01,
+ 0x01, 0x00, 0x1f, 0x1f,
+
+ -1, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f,
+ 0x07, 0x00, 0x3C, 0x36, 0x00, 0x3C, 0x36, 0x00,
+
+ -1, 0xf5, 0x00, 0x70, 0x66, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6d, 0x66, 0x06,
+
+ -1, 0xf6, 0x02, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x06, 0x01, 0x00,
+
+ -1, 0xf2, 0x00, 0x01, 0x03, 0x08, 0x08, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x08, 0x08,
+
+ -1, 0xf8, 0x11,
+
+ -1, 0xf7, 0xc8, 0x20, 0x00, 0x00,
+
+ -1, 0xf3, 0x00, 0x00,
+
+ -1, MIPI_DCS_EXIT_SLEEP_MODE,
+ -2, 50,
+
+ -1, 0xf3, 0x00, 0x01,
+ -2, 50,
+ -1, 0xf3, 0x00, 0x03,
+ -2, 50,
+ -1, 0xf3, 0x00, 0x07,
+ -2, 50,
+ -1, 0xf3, 0x00, 0x0f,
+ -2, 50,
+
+ -1, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x3f,
+ 0x07, 0x00, 0x3C, 0x36, 0x00, 0x3C, 0x36, 0x00,
+ -2, 50,
+
+ -1, 0xf3, 0x00, 0x1f,
+ -2, 50,
+ -1, 0xf3, 0x00, 0x7f,
+ -2, 50,
+
+ -1, 0xf3, 0x00, 0xff,
+ -2, 50,
+
+ -1, 0xfd, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, 0x00,
+ 0x01, 0x00, 0x16, 0x16,
+
+ -1, 0xf4, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3f, 0x3f,
+ 0x07, 0x00, 0x3C, 0x36, 0x00, 0x3C, 0x36, 0x00,
+
+ /* initializing sequence */
+
+ -1, MIPI_DCS_SET_ADDRESS_MODE, 0x08,
+
+ -1, MIPI_DCS_SET_TEAR_ON, 0x00,
+
+ -1, MIPI_DCS_SET_PIXEL_FORMAT, 0x05,
+
+ /* gamma setting - possible values 0x01, 0x02, 0x04, 0x08 */
+ -1, MIPI_DCS_SET_GAMMA_CURVE, 0x01,
+
+ -2, 150,
+ -1, MIPI_DCS_SET_DISPLAY_ON,
+ -1, MIPI_DCS_WRITE_MEMORY_START,
+ /* end marker */
+ -3
+
+};
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF);
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+#define MY BIT(7)
+#define MX BIT(6)
+#define MV BIT(5)
+static int set_var(struct fbtft_par *par)
+{
+ /*
+ * Memory data access control (0x36h)
+ * RGB/BGR:
+ * 1. Mode selection pin SRGB
+ * RGB H/W pin for color filter setting: 0=RGB, 1=BGR
+ * 2. MADCTL RGB bit
+ * RGB-BGR ORDER color filter panel: 0=RGB, 1=BGR
+ */
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MX | MY | (par->bgr << 3));
+ break;
+ case 270:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MY | MV | (par->bgr << 3));
+ break;
+ case 180:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ par->bgr << 3);
+ break;
+ case 90:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MX | MV | (par->bgr << 3));
+ break;
+ }
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = 128,
+ .height = 160,
+ .init_sequence = default_init_sequence,
+ .fbtftops = {
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "samsung,s6d02a1", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:s6d02a1");
+MODULE_ALIAS("platform:s6d02a1");
+
+MODULE_DESCRIPTION("FB driver for the S6D02A1 LCD Controller");
+MODULE_AUTHOR("WOLFGANG BUENING");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_s6d1121.c b/drivers/staging/fbtft/fb_s6d1121.c
new file mode 100644
index 000000000..62f27172f
--- /dev/null
+++ b/drivers/staging/fbtft/fb_s6d1121.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the S6D1121 LCD Controller
+ *
+ * Copyright (C) 2013 Roman Rolinsky
+ *
+ * Based on fb_ili9325.c by Noralf Tronnes
+ * Based on ili9325.c by Jeroen Domburg
+ * Init code from UTFT library by Henning Karlsen
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_s6d1121"
+#define WIDTH 240
+#define HEIGHT 320
+#define BPP 16
+#define FPS 20
+#define DEFAULT_GAMMA "26 09 24 2C 1F 23 24 25 22 26 25 23 0D 00\n" \
+ "1C 1A 13 1D 0B 11 12 10 13 15 36 19 00 0D"
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* Initialization sequence from Lib_UTFT */
+
+ write_reg(par, 0x0011, 0x2004);
+ write_reg(par, 0x0013, 0xCC00);
+ write_reg(par, 0x0015, 0x2600);
+ write_reg(par, 0x0014, 0x252A);
+ write_reg(par, 0x0012, 0x0033);
+ write_reg(par, 0x0013, 0xCC04);
+ write_reg(par, 0x0013, 0xCC06);
+ write_reg(par, 0x0013, 0xCC4F);
+ write_reg(par, 0x0013, 0x674F);
+ write_reg(par, 0x0011, 0x2003);
+ write_reg(par, 0x0016, 0x0007);
+ write_reg(par, 0x0002, 0x0013);
+ write_reg(par, 0x0003, 0x0003);
+ write_reg(par, 0x0001, 0x0127);
+ write_reg(par, 0x0008, 0x0303);
+ write_reg(par, 0x000A, 0x000B);
+ write_reg(par, 0x000B, 0x0003);
+ write_reg(par, 0x000C, 0x0000);
+ write_reg(par, 0x0041, 0x0000);
+ write_reg(par, 0x0050, 0x0000);
+ write_reg(par, 0x0060, 0x0005);
+ write_reg(par, 0x0070, 0x000B);
+ write_reg(par, 0x0071, 0x0000);
+ write_reg(par, 0x0078, 0x0000);
+ write_reg(par, 0x007A, 0x0000);
+ write_reg(par, 0x0079, 0x0007);
+ write_reg(par, 0x0007, 0x0051);
+ write_reg(par, 0x0007, 0x0053);
+ write_reg(par, 0x0079, 0x0000);
+
+ write_reg(par, 0x0022); /* Write Data to GRAM */
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ switch (par->info->var.rotate) {
+ /* R20h = Horizontal GRAM Start Address */
+ /* R21h = Vertical GRAM Start Address */
+ case 0:
+ write_reg(par, 0x0020, xs);
+ write_reg(par, 0x0021, ys);
+ break;
+ case 180:
+ write_reg(par, 0x0020, WIDTH - 1 - xs);
+ write_reg(par, 0x0021, HEIGHT - 1 - ys);
+ break;
+ case 270:
+ write_reg(par, 0x0020, WIDTH - 1 - ys);
+ write_reg(par, 0x0021, xs);
+ break;
+ case 90:
+ write_reg(par, 0x0020, ys);
+ write_reg(par, 0x0021, HEIGHT - 1 - xs);
+ break;
+ }
+ write_reg(par, 0x0022); /* Write Data to GRAM */
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ /* AM: GRAM update direction */
+ case 0:
+ write_reg(par, 0x03, 0x0003 | (par->bgr << 12));
+ break;
+ case 180:
+ write_reg(par, 0x03, 0x0000 | (par->bgr << 12));
+ break;
+ case 270:
+ write_reg(par, 0x03, 0x000A | (par->bgr << 12));
+ break;
+ case 90:
+ write_reg(par, 0x03, 0x0009 | (par->bgr << 12));
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Gamma string format:
+ * PKP0 PKP1 PKP2 PKP3 PKP4 PKP5 PKP6 PKP7 PKP8 PKP9 PKP10 PKP11 VRP0 VRP1
+ * PKN0 PKN1 PKN2 PKN3 PKN4 PKN5 PKN6 PKN7 PRN8 PRN9 PRN10 PRN11 VRN0 VRN1
+ */
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ static const unsigned long mask[] = {
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x1f, 0x1f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x1f, 0x1f,
+ };
+ int i, j;
+
+ /* apply mask */
+ for (i = 0; i < 2; i++)
+ for (j = 0; j < 14; j++)
+ CURVE(i, j) &= mask[i * par->gamma.num_values + j];
+
+ write_reg(par, 0x0030, CURVE(0, 1) << 8 | CURVE(0, 0));
+ write_reg(par, 0x0031, CURVE(0, 3) << 8 | CURVE(0, 2));
+ write_reg(par, 0x0032, CURVE(0, 5) << 8 | CURVE(0, 3));
+ write_reg(par, 0x0033, CURVE(0, 7) << 8 | CURVE(0, 6));
+ write_reg(par, 0x0034, CURVE(0, 9) << 8 | CURVE(0, 8));
+ write_reg(par, 0x0035, CURVE(0, 11) << 8 | CURVE(0, 10));
+
+ write_reg(par, 0x0036, CURVE(1, 1) << 8 | CURVE(1, 0));
+ write_reg(par, 0x0037, CURVE(1, 3) << 8 | CURVE(1, 2));
+ write_reg(par, 0x0038, CURVE(1, 5) << 8 | CURVE(1, 4));
+ write_reg(par, 0x0039, CURVE(1, 7) << 8 | CURVE(1, 6));
+ write_reg(par, 0x003A, CURVE(1, 9) << 8 | CURVE(1, 8));
+ write_reg(par, 0x003B, CURVE(1, 11) << 8 | CURVE(1, 10));
+
+ write_reg(par, 0x003C, CURVE(0, 13) << 8 | CURVE(0, 12));
+ write_reg(par, 0x003D, CURVE(1, 13) << 8 | CURVE(1, 12));
+
+ return 0;
+}
+
+#undef CURVE
+
+static struct fbtft_display display = {
+ .regwidth = 16,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .bpp = BPP,
+ .fps = FPS,
+ .gamma_num = 2,
+ .gamma_len = 14,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "samsung,s6d1121", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:s6d1121");
+MODULE_ALIAS("platform:s6d1121");
+
+MODULE_DESCRIPTION("FB driver for the S6D1121 LCD Controller");
+MODULE_AUTHOR("Roman Rolinsky");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_seps525.c b/drivers/staging/fbtft/fb_seps525.c
new file mode 100644
index 000000000..05882e2cd
--- /dev/null
+++ b/drivers/staging/fbtft/fb_seps525.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FB driver for the NHD-1.69-160128UGC3 (Newhaven Display International, Inc.)
+ * using the SEPS525 (Syncoam) LCD Controller
+ *
+ * Copyright (C) 2016 Analog Devices Inc.
+ *
+ * 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 "fbtft.h"
+
+#define DRVNAME "fb_seps525"
+#define WIDTH 160
+#define HEIGHT 128
+
+#define SEPS525_INDEX 0x00
+#define SEPS525_STATUS_RD 0x01
+#define SEPS525_OSC_CTL 0x02
+#define SEPS525_IREF 0x80
+#define SEPS525_CLOCK_DIV 0x03
+#define SEPS525_REDUCE_CURRENT 0x04
+#define SEPS525_SOFT_RST 0x05
+#define SEPS525_DISP_ONOFF 0x06
+#define SEPS525_PRECHARGE_TIME_R 0x08
+#define SEPS525_PRECHARGE_TIME_G 0x09
+#define SEPS525_PRECHARGE_TIME_B 0x0A
+#define SEPS525_PRECHARGE_CURRENT_R 0x0B
+#define SEPS525_PRECHARGE_CURRENT_G 0x0C
+#define SEPS525_PRECHARGE_CURRENT_B 0x0D
+#define SEPS525_DRIVING_CURRENT_R 0x10
+#define SEPS525_DRIVING_CURRENT_G 0x11
+#define SEPS525_DRIVING_CURRENT_B 0x12
+#define SEPS525_DISPLAYMODE_SET 0x13
+#define SEPS525_RGBIF 0x14
+#define SEPS525_RGB_POL 0x15
+#define SEPS525_MEMORY_WRITEMODE 0x16
+#define SEPS525_MX1_ADDR 0x17
+#define SEPS525_MX2_ADDR 0x18
+#define SEPS525_MY1_ADDR 0x19
+#define SEPS525_MY2_ADDR 0x1A
+#define SEPS525_MEMORY_ACCESS_POINTER_X 0x20
+#define SEPS525_MEMORY_ACCESS_POINTER_Y 0x21
+#define SEPS525_DDRAM_DATA_ACCESS_PORT 0x22
+#define SEPS525_GRAY_SCALE_TABLE_INDEX 0x50
+#define SEPS525_GRAY_SCALE_TABLE_DATA 0x51
+#define SEPS525_DUTY 0x28
+#define SEPS525_DSL 0x29
+#define SEPS525_D1_DDRAM_FAC 0x2E
+#define SEPS525_D1_DDRAM_FAR 0x2F
+#define SEPS525_D2_DDRAM_SAC 0x31
+#define SEPS525_D2_DDRAM_SAR 0x32
+#define SEPS525_SCR1_FX1 0x33
+#define SEPS525_SCR1_FX2 0x34
+#define SEPS525_SCR1_FY1 0x35
+#define SEPS525_SCR1_FY2 0x36
+#define SEPS525_SCR2_SX1 0x37
+#define SEPS525_SCR2_SX2 0x38
+#define SEPS525_SCR2_SY1 0x39
+#define SEPS525_SCR2_SY2 0x3A
+#define SEPS525_SCREEN_SAVER_CONTEROL 0x3B
+#define SEPS525_SS_SLEEP_TIMER 0x3C
+#define SEPS525_SCREEN_SAVER_MODE 0x3D
+#define SEPS525_SS_SCR1_FU 0x3E
+#define SEPS525_SS_SCR1_MXY 0x3F
+#define SEPS525_SS_SCR2_FU 0x40
+#define SEPS525_SS_SCR2_MXY 0x41
+#define SEPS525_MOVING_DIRECTION 0x42
+#define SEPS525_SS_SCR2_SX1 0x47
+#define SEPS525_SS_SCR2_SX2 0x48
+#define SEPS525_SS_SCR2_SY1 0x49
+#define SEPS525_SS_SCR2_SY2 0x4A
+
+/* SEPS525_DISPLAYMODE_SET */
+#define MODE_SWAP_BGR BIT(7)
+#define MODE_SM BIT(6)
+#define MODE_RD BIT(5)
+#define MODE_CD BIT(4)
+
+#define seps525_use_window 0 /* FBTFT doesn't really use it today */
+
+/* Init sequence taken from: Arduino Library for the Adafruit 2.2" display */
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ usleep_range(1000, 5000);
+
+ /* Disable Oscillator Power Down */
+ write_reg(par, SEPS525_REDUCE_CURRENT, 0x03);
+ usleep_range(1000, 5000);
+ /* Set Normal Driving Current */
+ write_reg(par, SEPS525_REDUCE_CURRENT, 0x00);
+ usleep_range(1000, 5000);
+
+ write_reg(par, SEPS525_SCREEN_SAVER_CONTEROL, 0x00);
+ /* Set EXPORT1 Pin at Internal Clock */
+ write_reg(par, SEPS525_OSC_CTL, 0x01);
+ /* Set Clock as 120 Frames/Sec */
+ write_reg(par, SEPS525_CLOCK_DIV, 0x90);
+ /* Set Reference Voltage Controlled by External Resister */
+ write_reg(par, SEPS525_IREF, 0x01);
+
+ /* precharge time R G B */
+ write_reg(par, SEPS525_PRECHARGE_TIME_R, 0x04);
+ write_reg(par, SEPS525_PRECHARGE_TIME_G, 0x05);
+ write_reg(par, SEPS525_PRECHARGE_TIME_B, 0x05);
+
+ /* precharge current R G B (uA) */
+ write_reg(par, SEPS525_PRECHARGE_CURRENT_R, 0x9D);
+ write_reg(par, SEPS525_PRECHARGE_CURRENT_G, 0x8C);
+ write_reg(par, SEPS525_PRECHARGE_CURRENT_B, 0x57);
+
+ /* driving current R G B (uA) */
+ write_reg(par, SEPS525_DRIVING_CURRENT_R, 0x56);
+ write_reg(par, SEPS525_DRIVING_CURRENT_G, 0x4D);
+ write_reg(par, SEPS525_DRIVING_CURRENT_B, 0x46);
+ /* Set Color Sequence */
+ write_reg(par, SEPS525_DISPLAYMODE_SET, 0xA0);
+ write_reg(par, SEPS525_RGBIF, 0x01); /* Set MCU Interface Mode */
+ /* Set Memory Write Mode */
+ write_reg(par, SEPS525_MEMORY_WRITEMODE, 0x66);
+ write_reg(par, SEPS525_DUTY, 0x7F); /* 1/128 Duty (0x0F~0x7F) */
+ /* Set Mapping RAM Display Start Line (0x00~0x7F) */
+ write_reg(par, SEPS525_DSL, 0x00);
+ write_reg(par, SEPS525_DISP_ONOFF, 0x01); /* Display On (0x00/0x01) */
+ /* Set All Internal Register Value as Normal Mode */
+ write_reg(par, SEPS525_SOFT_RST, 0x00);
+ /* Set RGB Interface Polarity as Active Low */
+ write_reg(par, SEPS525_RGB_POL, 0x00);
+
+ write_reg(par, SEPS525_DDRAM_DATA_ACCESS_PORT);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ if (seps525_use_window) {
+ /* Set Window Xs,Ys Xe,Ye*/
+ write_reg(par, SEPS525_MX1_ADDR, xs);
+ write_reg(par, SEPS525_MX2_ADDR, xe);
+ write_reg(par, SEPS525_MY1_ADDR, ys);
+ write_reg(par, SEPS525_MY2_ADDR, ye);
+ }
+ /* start position X,Y */
+ write_reg(par, SEPS525_MEMORY_ACCESS_POINTER_X, xs);
+ write_reg(par, SEPS525_MEMORY_ACCESS_POINTER_Y, ys);
+
+ write_reg(par, SEPS525_DDRAM_DATA_ACCESS_PORT);
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ u8 val;
+
+ switch (par->info->var.rotate) {
+ case 0:
+ val = 0;
+ break;
+ case 180:
+ val = MODE_RD | MODE_CD;
+ break;
+ case 90:
+ case 270:
+
+ default:
+ return -EINVAL;
+ }
+ /* Memory Access Control */
+ write_reg(par, SEPS525_DISPLAYMODE_SET, val |
+ (par->bgr ? MODE_SWAP_BGR : 0));
+
+ write_reg(par, SEPS525_DDRAM_DATA_ACCESS_PORT);
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "syncoam,seps525", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:seps525");
+MODULE_ALIAS("platform:seps525");
+
+MODULE_DESCRIPTION("FB driver for the SEPS525 LCD Controller");
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_sh1106.c b/drivers/staging/fbtft/fb_sh1106.c
new file mode 100644
index 000000000..9685ca516
--- /dev/null
+++ b/drivers/staging/fbtft/fb_sh1106.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the SH1106 OLED Controller
+ * Based on the SSD1306 driver by Noralf Tronnes
+ *
+ * Copyright (C) 2017 Heiner Kallweit
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_sh1106"
+#define WIDTH 128
+#define HEIGHT 64
+
+/* Init sequence based on the Adafruit SSD1306 Arduino library */
+static int init_display(struct fbtft_par *par)
+{
+ if (!par->info->var.xres || par->info->var.xres > WIDTH ||
+ !par->info->var.yres || par->info->var.yres > HEIGHT ||
+ par->info->var.yres % 8) {
+ dev_err(par->info->device, "Invalid screen size\n");
+ return -EINVAL;
+ }
+
+ if (par->info->var.rotate) {
+ dev_err(par->info->device, "Display rotation not supported\n");
+ return -EINVAL;
+ }
+
+ par->fbtftops.reset(par);
+
+ /* Set Display OFF */
+ write_reg(par, 0xAE);
+
+ /* Set Display Clock Divide Ratio/ Oscillator Frequency */
+ write_reg(par, 0xD5, 0x80);
+
+ /* Set Multiplex Ratio */
+ write_reg(par, 0xA8, par->info->var.yres - 1);
+
+ /* Set Display Offset */
+ write_reg(par, 0xD3, 0x00);
+
+ /* Set Display Start Line */
+ write_reg(par, 0x40 | 0x0);
+
+ /* Set Segment Re-map */
+ /* column address 127 is mapped to SEG0 */
+ write_reg(par, 0xA0 | 0x1);
+
+ /* Set COM Output Scan Direction */
+ /* remapped mode. Scan from COM[N-1] to COM0 */
+ write_reg(par, 0xC8);
+
+ /* Set COM Pins Hardware Configuration */
+ if (par->info->var.yres == 64)
+ /* A[4]=1b, Alternative COM pin configuration */
+ write_reg(par, 0xDA, 0x12);
+ else if (par->info->var.yres == 48)
+ /* A[4]=1b, Alternative COM pin configuration */
+ write_reg(par, 0xDA, 0x12);
+ else
+ /* A[4]=0b, Sequential COM pin configuration */
+ write_reg(par, 0xDA, 0x02);
+
+ /* Set Pre-charge Period */
+ write_reg(par, 0xD9, 0xF1);
+
+ /* Set VCOMH Deselect Level */
+ write_reg(par, 0xDB, 0x40);
+
+ /* Set Display ON */
+ write_reg(par, 0xAF);
+
+ msleep(150);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+}
+
+static int blank(struct fbtft_par *par, bool on)
+{
+ fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n",
+ __func__, on ? "true" : "false");
+
+ write_reg(par, on ? 0xAE : 0xAF);
+
+ return 0;
+}
+
+/* Gamma is used to control Contrast */
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ /* apply mask */
+ curves[0] &= 0xFF;
+
+ /* Set Contrast Control for BANK0 */
+ write_reg(par, 0x81, curves[0]);
+
+ return 0;
+}
+
+static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u16 *vmem16 = (u16 *)par->info->screen_buffer;
+ u32 xres = par->info->var.xres;
+ int page, page_start, page_end, x, i, ret;
+ u8 *buf = par->txbuf.buf;
+
+ /* offset refers to vmem with 2 bytes element size */
+ page_start = offset / (8 * 2 * xres);
+ page_end = DIV_ROUND_UP(offset + len, 8 * 2 * xres);
+
+ for (page = page_start; page < page_end; page++) {
+ /* set page and set column to 2 because of vidmem width 132 */
+ write_reg(par, 0xb0 | page, 0x00 | 2, 0x10 | 0);
+
+ memset(buf, 0, xres);
+ for (x = 0; x < xres; x++)
+ for (i = 0; i < 8; i++)
+ if (vmem16[(page * 8 + i) * xres + x])
+ buf[x] |= BIT(i);
+
+ /* Write data */
+ ret = fbtft_write_buf_dc(par, buf, xres, 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void write_register(struct fbtft_par *par, int len, ...)
+{
+ va_list args;
+ int i;
+
+ va_start(args, len);
+
+ for (i = 0; i < len; i++)
+ par->buf[i] = va_arg(args, unsigned int);
+
+ /* keep DC low for all command bytes to transfer */
+ fbtft_write_buf_dc(par, par->buf, len, 0);
+
+ va_end(args);
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .txbuflen = WIDTH,
+ .gamma_num = 1,
+ .gamma_len = 1,
+ /* set default contrast to 0xcd = 80% */
+ .gamma = "cd",
+ .fbtftops = {
+ .write_vmem = write_vmem,
+ .write_register = write_register,
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .blank = blank,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_SPI_DRIVER(DRVNAME, "sinowealth", "sh1106", &display);
+
+MODULE_DESCRIPTION("SH1106 OLED Driver");
+MODULE_AUTHOR("Heiner Kallweit");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ssd1289.c b/drivers/staging/fbtft/fb_ssd1289.c
new file mode 100644
index 000000000..f27bab38b
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ssd1289.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the SSD1289 LCD Controller
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ *
+ * Init sequence taken from ITDB02_Graph16.cpp - (C)2010-2011 Henning Karlsen
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ssd1289"
+#define WIDTH 240
+#define HEIGHT 320
+#define DEFAULT_GAMMA "02 03 2 5 7 7 4 2 4 2\n" \
+ "02 03 2 5 7 5 4 2 4 2"
+
+static unsigned int reg11 = 0x6040;
+module_param(reg11, uint, 0000);
+MODULE_PARM_DESC(reg11, "Register 11h value");
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ write_reg(par, 0x00, 0x0001);
+ write_reg(par, 0x03, 0xA8A4);
+ write_reg(par, 0x0C, 0x0000);
+ write_reg(par, 0x0D, 0x080C);
+ write_reg(par, 0x0E, 0x2B00);
+ write_reg(par, 0x1E, 0x00B7);
+ write_reg(par, 0x01,
+ BIT(13) | (par->bgr << 11) | BIT(9) | (HEIGHT - 1));
+ write_reg(par, 0x02, 0x0600);
+ write_reg(par, 0x10, 0x0000);
+ write_reg(par, 0x05, 0x0000);
+ write_reg(par, 0x06, 0x0000);
+ write_reg(par, 0x16, 0xEF1C);
+ write_reg(par, 0x17, 0x0003);
+ write_reg(par, 0x07, 0x0233);
+ write_reg(par, 0x0B, 0x0000);
+ write_reg(par, 0x0F, 0x0000);
+ write_reg(par, 0x41, 0x0000);
+ write_reg(par, 0x42, 0x0000);
+ write_reg(par, 0x48, 0x0000);
+ write_reg(par, 0x49, 0x013F);
+ write_reg(par, 0x4A, 0x0000);
+ write_reg(par, 0x4B, 0x0000);
+ write_reg(par, 0x44, 0xEF00);
+ write_reg(par, 0x45, 0x0000);
+ write_reg(par, 0x46, 0x013F);
+ write_reg(par, 0x23, 0x0000);
+ write_reg(par, 0x24, 0x0000);
+ write_reg(par, 0x25, 0x8000);
+ write_reg(par, 0x4f, 0x0000);
+ write_reg(par, 0x4e, 0x0000);
+ write_reg(par, 0x22);
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ switch (par->info->var.rotate) {
+ /* R4Eh - Set GDDRAM X address counter */
+ /* R4Fh - Set GDDRAM Y address counter */
+ case 0:
+ write_reg(par, 0x4e, xs);
+ write_reg(par, 0x4f, ys);
+ break;
+ case 180:
+ write_reg(par, 0x4e, par->info->var.xres - 1 - xs);
+ write_reg(par, 0x4f, par->info->var.yres - 1 - ys);
+ break;
+ case 270:
+ write_reg(par, 0x4e, par->info->var.yres - 1 - ys);
+ write_reg(par, 0x4f, xs);
+ break;
+ case 90:
+ write_reg(par, 0x4e, ys);
+ write_reg(par, 0x4f, par->info->var.xres - 1 - xs);
+ break;
+ }
+
+ /* R22h - RAM data write */
+ write_reg(par, 0x22);
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ if (par->fbtftops.init_display != init_display) {
+ /* don't risk messing up register 11h */
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
+ "%s: skipping since custom init_display() is used\n",
+ __func__);
+ return 0;
+ }
+
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, 0x11, reg11 | 0x30);
+ break;
+ case 270:
+ write_reg(par, 0x11, reg11 | 0x28);
+ break;
+ case 180:
+ write_reg(par, 0x11, reg11 | 0x00);
+ break;
+ case 90:
+ write_reg(par, 0x11, reg11 | 0x18);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Gamma string format:
+ * VRP0 VRP1 PRP0 PRP1 PKP0 PKP1 PKP2 PKP3 PKP4 PKP5
+ * VRN0 VRN1 PRN0 PRN1 PKN0 PKN1 PKN2 PKN3 PKN4 PKN5
+ */
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ static const unsigned long mask[] = {
+ 0x1f, 0x1f, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x1f, 0x1f, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ };
+ int i, j;
+
+ /* apply mask */
+ for (i = 0; i < 2; i++)
+ for (j = 0; j < 10; j++)
+ CURVE(i, j) &= mask[i * par->gamma.num_values + j];
+
+ write_reg(par, 0x0030, CURVE(0, 5) << 8 | CURVE(0, 4));
+ write_reg(par, 0x0031, CURVE(0, 7) << 8 | CURVE(0, 6));
+ write_reg(par, 0x0032, CURVE(0, 9) << 8 | CURVE(0, 8));
+ write_reg(par, 0x0033, CURVE(0, 3) << 8 | CURVE(0, 2));
+ write_reg(par, 0x0034, CURVE(1, 5) << 8 | CURVE(1, 4));
+ write_reg(par, 0x0035, CURVE(1, 7) << 8 | CURVE(1, 6));
+ write_reg(par, 0x0036, CURVE(1, 9) << 8 | CURVE(1, 8));
+ write_reg(par, 0x0037, CURVE(1, 3) << 8 | CURVE(1, 2));
+ write_reg(par, 0x003A, CURVE(0, 1) << 8 | CURVE(0, 0));
+ write_reg(par, 0x003B, CURVE(1, 1) << 8 | CURVE(1, 0));
+
+ return 0;
+}
+
+#undef CURVE
+
+static struct fbtft_display display = {
+ .regwidth = 16,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .gamma_num = 2,
+ .gamma_len = 10,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1289", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ssd1289");
+MODULE_ALIAS("platform:ssd1289");
+
+MODULE_DESCRIPTION("FB driver for the SSD1289 LCD Controller");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ssd1305.c b/drivers/staging/fbtft/fb_ssd1305.c
new file mode 100644
index 000000000..020fe48fe
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ssd1305.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the SSD1305 OLED Controller
+ *
+ * based on SSD1306 driver by Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ssd1305"
+
+#define WIDTH 128
+#define HEIGHT 64
+
+/*
+ * write_reg() caveat:
+ *
+ * This doesn't work because D/C has to be LOW for both values:
+ * write_reg(par, val1, val2);
+ *
+ * Do it like this:
+ * write_reg(par, val1);
+ * write_reg(par, val2);
+ */
+
+/* Init sequence taken from the Adafruit SSD1306 Arduino library */
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ if (par->gamma.curves[0] == 0) {
+ mutex_lock(&par->gamma.lock);
+ if (par->info->var.yres == 64)
+ par->gamma.curves[0] = 0xCF;
+ else
+ par->gamma.curves[0] = 0x8F;
+ mutex_unlock(&par->gamma.lock);
+ }
+
+ /* Set Display OFF */
+ write_reg(par, 0xAE);
+
+ /* Set Display Clock Divide Ratio/ Oscillator Frequency */
+ write_reg(par, 0xD5);
+ write_reg(par, 0x80);
+
+ /* Set Multiplex Ratio */
+ write_reg(par, 0xA8);
+ if (par->info->var.yres == 64)
+ write_reg(par, 0x3F);
+ else
+ write_reg(par, 0x1F);
+
+ /* Set Display Offset */
+ write_reg(par, 0xD3);
+ write_reg(par, 0x0);
+
+ /* Set Display Start Line */
+ write_reg(par, 0x40 | 0x0);
+
+ /* Charge Pump Setting */
+ write_reg(par, 0x8D);
+ /* A[2] = 1b, Enable charge pump during display on */
+ write_reg(par, 0x14);
+
+ /* Set Memory Addressing Mode */
+ write_reg(par, 0x20);
+ /* Vertical addressing mode */
+ write_reg(par, 0x01);
+
+ /*
+ * Set Segment Re-map
+ * column address 127 is mapped to SEG0
+ */
+ write_reg(par, 0xA0 | ((par->info->var.rotate == 180) ? 0x0 : 0x1));
+
+ /*
+ * Set COM Output Scan Direction
+ * remapped mode. Scan from COM[N-1] to COM0
+ */
+ write_reg(par, ((par->info->var.rotate == 180) ? 0xC8 : 0xC0));
+
+ /* Set COM Pins Hardware Configuration */
+ write_reg(par, 0xDA);
+ if (par->info->var.yres == 64) {
+ /* A[4]=1b, Alternative COM pin configuration */
+ write_reg(par, 0x12);
+ } else {
+ /* A[4]=0b, Sequential COM pin configuration */
+ write_reg(par, 0x02);
+ }
+
+ /* Set Pre-charge Period */
+ write_reg(par, 0xD9);
+ write_reg(par, 0xF1);
+
+ /*
+ * Entire Display ON
+ * Resume to RAM content display. Output follows RAM content
+ */
+ write_reg(par, 0xA4);
+
+ /*
+ * Set Normal Display
+ * 0 in RAM: OFF in display panel
+ * 1 in RAM: ON in display panel
+ */
+ write_reg(par, 0xA6);
+
+ /* Set Display ON */
+ write_reg(par, 0xAF);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ /* Set Lower Column Start Address for Page Addressing Mode */
+ write_reg(par, 0x00 | ((par->info->var.rotate == 180) ? 0x0 : 0x4));
+ /* Set Higher Column Start Address for Page Addressing Mode */
+ write_reg(par, 0x10 | 0x0);
+ /* Set Display Start Line */
+ write_reg(par, 0x40 | 0x0);
+}
+
+static int blank(struct fbtft_par *par, bool on)
+{
+ if (on)
+ write_reg(par, 0xAE);
+ else
+ write_reg(par, 0xAF);
+ return 0;
+}
+
+/* Gamma is used to control Contrast */
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ curves[0] &= 0xFF;
+ /* Set Contrast Control for BANK0 */
+ write_reg(par, 0x81);
+ write_reg(par, curves[0]);
+
+ return 0;
+}
+
+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, i;
+ int ret;
+
+ for (x = 0; x < par->info->var.xres; x++) {
+ for (y = 0; y < par->info->var.yres / 8; y++) {
+ *buf = 0x00;
+ for (i = 0; i < 8; i++)
+ *buf |= (vmem16[(y * 8 + i) *
+ par->info->var.xres + x] ?
+ 1 : 0) << i;
+ buf++;
+ }
+ }
+
+ /* Write data */
+ gpiod_set_value(par->gpio.dc, 1);
+ ret = par->fbtftops.write(par, par->txbuf.buf,
+ par->info->var.xres * par->info->var.yres /
+ 8);
+ if (ret < 0)
+ dev_err(par->info->device, "write failed and returned: %d\n",
+ ret);
+ return ret;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .txbuflen = WIDTH * HEIGHT / 8,
+ .gamma_num = 1,
+ .gamma_len = 1,
+ .gamma = "00",
+ .fbtftops = {
+ .write_vmem = write_vmem,
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .blank = blank,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1305", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ssd1305");
+MODULE_ALIAS("platform:ssd1305");
+
+MODULE_DESCRIPTION("SSD1305 OLED Driver");
+MODULE_AUTHOR("Alexey Mednyy");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ssd1306.c b/drivers/staging/fbtft/fb_ssd1306.c
new file mode 100644
index 000000000..6cf9df579
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ssd1306.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the SSD1306 OLED Controller
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ssd1306"
+#define WIDTH 128
+#define HEIGHT 64
+
+/*
+ * write_reg() caveat:
+ *
+ * This doesn't work because D/C has to be LOW for both values:
+ * write_reg(par, val1, val2);
+ *
+ * Do it like this:
+ * write_reg(par, val1);
+ * write_reg(par, val2);
+ */
+
+/* Init sequence taken from the Adafruit SSD1306 Arduino library */
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ if (par->gamma.curves[0] == 0) {
+ mutex_lock(&par->gamma.lock);
+ if (par->info->var.yres == 64)
+ par->gamma.curves[0] = 0xCF;
+ else
+ par->gamma.curves[0] = 0x8F;
+ mutex_unlock(&par->gamma.lock);
+ }
+
+ /* Set Display OFF */
+ write_reg(par, 0xAE);
+
+ /* Set Display Clock Divide Ratio/ Oscillator Frequency */
+ write_reg(par, 0xD5);
+ write_reg(par, 0x80);
+
+ /* Set Multiplex Ratio */
+ write_reg(par, 0xA8);
+ if (par->info->var.yres == 64)
+ write_reg(par, 0x3F);
+ else if (par->info->var.yres == 48)
+ write_reg(par, 0x2F);
+ else
+ write_reg(par, 0x1F);
+
+ /* Set Display Offset */
+ write_reg(par, 0xD3);
+ write_reg(par, 0x0);
+
+ /* Set Display Start Line */
+ write_reg(par, 0x40 | 0x0);
+
+ /* Charge Pump Setting */
+ write_reg(par, 0x8D);
+ /* A[2] = 1b, Enable charge pump during display on */
+ write_reg(par, 0x14);
+
+ /* Set Memory Addressing Mode */
+ write_reg(par, 0x20);
+ /* Vertical addressing mode */
+ write_reg(par, 0x01);
+
+ /* Set Segment Re-map */
+ /* column address 127 is mapped to SEG0 */
+ write_reg(par, 0xA0 | 0x1);
+
+ /* Set COM Output Scan Direction */
+ /* remapped mode. Scan from COM[N-1] to COM0 */
+ write_reg(par, 0xC8);
+
+ /* Set COM Pins Hardware Configuration */
+ write_reg(par, 0xDA);
+ if (par->info->var.yres == 64)
+ /* A[4]=1b, Alternative COM pin configuration */
+ write_reg(par, 0x12);
+ else if (par->info->var.yres == 48)
+ /* A[4]=1b, Alternative COM pin configuration */
+ write_reg(par, 0x12);
+ else
+ /* A[4]=0b, Sequential COM pin configuration */
+ write_reg(par, 0x02);
+
+ /* Set Pre-charge Period */
+ write_reg(par, 0xD9);
+ write_reg(par, 0xF1);
+
+ /* Set VCOMH Deselect Level */
+ write_reg(par, 0xDB);
+ /* according to the datasheet, this value is out of bounds */
+ write_reg(par, 0x40);
+
+ /* Entire Display ON */
+ /* Resume to RAM content display. Output follows RAM content */
+ write_reg(par, 0xA4);
+
+ /* Set Normal Display
+ * 0 in RAM: OFF in display panel
+ * 1 in RAM: ON in display panel
+ */
+ write_reg(par, 0xA6);
+
+ /* Set Display ON */
+ write_reg(par, 0xAF);
+
+ return 0;
+}
+
+static void set_addr_win_64x48(struct fbtft_par *par)
+{
+ /* Set Column Address */
+ write_reg(par, 0x21);
+ write_reg(par, 0x20);
+ write_reg(par, 0x5F);
+
+ /* Set Page Address */
+ write_reg(par, 0x22);
+ write_reg(par, 0x0);
+ write_reg(par, 0x5);
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ /* Set Lower Column Start Address for Page Addressing Mode */
+ write_reg(par, 0x00 | 0x0);
+ /* Set Higher Column Start Address for Page Addressing Mode */
+ write_reg(par, 0x10 | 0x0);
+ /* Set Display Start Line */
+ write_reg(par, 0x40 | 0x0);
+
+ if (par->info->var.xres == 64 && par->info->var.yres == 48)
+ set_addr_win_64x48(par);
+}
+
+static int blank(struct fbtft_par *par, bool on)
+{
+ fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n",
+ __func__, on ? "true" : "false");
+
+ if (on)
+ write_reg(par, 0xAE);
+ else
+ write_reg(par, 0xAF);
+ return 0;
+}
+
+/* Gamma is used to control Contrast */
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ /* apply mask */
+ curves[0] &= 0xFF;
+
+ /* Set Contrast Control for BANK0 */
+ write_reg(par, 0x81);
+ write_reg(par, curves[0]);
+
+ return 0;
+}
+
+static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u16 *vmem16 = (u16 *)par->info->screen_buffer;
+ u32 xres = par->info->var.xres;
+ u32 yres = par->info->var.yres;
+ u8 *buf = par->txbuf.buf;
+ int x, y, i;
+ int ret = 0;
+
+ for (x = 0; x < xres; x++) {
+ for (y = 0; y < yres / 8; y++) {
+ *buf = 0x00;
+ for (i = 0; i < 8; i++)
+ if (vmem16[(y * 8 + i) * xres + x])
+ *buf |= BIT(i);
+ buf++;
+ }
+ }
+
+ /* Write data */
+ gpiod_set_value(par->gpio.dc, 1);
+ ret = par->fbtftops.write(par, par->txbuf.buf, xres * yres / 8);
+ if (ret < 0)
+ dev_err(par->info->device, "write failed and returned: %d\n",
+ ret);
+
+ return ret;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .gamma_num = 1,
+ .gamma_len = 1,
+ .gamma = "00",
+ .fbtftops = {
+ .write_vmem = write_vmem,
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .blank = blank,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1306", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ssd1306");
+MODULE_ALIAS("platform:ssd1306");
+
+MODULE_DESCRIPTION("SSD1306 OLED Driver");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ssd1325.c b/drivers/staging/fbtft/fb_ssd1325.c
new file mode 100644
index 000000000..796a2ac3e
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ssd1325.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the SSD1325 OLED Controller
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ssd1325"
+
+#define WIDTH 128
+#define HEIGHT 64
+#define GAMMA_NUM 1
+#define GAMMA_LEN 15
+#define DEFAULT_GAMMA "7 1 1 1 1 2 2 3 3 4 4 5 5 6 6"
+
+/*
+ * write_reg() caveat:
+ *
+ * This doesn't work because D/C has to be LOW for both values:
+ * write_reg(par, val1, val2);
+ *
+ * Do it like this:
+ * write_reg(par, val1);
+ * write_reg(par, val2);
+ */
+
+/* Init sequence taken from the Adafruit SSD1306 Arduino library */
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ write_reg(par, 0xb3);
+ write_reg(par, 0xf0);
+ write_reg(par, 0xae);
+ write_reg(par, 0xa1);
+ write_reg(par, 0x00);
+ write_reg(par, 0xa8);
+ write_reg(par, 0x3f);
+ write_reg(par, 0xa0);
+ write_reg(par, 0x45);
+ write_reg(par, 0xa2);
+ write_reg(par, 0x40);
+ write_reg(par, 0x75);
+ write_reg(par, 0x00);
+ write_reg(par, 0x3f);
+ write_reg(par, 0x15);
+ write_reg(par, 0x00);
+ write_reg(par, 0x7f);
+ write_reg(par, 0xa4);
+ write_reg(par, 0xaf);
+
+ return 0;
+}
+
+static uint8_t rgb565_to_g16(u16 pixel)
+{
+ 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) / 195;
+ if (pixel > 255)
+ pixel = 255;
+ return (uint8_t)pixel / 16;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par,
+ "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe,
+ ye);
+
+ write_reg(par, 0x75);
+ write_reg(par, 0x00);
+ write_reg(par, 0x3f);
+ write_reg(par, 0x15);
+ write_reg(par, 0x00);
+ write_reg(par, 0x7f);
+}
+
+static int blank(struct fbtft_par *par, bool on)
+{
+ fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n",
+ __func__, on ? "true" : "false");
+
+ if (on)
+ write_reg(par, 0xAE);
+ else
+ write_reg(par, 0xAF);
+ return 0;
+}
+
+/*
+ * Grayscale Lookup Table
+ * GS1 - GS15
+ * The "Gamma curve" contains the relative values between the entries
+ * in the Lookup table.
+ *
+ * 0 = Setting of GS1 < Setting of GS2 < Setting of GS3.....<
+ * Setting of GS14 < Setting of GS15
+ */
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ int i;
+
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__);
+
+ for (i = 0; i < GAMMA_LEN; i++) {
+ if (i > 0 && curves[i] < 1) {
+ dev_err(par->info->device,
+ "Illegal value in Grayscale Lookup Table at index %d.\n"
+ "Must be greater than 0\n", i);
+ return -EINVAL;
+ }
+ if (curves[i] > 7) {
+ dev_err(par->info->device,
+ "Illegal value(s) in Grayscale Lookup Table.\n"
+ "At index=%d, the accumulated value has exceeded 7\n",
+ i);
+ return -EINVAL;
+ }
+ }
+ write_reg(par, 0xB8);
+ for (i = 0; i < 8; i++)
+ write_reg(par, (curves[i] & 0xFF));
+ return 0;
+}
+
+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;
+ u8 n1;
+ u8 n2;
+ int y, x;
+ int ret;
+
+ for (x = 0; x < par->info->var.xres; x++) {
+ if (x % 2)
+ continue;
+ for (y = 0; y < par->info->var.yres; y++) {
+ n1 = rgb565_to_g16(vmem16[y * par->info->var.xres + x]);
+ n2 = rgb565_to_g16(vmem16
+ [y * par->info->var.xres + x + 1]);
+ *buf = (n1 << 4) | n2;
+ buf++;
+ }
+ }
+
+ gpiod_set_value(par->gpio.dc, 1);
+
+ /* Write data */
+ ret = par->fbtftops.write(par, par->txbuf.buf,
+ par->info->var.xres * par->info->var.yres / 2);
+ if (ret < 0)
+ dev_err(par->info->device,
+ "%s: write failed and returned: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .txbuflen = WIDTH * HEIGHT / 2,
+ .gamma_num = GAMMA_NUM,
+ .gamma_len = GAMMA_LEN,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .write_vmem = write_vmem,
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .blank = blank,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1325", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ssd1325");
+MODULE_ALIAS("platform:ssd1325");
+
+MODULE_DESCRIPTION("SSD1325 OLED Driver");
+MODULE_AUTHOR("Alexey Mednyy");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ssd1331.c b/drivers/staging/fbtft/fb_ssd1331.c
new file mode 100644
index 000000000..ec5eced7f
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ssd1331.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ssd1331"
+#define WIDTH 96
+#define HEIGHT 64
+#define GAMMA_NUM 1
+#define GAMMA_LEN 63
+#define DEFAULT_GAMMA "0 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2" \
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ write_reg(par, 0xae); /* Display Off */
+
+ /* Set Column Address Mapping, COM Scan Direction and Colour Depth */
+ if (par->info->var.rotate == 180)
+ write_reg(par, 0xa0, 0x60 | (par->bgr << 2));
+ else
+ write_reg(par, 0xa0, 0x72 | (par->bgr << 2));
+
+ write_reg(par, 0x72); /* RGB colour */
+ write_reg(par, 0xa1, 0x00); /* Set Display Start Line */
+ write_reg(par, 0xa2, 0x00); /* Set Display Offset */
+ write_reg(par, 0xa4); /* NORMALDISPLAY */
+ write_reg(par, 0xa8, 0x3f); /* Set multiplex */
+ write_reg(par, 0xad, 0x8e); /* Set master */
+ /* write_reg(par, 0xb0, 0x0b); Set power mode */
+ write_reg(par, 0xb1, 0x31); /* Precharge */
+ write_reg(par, 0xb3, 0xf0); /* Clock div */
+ write_reg(par, 0x8a, 0x64); /* Precharge A */
+ write_reg(par, 0x8b, 0x78); /* Precharge B */
+ write_reg(par, 0x8c, 0x64); /* Precharge C */
+ write_reg(par, 0xbb, 0x3a); /* Precharge level */
+ write_reg(par, 0xbe, 0x3e); /* vcomh */
+ write_reg(par, 0x87, 0x06); /* Master current */
+ write_reg(par, 0x81, 0x91); /* Contrast A */
+ write_reg(par, 0x82, 0x50); /* Contrast B */
+ write_reg(par, 0x83, 0x7d); /* Contrast C */
+ write_reg(par, 0xaf); /* Set Sleep Mode Display On */
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, 0x15, xs, xe);
+ write_reg(par, 0x75, ys, ye);
+}
+
+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);
+ gpiod_set_value(par->gpio.dc, 0);
+ ret = par->fbtftops.write(par, par->buf, sizeof(u8));
+ if (ret < 0) {
+ va_end(args);
+ dev_err(par->info->device,
+ "write() failed and returned %d\n", ret);
+ return;
+ }
+ 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;
+ }
+ }
+ gpiod_set_value(par->gpio.dc, 1);
+ va_end(args);
+}
+
+/*
+ * Grayscale Lookup Table
+ * GS1 - GS63
+ * The driver Gamma curve contains the relative values between the entries
+ * in the Lookup table.
+ *
+ * From datasheet:
+ * 8.8 Gray Scale Decoder
+ *
+ * there are total 180 Gamma Settings (Setting 0 to Setting 180)
+ * available for the Gray Scale table.
+ *
+ * The gray scale is defined in incremental way, with reference
+ * to the length of previous table entry:
+ * Setting of GS1 has to be >= 0
+ * Setting of GS2 has to be > Setting of GS1 +1
+ * Setting of GS3 has to be > Setting of GS2 +1
+ * :
+ * Setting of GS63 has to be > Setting of GS62 +1
+ *
+ */
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ unsigned long tmp[GAMMA_NUM * GAMMA_LEN];
+ int i, acc = 0;
+
+ for (i = 0; i < 63; i++) {
+ if (i > 0 && curves[i] < 2) {
+ dev_err(par->info->device,
+ "Illegal value in Grayscale Lookup Table at index %d. Must be greater than 1\n",
+ i);
+ return -EINVAL;
+ }
+ acc += curves[i];
+ tmp[i] = acc;
+ if (acc > 180) {
+ dev_err(par->info->device,
+ "Illegal value(s) in Grayscale Lookup Table. At index=%d, the accumulated value has exceeded 180\n",
+ i);
+ return -EINVAL;
+ }
+ }
+
+ write_reg(par, 0xB8,
+ tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6],
+ tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13],
+ tmp[14], tmp[15], tmp[16], tmp[17], tmp[18], tmp[19], tmp[20],
+ tmp[21], tmp[22], tmp[23], tmp[24], tmp[25], tmp[26], tmp[27],
+ tmp[28], tmp[29], tmp[30], tmp[31], tmp[32], tmp[33], tmp[34],
+ tmp[35], tmp[36], tmp[37], tmp[38], tmp[39], tmp[40], tmp[41],
+ tmp[42], tmp[43], tmp[44], tmp[45], tmp[46], tmp[47], tmp[48],
+ tmp[49], tmp[50], tmp[51], tmp[52], tmp[53], tmp[54], tmp[55],
+ tmp[56], tmp[57], tmp[58], tmp[59], tmp[60], tmp[61],
+ tmp[62]);
+
+ return 0;
+}
+
+static int blank(struct fbtft_par *par, bool on)
+{
+ fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n",
+ __func__, on ? "true" : "false");
+ if (on)
+ write_reg(par, 0xAE);
+ else
+ write_reg(par, 0xAF);
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .gamma_num = GAMMA_NUM,
+ .gamma_len = GAMMA_LEN,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .write_register = write_reg8_bus8,
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_gamma = set_gamma,
+ .blank = blank,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1331", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ssd1331");
+MODULE_ALIAS("platform:ssd1331");
+
+MODULE_DESCRIPTION("SSD1331 OLED Driver");
+MODULE_AUTHOR("Alec Smecher (adapted from SSD1351 by James Davies)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_ssd1351.c b/drivers/staging/fbtft/fb_ssd1351.c
new file mode 100644
index 000000000..b8d55aa8c
--- /dev/null
+++ b/drivers/staging/fbtft/fb_ssd1351.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_ssd1351"
+#define WIDTH 128
+#define HEIGHT 128
+#define GAMMA_NUM 1
+#define GAMMA_LEN 63
+#define DEFAULT_GAMMA "0 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2 2 " \
+ "2 2 2 2 2 2 2" \
+
+static void register_onboard_backlight(struct fbtft_par *par);
+
+static int init_display(struct fbtft_par *par)
+{
+ if (par->pdata &&
+ par->pdata->display.backlight == FBTFT_ONBOARD_BACKLIGHT) {
+ /* module uses onboard GPIO for panel power */
+ par->fbtftops.register_backlight = register_onboard_backlight;
+ }
+
+ par->fbtftops.reset(par);
+
+ write_reg(par, 0xfd, 0x12); /* Command Lock */
+ write_reg(par, 0xfd, 0xb1); /* Command Lock */
+ write_reg(par, 0xae); /* Display Off */
+ write_reg(par, 0xb3, 0xf1); /* Front Clock Div */
+ write_reg(par, 0xca, 0x7f); /* Set Mux Ratio */
+ write_reg(par, 0x15, 0x00, 0x7f); /* Set Column Address */
+ write_reg(par, 0x75, 0x00, 0x7f); /* Set Row Address */
+ write_reg(par, 0xa1, 0x00); /* Set Display Start Line */
+ write_reg(par, 0xa2, 0x00); /* Set Display Offset */
+ write_reg(par, 0xb5, 0x00); /* Set GPIO */
+ write_reg(par, 0xab, 0x01); /* Set Function Selection */
+ write_reg(par, 0xb1, 0x32); /* Set Phase Length */
+ write_reg(par, 0xb4, 0xa0, 0xb5, 0x55); /* Set Segment Low Voltage */
+ write_reg(par, 0xbb, 0x17); /* Set Precharge Voltage */
+ write_reg(par, 0xbe, 0x05); /* Set VComH Voltage */
+ write_reg(par, 0xc1, 0xc8, 0x80, 0xc8); /* Set Contrast */
+ write_reg(par, 0xc7, 0x0f); /* Set Master Contrast */
+ write_reg(par, 0xb6, 0x01); /* Set Second Precharge Period */
+ write_reg(par, 0xa6); /* Set Display Mode Reset */
+ write_reg(par, 0xaf); /* Set Sleep Mode Display On */
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, 0x15, xs, xe);
+ write_reg(par, 0x75, ys, ye);
+ write_reg(par, 0x5c);
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ unsigned int remap;
+
+ if (par->fbtftops.init_display != init_display) {
+ /* don't risk messing up register A0h */
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
+ "%s: skipping since custom init_display() is used\n",
+ __func__);
+ return 0;
+ }
+
+ remap = 0x60 | (par->bgr << 2); /* Set Colour Depth */
+
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, 0xA0, remap | 0x00 | BIT(4));
+ break;
+ case 270:
+ write_reg(par, 0xA0, remap | 0x03 | BIT(4));
+ break;
+ case 180:
+ write_reg(par, 0xA0, remap | 0x02);
+ break;
+ case 90:
+ write_reg(par, 0xA0, remap | 0x01);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Grayscale Lookup Table
+ * GS1 - GS63
+ * The driver Gamma curve contains the relative values between the entries
+ * in the Lookup table.
+ *
+ * From datasheet:
+ * 8.8 Gray Scale Decoder
+ *
+ * there are total 180 Gamma Settings (Setting 0 to Setting 180)
+ * available for the Gray Scale table.
+ *
+ * The gray scale is defined in incremental way, with reference
+ * to the length of previous table entry:
+ * Setting of GS1 has to be >= 0
+ * Setting of GS2 has to be > Setting of GS1 +1
+ * Setting of GS3 has to be > Setting of GS2 +1
+ * :
+ * Setting of GS63 has to be > Setting of GS62 +1
+ *
+ */
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ unsigned long tmp[GAMMA_NUM * GAMMA_LEN];
+ int i, acc = 0;
+
+ for (i = 0; i < 63; i++) {
+ if (i > 0 && curves[i] < 2) {
+ dev_err(par->info->device,
+ "Illegal value in Grayscale Lookup Table at index %d : %d. Must be greater than 1\n",
+ i, curves[i]);
+ return -EINVAL;
+ }
+ acc += curves[i];
+ tmp[i] = acc;
+ if (acc > 180) {
+ dev_err(par->info->device,
+ "Illegal value(s) in Grayscale Lookup Table. At index=%d : %d, the accumulated value has exceeded 180\n",
+ i, acc);
+ return -EINVAL;
+ }
+ }
+
+ write_reg(par, 0xB8,
+ tmp[0], tmp[1], tmp[2], tmp[3],
+ tmp[4], tmp[5], tmp[6], tmp[7],
+ tmp[8], tmp[9], tmp[10], tmp[11],
+ tmp[12], tmp[13], tmp[14], tmp[15],
+ tmp[16], tmp[17], tmp[18], tmp[19],
+ tmp[20], tmp[21], tmp[22], tmp[23],
+ tmp[24], tmp[25], tmp[26], tmp[27],
+ tmp[28], tmp[29], tmp[30], tmp[31],
+ tmp[32], tmp[33], tmp[34], tmp[35],
+ tmp[36], tmp[37], tmp[38], tmp[39],
+ tmp[40], tmp[41], tmp[42], tmp[43],
+ tmp[44], tmp[45], tmp[46], tmp[47],
+ tmp[48], tmp[49], tmp[50], tmp[51],
+ tmp[52], tmp[53], tmp[54], tmp[55],
+ tmp[56], tmp[57], tmp[58], tmp[59],
+ tmp[60], tmp[61], tmp[62]);
+
+ return 0;
+}
+
+static int blank(struct fbtft_par *par, bool on)
+{
+ fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n",
+ __func__, on ? "true" : "false");
+ if (on)
+ write_reg(par, 0xAE);
+ else
+ write_reg(par, 0xAF);
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .gamma_num = GAMMA_NUM,
+ .gamma_len = GAMMA_LEN,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ .blank = blank,
+ },
+};
+
+static int update_onboard_backlight(struct backlight_device *bd)
+{
+ struct fbtft_par *par = bl_get_data(bd);
+ bool on;
+
+ fbtft_par_dbg(DEBUG_BACKLIGHT, par,
+ "%s: power=%d, fb_blank=%d\n",
+ __func__, bd->props.power, bd->props.fb_blank);
+
+ on = !backlight_is_blank(bd);
+ /* Onboard backlight connected to GPIO0 on SSD1351, GPIO1 unused */
+ write_reg(par, 0xB5, on ? 0x03 : 0x02);
+
+ return 0;
+}
+
+static const struct backlight_ops bl_ops = {
+ .update_status = update_onboard_backlight,
+};
+
+static void register_onboard_backlight(struct fbtft_par *par)
+{
+ struct backlight_device *bd;
+ struct backlight_properties bl_props = { 0, };
+
+ bl_props.type = BACKLIGHT_RAW;
+ bl_props.power = FB_BLANK_POWERDOWN;
+
+ bd = backlight_device_register(dev_driver_string(par->info->device),
+ par->info->device, par, &bl_ops,
+ &bl_props);
+ if (IS_ERR(bd)) {
+ dev_err(par->info->device,
+ "cannot register backlight device (%ld)\n",
+ PTR_ERR(bd));
+ return;
+ }
+ par->info->bl_dev = bd;
+
+ if (!par->fbtftops.unregister_backlight)
+ par->fbtftops.unregister_backlight = fbtft_unregister_backlight;
+}
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1351", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:ssd1351");
+MODULE_ALIAS("platform:ssd1351");
+
+MODULE_DESCRIPTION("SSD1351 OLED Driver");
+MODULE_AUTHOR("James Davies");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_st7735r.c b/drivers/staging/fbtft/fb_st7735r.c
new file mode 100644
index 000000000..9670a8989
--- /dev/null
+++ b/drivers/staging/fbtft/fb_st7735r.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ST7735R LCD Controller
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_st7735r"
+#define DEFAULT_GAMMA "0F 1A 0F 18 2F 28 20 22 1F 1B 23 37 00 07 02 10\n" \
+ "0F 1B 0F 17 33 2C 29 2E 30 30 39 3F 00 07 03 10"
+
+static const s16 default_init_sequence[] = {
+ -1, MIPI_DCS_SOFT_RESET,
+ -2, 150, /* delay */
+
+ -1, MIPI_DCS_EXIT_SLEEP_MODE,
+ -2, 500, /* delay */
+
+ /* FRMCTR1 - frame rate control: normal mode
+ * frame rate = fosc / (1 x 2 + 40) * (LINE + 2C + 2D)
+ */
+ -1, 0xB1, 0x01, 0x2C, 0x2D,
+
+ /* FRMCTR2 - frame rate control: idle mode
+ * frame rate = fosc / (1 x 2 + 40) * (LINE + 2C + 2D)
+ */
+ -1, 0xB2, 0x01, 0x2C, 0x2D,
+
+ /* FRMCTR3 - frame rate control - partial mode
+ * dot inversion mode, line inversion mode
+ */
+ -1, 0xB3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D,
+
+ /* INVCTR - display inversion control
+ * no inversion
+ */
+ -1, 0xB4, 0x07,
+
+ /* PWCTR1 - Power Control
+ * -4.6V, AUTO mode
+ */
+ -1, 0xC0, 0xA2, 0x02, 0x84,
+
+ /* PWCTR2 - Power Control
+ * VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
+ */
+ -1, 0xC1, 0xC5,
+
+ /* PWCTR3 - Power Control
+ * Opamp current small, Boost frequency
+ */
+ -1, 0xC2, 0x0A, 0x00,
+
+ /* PWCTR4 - Power Control
+ * BCLK/2, Opamp current small & Medium low
+ */
+ -1, 0xC3, 0x8A, 0x2A,
+
+ /* PWCTR5 - Power Control */
+ -1, 0xC4, 0x8A, 0xEE,
+
+ /* VMCTR1 - Power Control */
+ -1, 0xC5, 0x0E,
+
+ -1, MIPI_DCS_EXIT_INVERT_MODE,
+
+ -1, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT,
+
+ -1, MIPI_DCS_SET_DISPLAY_ON,
+ -2, 100, /* delay */
+
+ -1, MIPI_DCS_ENTER_NORMAL_MODE,
+ -2, 10, /* delay */
+
+ /* end marker */
+ -3
+};
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF);
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+#define MY BIT(7)
+#define MX BIT(6)
+#define MV BIT(5)
+static int set_var(struct fbtft_par *par)
+{
+ /* MADCTL - Memory data access control
+ * RGB/BGR:
+ * 1. Mode selection pin SRGB
+ * RGB H/W pin for color filter setting: 0=RGB, 1=BGR
+ * 2. MADCTL RGB bit
+ * RGB-BGR ORDER color filter panel: 0=RGB, 1=BGR
+ */
+ switch (par->info->var.rotate) {
+ case 0:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MX | MY | (par->bgr << 3));
+ break;
+ case 270:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MY | MV | (par->bgr << 3));
+ break;
+ case 180:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ par->bgr << 3);
+ break;
+ case 90:
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
+ MX | MV | (par->bgr << 3));
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Gamma string format:
+ * VRF0P VOS0P PK0P PK1P PK2P PK3P PK4P PK5P PK6P PK7P PK8P PK9P SELV0P SELV1P SELV62P SELV63P
+ * VRF0N VOS0N PK0N PK1N PK2N PK3N PK4N PK5N PK6N PK7N PK8N PK9N SELV0N SELV1N SELV62N SELV63N
+ */
+#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ int i, j;
+
+ /* apply mask */
+ for (i = 0; i < par->gamma.num_curves; i++)
+ for (j = 0; j < par->gamma.num_values; j++)
+ CURVE(i, j) &= 0x3f;
+
+ for (i = 0; i < par->gamma.num_curves; i++)
+ write_reg(par, 0xE0 + i,
+ CURVE(i, 0), CURVE(i, 1),
+ CURVE(i, 2), CURVE(i, 3),
+ CURVE(i, 4), CURVE(i, 5),
+ CURVE(i, 6), CURVE(i, 7),
+ CURVE(i, 8), CURVE(i, 9),
+ CURVE(i, 10), CURVE(i, 11),
+ CURVE(i, 12), CURVE(i, 13),
+ CURVE(i, 14), CURVE(i, 15));
+
+ return 0;
+}
+
+#undef CURVE
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = 128,
+ .height = 160,
+ .init_sequence = default_init_sequence,
+ .gamma_num = 2,
+ .gamma_len = 16,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7735r", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:st7735r");
+MODULE_ALIAS("platform:st7735r");
+
+MODULE_DESCRIPTION("FB driver for the ST7735R LCD Controller");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_st7789v.c b/drivers/staging/fbtft/fb_st7789v.c
new file mode 100644
index 000000000..861a15414
--- /dev/null
+++ b/drivers/staging/fbtft/fb_st7789v.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the ST7789V LCD Controller
+ *
+ * Copyright (C) 2015 Dennis Menschel
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/module.h>
+
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_st7789v"
+
+#define DEFAULT_GAMMA \
+ "70 2C 2E 15 10 09 48 33 53 0B 19 18 20 25\n" \
+ "70 2C 2E 15 10 09 48 33 53 0B 19 18 20 25"
+
+#define HSD20_IPS_GAMMA \
+ "D0 05 0A 09 08 05 2E 44 45 0F 17 16 2B 33\n" \
+ "D0 05 0A 09 08 05 2E 43 45 0F 16 16 2B 33"
+
+#define HSD20_IPS 1
+
+/**
+ * enum st7789v_command - ST7789V display controller commands
+ *
+ * @PORCTRL: porch setting
+ * @GCTRL: gate control
+ * @VCOMS: VCOM setting
+ * @VDVVRHEN: VDV and VRH command enable
+ * @VRHS: VRH set
+ * @VDVS: VDV set
+ * @VCMOFSET: VCOM offset set
+ * @PWCTRL1: power control 1
+ * @PVGAMCTRL: positive voltage gamma control
+ * @NVGAMCTRL: negative voltage gamma control
+ *
+ * The command names are the same as those found in the datasheet to ease
+ * looking up their semantics and usage.
+ *
+ * Note that the ST7789V display controller offers quite a few more commands
+ * which have been omitted from this list as they are not used at the moment.
+ * Furthermore, commands that are compliant with the MIPI DCS have been left
+ * out as well to avoid duplicate entries.
+ */
+enum st7789v_command {
+ PORCTRL = 0xB2,
+ GCTRL = 0xB7,
+ VCOMS = 0xBB,
+ VDVVRHEN = 0xC2,
+ VRHS = 0xC3,
+ VDVS = 0xC4,
+ VCMOFSET = 0xC5,
+ PWCTRL1 = 0xD0,
+ PVGAMCTRL = 0xE0,
+ NVGAMCTRL = 0xE1,
+};
+
+#define MADCTL_BGR BIT(3) /* bitmask for RGB/BGR order */
+#define MADCTL_MV BIT(5) /* bitmask for page/column order */
+#define MADCTL_MX BIT(6) /* bitmask for column address order */
+#define MADCTL_MY BIT(7) /* bitmask for page address order */
+
+/* 60Hz for 16.6ms, configured as 2*16.6ms */
+#define PANEL_TE_TIMEOUT_MS 33
+
+static struct completion panel_te; /* completion for panel TE line */
+static int irq_te; /* Linux IRQ for LCD TE line */
+
+static irqreturn_t panel_te_handler(int irq, void *data)
+{
+ complete(&panel_te);
+ return IRQ_HANDLED;
+}
+
+/*
+ * init_tearing_effect_line() - init tearing effect line.
+ * @par: FBTFT parameter object.
+ *
+ * Return: 0 on success, or a negative error code otherwise.
+ */
+static int init_tearing_effect_line(struct fbtft_par *par)
+{
+ struct device *dev = par->info->device;
+ struct gpio_desc *te;
+ int rc, irq;
+
+ te = gpiod_get_optional(dev, "te", GPIOD_IN);
+ if (IS_ERR(te))
+ return dev_err_probe(dev, PTR_ERR(te), "Failed to request te GPIO\n");
+
+ /* if te is NULL, indicating no configuration, directly return success */
+ if (!te) {
+ irq_te = 0;
+ return 0;
+ }
+
+ irq = gpiod_to_irq(te);
+
+ /* GPIO is locked as an IRQ, we may drop the reference */
+ gpiod_put(te);
+
+ if (irq < 0)
+ return irq;
+
+ irq_te = irq;
+ init_completion(&panel_te);
+
+ /* The effective state is high and lasts no more than 1000 microseconds */
+ rc = devm_request_irq(dev, irq_te, panel_te_handler,
+ IRQF_TRIGGER_RISING, "TE_GPIO", par);
+ if (rc)
+ return dev_err_probe(dev, rc, "TE IRQ request failed.\n");
+
+ disable_irq_nosync(irq_te);
+
+ return 0;
+}
+
+/**
+ * init_display() - initialize the display controller
+ *
+ * @par: FBTFT parameter object
+ *
+ * Most of the commands in this init function set their parameters to the
+ * same default values which are already in place after the display has been
+ * powered up. (The main exception to this rule is the pixel format which
+ * would default to 18 instead of 16 bit per pixel.)
+ * Nonetheless, this sequence can be used as a template for concrete
+ * displays which usually need some adjustments.
+ *
+ * Return: 0 on success, < 0 if error occurred.
+ */
+static int init_display(struct fbtft_par *par)
+{
+ int rc;
+
+ par->fbtftops.reset(par);
+
+ rc = init_tearing_effect_line(par);
+ if (rc)
+ return rc;
+
+ /* turn off sleep mode */
+ write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
+ mdelay(120);
+
+ /* set pixel format to RGB-565 */
+ write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT);
+ if (HSD20_IPS)
+ write_reg(par, PORCTRL, 0x05, 0x05, 0x00, 0x33, 0x33);
+
+ else
+ write_reg(par, PORCTRL, 0x08, 0x08, 0x00, 0x22, 0x22);
+
+ /*
+ * VGH = 13.26V
+ * VGL = -10.43V
+ */
+ if (HSD20_IPS)
+ write_reg(par, GCTRL, 0x75);
+ else
+ write_reg(par, GCTRL, 0x35);
+
+ /*
+ * VDV and VRH register values come from command write
+ * (instead of NVM)
+ */
+ write_reg(par, VDVVRHEN, 0x01, 0xFF);
+
+ /*
+ * VAP = 4.1V + (VCOM + VCOM offset + 0.5 * VDV)
+ * VAN = -4.1V + (VCOM + VCOM offset + 0.5 * VDV)
+ */
+ if (HSD20_IPS)
+ write_reg(par, VRHS, 0x13);
+ else
+ write_reg(par, VRHS, 0x0B);
+
+ /* VDV = 0V */
+ write_reg(par, VDVS, 0x20);
+
+ /* VCOM = 0.9V */
+ if (HSD20_IPS)
+ write_reg(par, VCOMS, 0x22);
+ else
+ write_reg(par, VCOMS, 0x20);
+
+ /* VCOM offset = 0V */
+ write_reg(par, VCMOFSET, 0x20);
+
+ /*
+ * AVDD = 6.8V
+ * AVCL = -4.8V
+ * VDS = 2.3V
+ */
+ write_reg(par, PWCTRL1, 0xA4, 0xA1);
+
+ /* TE line output is off by default when powering on */
+ if (irq_te)
+ write_reg(par, MIPI_DCS_SET_TEAR_ON, 0x00);
+
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
+
+ if (HSD20_IPS)
+ write_reg(par, MIPI_DCS_ENTER_INVERT_MODE);
+
+ return 0;
+}
+
+/*
+ * write_vmem() - write data to display.
+ * @par: FBTFT parameter object.
+ * @offset: offset from screen_buffer.
+ * @len: the length of data to be writte.
+ *
+ * Return: 0 on success, or a negative error code otherwise.
+ */
+static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
+{
+ struct device *dev = par->info->device;
+ int ret;
+
+ if (irq_te) {
+ enable_irq(irq_te);
+ reinit_completion(&panel_te);
+ ret = wait_for_completion_timeout(&panel_te,
+ msecs_to_jiffies(PANEL_TE_TIMEOUT_MS));
+ if (ret == 0)
+ dev_err(dev, "wait panel TE timeout\n");
+
+ disable_irq(irq_te);
+ }
+
+ switch (par->pdata->display.buswidth) {
+ case 8:
+ ret = fbtft_write_vmem16_bus8(par, offset, len);
+ break;
+ case 9:
+ ret = fbtft_write_vmem16_bus9(par, offset, len);
+ break;
+ case 16:
+ ret = fbtft_write_vmem16_bus16(par, offset, len);
+ break;
+ default:
+ dev_err(dev, "Unsupported buswidth %d\n",
+ par->pdata->display.buswidth);
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * set_var() - apply LCD properties like rotation and BGR mode
+ *
+ * @par: FBTFT parameter object
+ *
+ * Return: 0 on success, < 0 if error occurred.
+ */
+static int set_var(struct fbtft_par *par)
+{
+ u8 madctl_par = 0;
+
+ if (par->bgr)
+ madctl_par |= MADCTL_BGR;
+ switch (par->info->var.rotate) {
+ case 0:
+ break;
+ case 90:
+ madctl_par |= (MADCTL_MV | MADCTL_MY);
+ break;
+ case 180:
+ madctl_par |= (MADCTL_MX | MADCTL_MY);
+ break;
+ case 270:
+ madctl_par |= (MADCTL_MV | MADCTL_MX);
+ break;
+ default:
+ return -EINVAL;
+ }
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, madctl_par);
+ return 0;
+}
+
+/**
+ * set_gamma() - set gamma curves
+ *
+ * @par: FBTFT parameter object
+ * @curves: gamma curves
+ *
+ * Before the gamma curves are applied, they are preprocessed with a bitmask
+ * to ensure syntactically correct input for the display controller.
+ * This implies that the curves input parameter might be changed by this
+ * function and that illegal gamma values are auto-corrected and not
+ * reported as errors.
+ *
+ * Return: 0 on success, < 0 if error occurred.
+ */
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ int i;
+ int j;
+ int c; /* curve index offset */
+
+ /*
+ * Bitmasks for gamma curve command parameters.
+ * The masks are the same for both positive and negative voltage
+ * gamma curves.
+ */
+ static const u8 gamma_par_mask[] = {
+ 0xFF, /* V63[3:0], V0[3:0]*/
+ 0x3F, /* V1[5:0] */
+ 0x3F, /* V2[5:0] */
+ 0x1F, /* V4[4:0] */
+ 0x1F, /* V6[4:0] */
+ 0x3F, /* J0[1:0], V13[3:0] */
+ 0x7F, /* V20[6:0] */
+ 0x77, /* V36[2:0], V27[2:0] */
+ 0x7F, /* V43[6:0] */
+ 0x3F, /* J1[1:0], V50[3:0] */
+ 0x1F, /* V57[4:0] */
+ 0x1F, /* V59[4:0] */
+ 0x3F, /* V61[5:0] */
+ 0x3F, /* V62[5:0] */
+ };
+
+ for (i = 0; i < par->gamma.num_curves; i++) {
+ c = i * par->gamma.num_values;
+ for (j = 0; j < par->gamma.num_values; j++)
+ curves[c + j] &= gamma_par_mask[j];
+ write_reg(par, PVGAMCTRL + i,
+ curves[c + 0], curves[c + 1], curves[c + 2],
+ curves[c + 3], curves[c + 4], curves[c + 5],
+ curves[c + 6], curves[c + 7], curves[c + 8],
+ curves[c + 9], curves[c + 10], curves[c + 11],
+ curves[c + 12], curves[c + 13]);
+ }
+ return 0;
+}
+
+/**
+ * blank() - blank the display
+ *
+ * @par: FBTFT parameter object
+ * @on: whether to enable or disable blanking the display
+ *
+ * Return: 0 on success, < 0 if error occurred.
+ */
+static int blank(struct fbtft_par *par, bool on)
+{
+ if (on)
+ write_reg(par, MIPI_DCS_SET_DISPLAY_OFF);
+ else
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = 240,
+ .height = 320,
+ .gamma_num = 2,
+ .gamma_len = 14,
+ .gamma = HSD20_IPS_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .write_vmem = write_vmem,
+ .set_var = set_var,
+ .set_gamma = set_gamma,
+ .blank = blank,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7789v", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:st7789v");
+MODULE_ALIAS("platform:st7789v");
+
+MODULE_DESCRIPTION("FB driver for the ST7789V LCD Controller");
+MODULE_AUTHOR("Dennis Menschel");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_tinylcd.c b/drivers/staging/fbtft/fb_tinylcd.c
new file mode 100644
index 000000000..9469248f2
--- /dev/null
+++ b/drivers/staging/fbtft/fb_tinylcd.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Custom FB driver for tinylcd.com display
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_tinylcd"
+#define WIDTH 320
+#define HEIGHT 480
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ write_reg(par, 0xB0, 0x80);
+ write_reg(par, 0xC0, 0x0A, 0x0A);
+ write_reg(par, 0xC1, 0x45, 0x07);
+ write_reg(par, 0xC2, 0x33);
+ write_reg(par, 0xC5, 0x00, 0x42, 0x80);
+ write_reg(par, 0xB1, 0xD0, 0x11);
+ write_reg(par, 0xB4, 0x02);
+ write_reg(par, 0xB6, 0x00, 0x22, 0x3B);
+ write_reg(par, 0xB7, 0x07);
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x58);
+ write_reg(par, 0xF0, 0x36, 0xA5, 0xD3);
+ write_reg(par, 0xE5, 0x80);
+ write_reg(par, 0xE5, 0x01);
+ write_reg(par, 0xB3, 0x00);
+ write_reg(par, 0xE5, 0x00);
+ write_reg(par, 0xF0, 0x36, 0xA5, 0x53);
+ write_reg(par, 0xE0, 0x00, 0x35, 0x33, 0x00, 0x00, 0x00,
+ 0x00, 0x35, 0x33, 0x00, 0x00, 0x00);
+ write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
+ write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
+ udelay(250);
+ write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF);
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ case 270:
+ write_reg(par, 0xB6, 0x00, 0x02, 0x3B);
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x28);
+ break;
+ case 180:
+ write_reg(par, 0xB6, 0x00, 0x22, 0x3B);
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x58);
+ break;
+ case 90:
+ write_reg(par, 0xB6, 0x00, 0x22, 0x3B);
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x38);
+ break;
+ default:
+ write_reg(par, 0xB6, 0x00, 0x22, 0x3B);
+ write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x08);
+ break;
+ }
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "neosec,tinylcd", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("spi:tinylcd");
+
+MODULE_DESCRIPTION("Custom FB driver for tinylcd.com display");
+MODULE_AUTHOR("Noralf Tronnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_tls8204.c b/drivers/staging/fbtft/fb_tls8204.c
new file mode 100644
index 000000000..bec6dd0ff
--- /dev/null
+++ b/drivers/staging/fbtft/fb_tls8204.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the TLS8204 LCD Controller
+ *
+ * The display is monochrome and the video memory is RGB565.
+ * Any pixel value except 0 turns the pixel on.
+ *
+ * Copyright (C) 2013 Noralf Tronnes
+ * Copyright (C) 2014 Michael Hope (adapted for the TLS8204)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_tls8204"
+#define WIDTH 84
+#define HEIGHT 48
+#define TXBUFLEN WIDTH
+
+/* gamma is used to control contrast in this driver */
+#define DEFAULT_GAMMA "40"
+
+static unsigned int bs = 4;
+module_param(bs, uint, 0000);
+MODULE_PARM_DESC(bs, "BS[2:0] Bias voltage level: 0-7 (default: 4)");
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* Enter extended command mode */
+ write_reg(par, 0x21); /* 5:1 1
+ * 2:0 PD - Powerdown control: chip is active
+ * 1:0 V - Entry mode: horizontal addressing
+ * 0:1 H - Extended instruction set control:
+ * extended
+ */
+
+ /* H=1 Bias system */
+ write_reg(par, 0x10 | (bs & 0x7));
+ /* 4:1 1
+ * 3:0 0
+ * 2:x BS2 - Bias System
+ * 1:x BS1
+ * 0:x BS0
+ */
+
+ /* Set the address of the first display line. */
+ write_reg(par, 0x04 | (64 >> 6));
+ write_reg(par, 0x40 | (64 & 0x3F));
+
+ /* Enter H=0 standard command mode */
+ write_reg(par, 0x20);
+
+ /* H=0 Display control */
+ write_reg(par, 0x08 | 4);
+ /* 3:1 1
+ * 2:1 D - DE: 10=normal mode
+ * 1:0 0
+ * 0:0 E
+ */
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ /* H=0 Set X address of RAM */
+ write_reg(par, 0x80); /* 7:1 1
+ * 6-0: X[6:0] - 0x00
+ */
+
+ /* H=0 Set Y address of RAM */
+ write_reg(par, 0x40); /* 7:0 0
+ * 6:1 1
+ * 2-0: Y[2:0] - 0x0
+ */
+}
+
+static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u16 *vmem16 = (u16 *)par->info->screen_buffer;
+ int x, y, i;
+ int ret = 0;
+
+ for (y = 0; y < HEIGHT / 8; y++) {
+ u8 *buf = par->txbuf.buf;
+ /* The display is 102x68 but the LCD is 84x48.
+ * Set the write pointer at the start of each row.
+ */
+ gpiod_set_value(par->gpio.dc, 0);
+ write_reg(par, 0x80 | 0);
+ write_reg(par, 0x40 | y);
+
+ for (x = 0; x < WIDTH; x++) {
+ u8 ch = 0;
+
+ for (i = 0; i < 8 * WIDTH; i += WIDTH) {
+ ch >>= 1;
+ if (vmem16[(y * 8 * WIDTH) + i + x])
+ ch |= 0x80;
+ }
+ *buf++ = ch;
+ }
+ /* Write the row */
+ gpiod_set_value(par->gpio.dc, 1);
+ ret = par->fbtftops.write(par, par->txbuf.buf, WIDTH);
+ if (ret < 0) {
+ dev_err(par->info->device,
+ "write failed and returned: %d\n", ret);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int set_gamma(struct fbtft_par *par, u32 *curves)
+{
+ /* apply mask */
+ curves[0] &= 0x7F;
+
+ write_reg(par, 0x21); /* turn on extended instruction set */
+ write_reg(par, 0x80 | curves[0]);
+ write_reg(par, 0x20); /* turn off extended instruction set */
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .txbuflen = TXBUFLEN,
+ .gamma_num = 1,
+ .gamma_len = 1,
+ .gamma = DEFAULT_GAMMA,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .write_vmem = write_vmem,
+ .set_gamma = set_gamma,
+ },
+ .backlight = 1,
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "teralane,tls8204", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("spi:tls8204");
+
+MODULE_DESCRIPTION("FB driver for the TLS8204 LCD Controller");
+MODULE_AUTHOR("Michael Hope");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_uc1611.c b/drivers/staging/fbtft/fb_uc1611.c
new file mode 100644
index 000000000..f61e373c7
--- /dev/null
+++ b/drivers/staging/fbtft/fb_uc1611.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the UltraChip UC1611 LCD controller
+ *
+ * The display is 4-bit grayscale (16 shades) 240x160.
+ *
+ * Copyright (C) 2015 Henri Chain
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_uc1611"
+#define WIDTH 240
+#define HEIGHT 160
+#define BPP 8
+#define FPS 40
+
+/*
+ * LCD voltage is a combination of ratio, gain, pot and temp
+ *
+ * V_LCD = V_BIAS * ratio
+ * V_LCD = (C_V0 + C_PM × pot) * (1 + (T - 25) * temp)
+ * C_V0 and C_PM depend on ratio and gain
+ * T is ambient temperature
+ */
+
+/* BR -> actual ratio: 0-3 -> 5, 10, 11, 13 */
+static unsigned int ratio = 2;
+module_param(ratio, uint, 0000);
+MODULE_PARM_DESC(ratio, "BR[1:0] Bias voltage ratio: 0-3 (default: 2)");
+
+static unsigned int gain = 3;
+module_param(gain, uint, 0000);
+MODULE_PARM_DESC(gain, "GN[1:0] Bias voltage gain: 0-3 (default: 3)");
+
+static unsigned int pot = 16;
+module_param(pot, uint, 0000);
+MODULE_PARM_DESC(pot, "PM[6:0] Bias voltage pot.: 0-63 (default: 16)");
+
+/* TC -> % compensation per deg C: 0-3 -> -.05, -.10, -.015, -.20 */
+static unsigned int temp;
+module_param(temp, uint, 0000);
+MODULE_PARM_DESC(temp, "TC[1:0] Temperature compensation: 0-3 (default: 0)");
+
+/* PC[1:0] -> LCD capacitance: 0-3 -> <20nF, 20-28 nF, 29-40 nF, 40-56 nF */
+static unsigned int load = 1;
+module_param(load, uint, 0000);
+MODULE_PARM_DESC(load, "PC[1:0] Panel Loading: 0-3 (default: 1)");
+
+/* PC[3:2] -> V_LCD: 0, 1, 3 -> ext., int. with ratio = 5, int. standard */
+static unsigned int pump = 3;
+module_param(pump, uint, 0000);
+MODULE_PARM_DESC(pump, "PC[3:2] Pump control: 0,1,3 (default: 3)");
+
+static int init_display(struct fbtft_par *par)
+{
+ int ret;
+
+ /*
+ * Set CS active inverse polarity: just setting SPI_CS_HIGH does not
+ * work with GPIO based chip selects that are logically active high
+ * but inverted inside the GPIO library, so enforce inverted
+ * semantics.
+ */
+ par->spi->mode ^= SPI_CS_HIGH;
+ ret = spi_setup(par->spi);
+ if (ret) {
+ dev_err(par->info->device,
+ "Could not set inverse CS polarity\n");
+ return ret;
+ }
+
+ /* Reset controller */
+ write_reg(par, 0xE2);
+
+ /* Set bias ratio */
+ write_reg(par, 0xE8 | (ratio & 0x03));
+
+ /* Set bias gain and potentiometer */
+ write_reg(par, 0x81);
+ write_reg(par, (gain & 0x03) << 6 | (pot & 0x3F));
+
+ /* Set temperature compensation */
+ write_reg(par, 0x24 | (temp & 0x03));
+
+ /* Set panel loading */
+ write_reg(par, 0x28 | (load & 0x03));
+
+ /* Set pump control */
+ write_reg(par, 0x2C | (pump & 0x03));
+
+ /* Set inverse display */
+ write_reg(par, 0xA6 | 0x01);
+
+ /* Set 4-bit grayscale mode */
+ write_reg(par, 0xD0 | (0x02 & 0x03));
+
+ /* Set Display enable */
+ write_reg(par, 0xA8 | 0x07);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ switch (par->info->var.rotate) {
+ case 90:
+ case 270:
+ /* Set column address */
+ write_reg(par, ys & 0x0F);
+ write_reg(par, 0x10 | (ys >> 4));
+
+ /* Set page address (divide xs by 2) (not used by driver) */
+ write_reg(par, 0x60 | ((xs >> 1) & 0x0F));
+ write_reg(par, 0x70 | (xs >> 5));
+ break;
+ default:
+ /* Set column address (not used by driver) */
+ write_reg(par, xs & 0x0F);
+ write_reg(par, 0x10 | (xs >> 4));
+
+ /* Set page address (divide ys by 2) */
+ write_reg(par, 0x60 | ((ys >> 1) & 0x0F));
+ write_reg(par, 0x70 | (ys >> 5));
+ break;
+ }
+}
+
+static int blank(struct fbtft_par *par, bool on)
+{
+ fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n",
+ __func__, on ? "true" : "false");
+
+ if (on)
+ write_reg(par, 0xA8 | 0x00);
+ else
+ write_reg(par, 0xA8 | 0x07);
+ return 0;
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ /* par->info->fix.visual = FB_VISUAL_PSEUDOCOLOR; */
+ par->info->var.grayscale = 1;
+ par->info->var.red.offset = 0;
+ par->info->var.red.length = 8;
+ par->info->var.green.offset = 0;
+ par->info->var.green.length = 8;
+ par->info->var.blue.offset = 0;
+ par->info->var.blue.length = 8;
+ par->info->var.transp.offset = 0;
+ par->info->var.transp.length = 0;
+
+ switch (par->info->var.rotate) {
+ case 90:
+ /* Set RAM address control */
+ write_reg(par, 0x88
+ | (0x0 & 0x1) << 2 /* Increment positively */
+ | (0x1 << 1) /* Increment page first */
+ | 0x1); /* Wrap around (default) */
+
+ /* Set LCD mapping */
+ write_reg(par, 0xC0
+ | (0x0 & 0x1) << 2 /* Mirror Y OFF */
+ | (0x0 & 0x1) << 1 /* Mirror X OFF */
+ | (0x0 & 0x1)); /* MS nibble last (default) */
+ break;
+ case 180:
+ /* Set RAM address control */
+ write_reg(par, 0x88
+ | (0x0 & 0x1) << 2 /* Increment positively */
+ | (0x0 & 0x1) << 1 /* Increment column first */
+ | 0x1); /* Wrap around (default) */
+
+ /* Set LCD mapping */
+ write_reg(par, 0xC0
+ | (0x1 << 2) /* Mirror Y ON */
+ | (0x0 & 0x1) << 1 /* Mirror X OFF */
+ | (0x0 & 0x1)); /* MS nibble last (default) */
+ break;
+ case 270:
+ /* Set RAM address control */
+ write_reg(par, 0x88
+ | (0x0 & 0x1) << 2 /* Increment positively */
+ | (0x1 << 1) /* Increment page first */
+ | 0x1); /* Wrap around (default) */
+
+ /* Set LCD mapping */
+ write_reg(par, 0xC0
+ | (0x1 << 2) /* Mirror Y ON */
+ | (0x1 << 1) /* Mirror X ON */
+ | (0x0 & 0x1)); /* MS nibble last (default) */
+ break;
+ default:
+ /* Set RAM address control */
+ write_reg(par, 0x88
+ | (0x0 & 0x1) << 2 /* Increment positively */
+ | (0x0 & 0x1) << 1 /* Increment column first */
+ | 0x1); /* Wrap around (default) */
+
+ /* Set LCD mapping */
+ write_reg(par, 0xC0
+ | (0x0 & 0x1) << 2 /* Mirror Y OFF */
+ | (0x1 << 1) /* Mirror X ON */
+ | (0x0 & 0x1)); /* MS nibble last (default) */
+ break;
+ }
+
+ return 0;
+}
+
+static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u8 *vmem8 = (u8 *)(par->info->screen_buffer);
+ u8 *buf8 = par->txbuf.buf;
+ u16 *buf16 = par->txbuf.buf;
+ int line_length = par->info->fix.line_length;
+ int y_start = offset / line_length;
+ int y_end = (offset + len - 1) / line_length;
+ int x, y, i;
+ int ret = 0;
+
+ switch (par->pdata->display.buswidth) {
+ case 8:
+ switch (par->info->var.rotate) {
+ case 90:
+ case 270:
+ i = y_start * line_length;
+ for (y = y_start; y <= y_end; y++) {
+ for (x = 0; x < line_length; x += 2) {
+ *buf8 = vmem8[i] >> 4;
+ *buf8 |= vmem8[i + 1] & 0xF0;
+ buf8++;
+ i += 2;
+ }
+ }
+ break;
+ default:
+ /* Must be even because pages are two lines */
+ y_start &= 0xFE;
+ i = y_start * line_length;
+ for (y = y_start; y <= y_end; y += 2) {
+ for (x = 0; x < line_length; x++) {
+ *buf8 = vmem8[i] >> 4;
+ *buf8 |= vmem8[i + line_length] & 0xF0;
+ buf8++;
+ i++;
+ }
+ i += line_length;
+ }
+ break;
+ }
+ gpiod_set_value(par->gpio.dc, 1);
+
+ /* Write data */
+ ret = par->fbtftops.write(par, par->txbuf.buf, len / 2);
+ break;
+ case 9:
+ switch (par->info->var.rotate) {
+ case 90:
+ case 270:
+ i = y_start * line_length;
+ for (y = y_start; y <= y_end; y++) {
+ for (x = 0; x < line_length; x += 2) {
+ *buf16 = 0x100;
+ *buf16 |= vmem8[i] >> 4;
+ *buf16 |= vmem8[i + 1] & 0xF0;
+ buf16++;
+ i += 2;
+ }
+ }
+ break;
+ default:
+ /* Must be even because pages are two lines */
+ y_start &= 0xFE;
+ i = y_start * line_length;
+ for (y = y_start; y <= y_end; y += 2) {
+ for (x = 0; x < line_length; x++) {
+ *buf16 = 0x100;
+ *buf16 |= vmem8[i] >> 4;
+ *buf16 |= vmem8[i + line_length] & 0xF0;
+ buf16++;
+ i++;
+ }
+ i += line_length;
+ }
+ break;
+ }
+
+ /* Write data */
+ ret = par->fbtftops.write(par, par->txbuf.buf, len);
+ break;
+ default:
+ dev_err(par->info->device, "unsupported buswidth %d\n",
+ par->pdata->display.buswidth);
+ }
+
+ if (ret < 0)
+ dev_err(par->info->device, "write failed and returned: %d\n",
+ ret);
+
+ return ret;
+}
+
+static struct fbtft_display display = {
+ .txbuflen = -1,
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .bpp = BPP,
+ .fps = FPS,
+ .fbtftops = {
+ .write_vmem = write_vmem,
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ .blank = blank,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "ultrachip,uc1611", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:uc1611");
+MODULE_ALIAS("platform:uc1611");
+
+MODULE_DESCRIPTION("FB driver for the UC1611 LCD controller");
+MODULE_AUTHOR("Henri Chain");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_uc1701.c b/drivers/staging/fbtft/fb_uc1701.c
new file mode 100644
index 000000000..e4ccc7386
--- /dev/null
+++ b/drivers/staging/fbtft/fb_uc1701.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the UC1701 LCD Controller
+ *
+ * The display is monochrome and the video memory is RGB565.
+ * Any pixel value except 0 turns the pixel on.
+ *
+ * Copyright (C) 2014 Juergen Holzmann
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_uc1701"
+#define WIDTH 102
+#define HEIGHT 64
+#define PAGES (HEIGHT / 8)
+
+/* 1: Display on/off */
+#define LCD_DISPLAY_ENABLE 0xAE
+/* 2: display start line set */
+#define LCD_START_LINE 0x40
+/* 3: Page address set (lower 4 bits select one of the pages) */
+#define LCD_PAGE_ADDRESS 0xB0
+/* 4: column address */
+#define LCD_COL_ADDRESS 0x10
+/* 8: select orientation */
+#define LCD_BOTTOMVIEW 0xA0
+/* 9: inverted display */
+#define LCD_DISPLAY_INVERT 0xA6
+/* 10: show memory content or switch all pixels on */
+#define LCD_ALL_PIXEL 0xA4
+/* 11: lcd bias set */
+#define LCD_BIAS 0xA2
+/* 14: Reset Controller */
+#define LCD_RESET_CMD 0xE2
+/* 15: output mode select (turns display upside-down) */
+#define LCD_SCAN_DIR 0xC0
+/* 16: power control set */
+#define LCD_POWER_CONTROL 0x28
+/* 17: voltage regulator resistor ratio set */
+#define LCD_VOLTAGE 0x20
+/* 18: Volume mode set */
+#define LCD_VOLUME_MODE 0x81
+/* 22: NOP command */
+#define LCD_NO_OP 0xE3
+/* 25: advanced program control */
+#define LCD_ADV_PROG_CTRL 0xFA
+/* 25: advanced program control2 */
+#define LCD_ADV_PROG_CTRL2 0x10
+#define LCD_TEMPCOMP_HIGH 0x80
+/* column offset for normal orientation */
+#define SHIFT_ADDR_NORMAL 0
+/* column offset for bottom view orientation */
+#define SHIFT_ADDR_TOPVIEW 30
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* softreset of LCD */
+ write_reg(par, LCD_RESET_CMD);
+ mdelay(10);
+
+ /* set startpoint */
+ write_reg(par, LCD_START_LINE);
+
+ /* select orientation BOTTOMVIEW */
+ write_reg(par, LCD_BOTTOMVIEW | 1);
+
+ /* output mode select (turns display upside-down) */
+ write_reg(par, LCD_SCAN_DIR | 0x00);
+
+ /* Normal Pixel mode */
+ write_reg(par, LCD_ALL_PIXEL | 0);
+
+ /* positive display */
+ write_reg(par, LCD_DISPLAY_INVERT | 0);
+
+ /* bias 1/9 */
+ write_reg(par, LCD_BIAS | 0);
+
+ /* power control mode: all features on */
+ write_reg(par, LCD_POWER_CONTROL | 0x07);
+
+ /* set voltage regulator R/R */
+ write_reg(par, LCD_VOLTAGE | 0x07);
+
+ /* volume mode set */
+ write_reg(par, LCD_VOLUME_MODE);
+ write_reg(par, 0x09);
+ write_reg(par, LCD_NO_OP);
+
+ /* advanced program control */
+ write_reg(par, LCD_ADV_PROG_CTRL);
+ write_reg(par, LCD_ADV_PROG_CTRL2 | LCD_TEMPCOMP_HIGH);
+
+ /* enable display */
+ write_reg(par, LCD_DISPLAY_ENABLE | 1);
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ /* goto address */
+ write_reg(par, LCD_PAGE_ADDRESS);
+ write_reg(par, 0x00);
+ write_reg(par, LCD_COL_ADDRESS);
+}
+
+static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u16 *vmem16 = (u16 *)par->info->screen_buffer;
+ u8 *buf;
+ int x, y, i;
+ int ret = 0;
+
+ for (y = 0; y < PAGES; y++) {
+ buf = par->txbuf.buf;
+ for (x = 0; x < WIDTH; x++) {
+ *buf = 0x00;
+ for (i = 0; i < 8; i++)
+ *buf |= (vmem16[((y * 8 * WIDTH) +
+ (i * WIDTH)) + x] ?
+ 1 : 0) << i;
+ buf++;
+ }
+
+ write_reg(par, LCD_PAGE_ADDRESS | (u8)y);
+ write_reg(par, 0x00);
+ write_reg(par, LCD_COL_ADDRESS);
+ gpiod_set_value(par->gpio.dc, 1);
+ ret = par->fbtftops.write(par, par->txbuf.buf, WIDTH);
+ gpiod_set_value(par->gpio.dc, 0);
+ }
+
+ if (ret < 0)
+ dev_err(par->info->device, "write failed and returned: %d\n",
+ ret);
+
+ return ret;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 8,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .write_vmem = write_vmem,
+ },
+ .backlight = 1,
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "UltraChip,uc1701", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("spi:uc1701");
+
+MODULE_DESCRIPTION("FB driver for the UC1701 LCD Controller");
+MODULE_AUTHOR("Juergen Holzmann");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fb_upd161704.c b/drivers/staging/fbtft/fb_upd161704.c
new file mode 100644
index 000000000..c680160d6
--- /dev/null
+++ b/drivers/staging/fbtft/fb_upd161704.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * FB driver for the uPD161704 LCD Controller
+ *
+ * Copyright (C) 2014 Seong-Woo Kim
+ *
+ * Based on fb_ili9325.c by Noralf Tronnes
+ * Based on ili9325.c by Jeroen Domburg
+ * Init code from UTFT library by Henning Karlsen
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include "fbtft.h"
+
+#define DRVNAME "fb_upd161704"
+#define WIDTH 240
+#define HEIGHT 320
+#define BPP 16
+
+static int init_display(struct fbtft_par *par)
+{
+ par->fbtftops.reset(par);
+
+ /* Initialization sequence from Lib_UTFT */
+
+ /* register reset */
+ write_reg(par, 0x0003, 0x0001); /* Soft reset */
+
+ /* oscillator start */
+ write_reg(par, 0x003A, 0x0001); /*Oscillator 0: stop, 1: operation */
+ udelay(100);
+
+ /* y-setting */
+ write_reg(par, 0x0024, 0x007B); /* amplitude setting */
+ udelay(10);
+ write_reg(par, 0x0025, 0x003B); /* amplitude setting */
+ write_reg(par, 0x0026, 0x0034); /* amplitude setting */
+ udelay(10);
+ write_reg(par, 0x0027, 0x0004); /* amplitude setting */
+ write_reg(par, 0x0052, 0x0025); /* circuit setting 1 */
+ udelay(10);
+ write_reg(par, 0x0053, 0x0033); /* circuit setting 2 */
+ write_reg(par, 0x0061, 0x001C); /* adjustment V10 positive polarity */
+ udelay(10);
+ write_reg(par, 0x0062, 0x002C); /* adjustment V9 negative polarity */
+ write_reg(par, 0x0063, 0x0022); /* adjustment V34 positive polarity */
+ udelay(10);
+ write_reg(par, 0x0064, 0x0027); /* adjustment V31 negative polarity */
+ udelay(10);
+ write_reg(par, 0x0065, 0x0014); /* adjustment V61 negative polarity */
+ udelay(10);
+ write_reg(par, 0x0066, 0x0010); /* adjustment V61 negative polarity */
+
+ /* Basical clock for 1 line (BASECOUNT[7:0]) number specified */
+ write_reg(par, 0x002E, 0x002D);
+
+ /* Power supply setting */
+ write_reg(par, 0x0019, 0x0000); /* DC/DC output setting */
+ udelay(200);
+ write_reg(par, 0x001A, 0x1000); /* DC/DC frequency setting */
+ write_reg(par, 0x001B, 0x0023); /* DC/DC rising setting */
+ write_reg(par, 0x001C, 0x0C01); /* Regulator voltage setting */
+ write_reg(par, 0x001D, 0x0000); /* Regulator current setting */
+ write_reg(par, 0x001E, 0x0009); /* VCOM output setting */
+ write_reg(par, 0x001F, 0x0035); /* VCOM amplitude setting */
+ write_reg(par, 0x0020, 0x0015); /* VCOMM cencter setting */
+ write_reg(par, 0x0018, 0x1E7B); /* DC/DC operation setting */
+
+ /* windows setting */
+ write_reg(par, 0x0008, 0x0000); /* Minimum X address */
+ write_reg(par, 0x0009, 0x00EF); /* Maximum X address */
+ write_reg(par, 0x000a, 0x0000); /* Minimum Y address */
+ write_reg(par, 0x000b, 0x013F); /* Maximum Y address */
+
+ /* LCD display area setting */
+ write_reg(par, 0x0029, 0x0000); /* [LCDSIZE] X MIN. size set */
+ write_reg(par, 0x002A, 0x0000); /* [LCDSIZE] Y MIN. size set */
+ write_reg(par, 0x002B, 0x00EF); /* [LCDSIZE] X MAX. size set */
+ write_reg(par, 0x002C, 0x013F); /* [LCDSIZE] Y MAX. size set */
+
+ /* Gate scan setting */
+ write_reg(par, 0x0032, 0x0002);
+
+ /* n line inversion line number */
+ write_reg(par, 0x0033, 0x0000);
+
+ /* Line inversion/frame inversion/interlace setting */
+ write_reg(par, 0x0037, 0x0000);
+
+ /* Gate scan operation setting register */
+ write_reg(par, 0x003B, 0x0001);
+
+ /* Color mode */
+ /*GS = 0: 260-k color (64 gray scale), GS = 1: 8 color (2 gray scale) */
+ write_reg(par, 0x0004, 0x0000);
+
+ /* RAM control register */
+ write_reg(par, 0x0005, 0x0000); /*Window access 00:Normal, 10:Window */
+
+ /* Display setting register 2 */
+ write_reg(par, 0x0001, 0x0000);
+
+ /* display setting */
+ write_reg(par, 0x0000, 0x0000); /* display on */
+
+ return 0;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ switch (par->info->var.rotate) {
+ /* R20h = Horizontal GRAM Start Address */
+ /* R21h = Vertical GRAM Start Address */
+ case 0:
+ write_reg(par, 0x0006, xs);
+ write_reg(par, 0x0007, ys);
+ break;
+ case 180:
+ write_reg(par, 0x0006, WIDTH - 1 - xs);
+ write_reg(par, 0x0007, HEIGHT - 1 - ys);
+ break;
+ case 270:
+ write_reg(par, 0x0006, WIDTH - 1 - ys);
+ write_reg(par, 0x0007, xs);
+ break;
+ case 90:
+ write_reg(par, 0x0006, ys);
+ write_reg(par, 0x0007, HEIGHT - 1 - xs);
+ break;
+ }
+
+ write_reg(par, 0x0e); /* Write Data to GRAM */
+}
+
+static int set_var(struct fbtft_par *par)
+{
+ switch (par->info->var.rotate) {
+ /* AM: GRAM update direction */
+ case 0:
+ write_reg(par, 0x01, 0x0000);
+ write_reg(par, 0x05, 0x0000);
+ break;
+ case 180:
+ write_reg(par, 0x01, 0x00C0);
+ write_reg(par, 0x05, 0x0000);
+ break;
+ case 270:
+ write_reg(par, 0x01, 0x0080);
+ write_reg(par, 0x05, 0x0001);
+ break;
+ case 90:
+ write_reg(par, 0x01, 0x0040);
+ write_reg(par, 0x05, 0x0001);
+ break;
+ }
+
+ return 0;
+}
+
+static struct fbtft_display display = {
+ .regwidth = 16,
+ .width = WIDTH,
+ .height = HEIGHT,
+ .fbtftops = {
+ .init_display = init_display,
+ .set_addr_win = set_addr_win,
+ .set_var = set_var,
+ },
+};
+
+FBTFT_REGISTER_DRIVER(DRVNAME, "nec,upd161704", &display);
+
+MODULE_ALIAS("spi:" DRVNAME);
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_ALIAS("spi:upd161704");
+MODULE_ALIAS("platform:upd161704");
+
+MODULE_DESCRIPTION("FB driver for the uPD161704 LCD Controller");
+MODULE_AUTHOR("Seong-Woo Kim");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fbtft-bus.c b/drivers/staging/fbtft/fbtft-bus.c
new file mode 100644
index 000000000..3d422bc11
--- /dev/null
+++ b/drivers/staging/fbtft/fbtft-bus.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/export.h>
+#include <linux/errno.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include "fbtft.h"
+
+/*****************************************************************************
+ *
+ * void (*write_reg)(struct fbtft_par *par, int len, ...);
+ *
+ *****************************************************************************/
+
+#define define_fbtft_write_reg(func, buffer_type, data_type, modifier) \
+void func(struct fbtft_par *par, int len, ...) \
+{ \
+ va_list args; \
+ int i, ret; \
+ int offset = 0; \
+ buffer_type *buf = (buffer_type *)par->buf; \
+ \
+ if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { \
+ va_start(args, len); \
+ for (i = 0; i < len; i++) { \
+ buf[i] = modifier((data_type)va_arg(args, \
+ unsigned int)); \
+ } \
+ va_end(args); \
+ fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, \
+ par->info->device, buffer_type, buf, len, \
+ "%s: ", __func__); \
+ } \
+ \
+ va_start(args, len); \
+ \
+ if (par->startbyte) { \
+ *(u8 *)par->buf = par->startbyte; \
+ buf = (buffer_type *)(par->buf + 1); \
+ offset = 1; \
+ } \
+ \
+ *buf = modifier((data_type)va_arg(args, unsigned int)); \
+ ret = fbtft_write_buf_dc(par, par->buf, sizeof(data_type) + offset, \
+ 0); \
+ if (ret < 0) \
+ goto out; \
+ len--; \
+ \
+ if (par->startbyte) \
+ *(u8 *)par->buf = par->startbyte | 0x2; \
+ \
+ if (len) { \
+ i = len; \
+ while (i--) \
+ *buf++ = modifier((data_type)va_arg(args, \
+ unsigned int)); \
+ fbtft_write_buf_dc(par, par->buf, \
+ len * (sizeof(data_type) + offset), 1); \
+ } \
+out: \
+ va_end(args); \
+} \
+EXPORT_SYMBOL(func);
+
+define_fbtft_write_reg(fbtft_write_reg8_bus8, u8, u8, )
+define_fbtft_write_reg(fbtft_write_reg16_bus8, __be16, u16, cpu_to_be16)
+define_fbtft_write_reg(fbtft_write_reg16_bus16, u16, u16, )
+
+void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...)
+{
+ va_list args;
+ int i, ret;
+ int pad = 0;
+ u16 *buf = (u16 *)par->buf;
+
+ if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) {
+ va_start(args, len);
+ for (i = 0; i < len; i++)
+ *(((u8 *)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__);
+ }
+ if (len <= 0)
+ return;
+
+ if (par->spi && (par->spi->bits_per_word == 8)) {
+ /* we're emulating 9-bit, pad start of buffer with no-ops
+ * (assuming here that zero is a no-op)
+ */
+ pad = (len % 4) ? 4 - (len % 4) : 0;
+ for (i = 0; i < pad; i++)
+ *buf++ = 0x000;
+ }
+
+ va_start(args, len);
+ *buf++ = (u8)va_arg(args, unsigned int);
+ i = len - 1;
+ while (i--) {
+ *buf = (u8)va_arg(args, unsigned int);
+ *buf++ |= 0x100; /* dc=1 */
+ }
+ va_end(args);
+ ret = par->fbtftops.write(par, par->buf, (len + pad) * sizeof(u16));
+ if (ret < 0) {
+ dev_err(par->info->device,
+ "write() failed and returned %d\n", ret);
+ return;
+ }
+}
+EXPORT_SYMBOL(fbtft_write_reg8_bus9);
+
+/*****************************************************************************
+ *
+ * int (*write_vmem)(struct fbtft_par *par);
+ *
+ *****************************************************************************/
+
+/* 16 bit pixel over 8-bit databus */
+int fbtft_write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u16 *vmem16;
+ __be16 *txbuf16 = par->txbuf.buf;
+ size_t remain;
+ size_t to_copy;
+ size_t tx_array_size;
+ int i;
+ int ret = 0;
+ size_t startbyte_size = 0;
+
+ fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n",
+ __func__, offset, len);
+
+ remain = len / 2;
+ vmem16 = (u16 *)(par->info->screen_buffer + offset);
+
+ gpiod_set_value(par->gpio.dc, 1);
+
+ /* non buffered write */
+ if (!par->txbuf.buf)
+ return par->fbtftops.write(par, vmem16, len);
+
+ /* buffered write */
+ tx_array_size = par->txbuf.len / 2;
+
+ if (par->startbyte) {
+ txbuf16 = par->txbuf.buf + 1;
+ tx_array_size -= 2;
+ *(u8 *)(par->txbuf.buf) = par->startbyte | 0x2;
+ startbyte_size = 1;
+ }
+
+ while (remain) {
+ to_copy = min(tx_array_size, remain);
+ dev_dbg(par->info->device, "to_copy=%zu, remain=%zu\n",
+ to_copy, remain - to_copy);
+
+ for (i = 0; i < to_copy; i++)
+ txbuf16[i] = cpu_to_be16(vmem16[i]);
+
+ vmem16 = vmem16 + to_copy;
+ ret = par->fbtftops.write(par, par->txbuf.buf,
+ startbyte_size + to_copy * 2);
+ if (ret < 0)
+ return ret;
+ remain -= to_copy;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(fbtft_write_vmem16_bus8);
+
+/* 16 bit pixel over 9-bit SPI bus: dc + high byte, dc + low byte */
+int fbtft_write_vmem16_bus9(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u8 *vmem8;
+ u16 *txbuf16 = par->txbuf.buf;
+ size_t remain;
+ size_t to_copy;
+ size_t tx_array_size;
+ int i;
+ int ret = 0;
+
+ fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n",
+ __func__, offset, len);
+
+ if (!par->txbuf.buf) {
+ dev_err(par->info->device, "%s: txbuf.buf is NULL\n", __func__);
+ return -1;
+ }
+
+ remain = len;
+ vmem8 = par->info->screen_buffer + offset;
+
+ tx_array_size = par->txbuf.len / 2;
+
+ while (remain) {
+ to_copy = min(tx_array_size, remain);
+ dev_dbg(par->info->device, "to_copy=%zu, remain=%zu\n",
+ to_copy, remain - to_copy);
+
+#ifdef __LITTLE_ENDIAN
+ for (i = 0; i < to_copy; i += 2) {
+ txbuf16[i] = 0x0100 | vmem8[i + 1];
+ txbuf16[i + 1] = 0x0100 | vmem8[i];
+ }
+#else
+ for (i = 0; i < to_copy; i++)
+ txbuf16[i] = 0x0100 | vmem8[i];
+#endif
+ vmem8 = vmem8 + to_copy;
+ ret = par->fbtftops.write(par, par->txbuf.buf, to_copy * 2);
+ if (ret < 0)
+ return ret;
+ remain -= to_copy;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(fbtft_write_vmem16_bus9);
+
+int fbtft_write_vmem8_bus8(struct fbtft_par *par, size_t offset, size_t len)
+{
+ dev_err(par->info->device, "%s: function not implemented\n", __func__);
+ return -1;
+}
+EXPORT_SYMBOL(fbtft_write_vmem8_bus8);
+
+/* 16 bit pixel over 16-bit databus */
+int fbtft_write_vmem16_bus16(struct fbtft_par *par, size_t offset, size_t len)
+{
+ u16 *vmem16;
+
+ fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n",
+ __func__, offset, len);
+
+ vmem16 = (u16 *)(par->info->screen_buffer + offset);
+
+ /* no need for buffered write with 16-bit bus */
+ return fbtft_write_buf_dc(par, vmem16, len, 1);
+}
+EXPORT_SYMBOL(fbtft_write_vmem16_bus16);
diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c
new file mode 100644
index 000000000..afaba94d1
--- /dev/null
+++ b/drivers/staging/fbtft/fbtft-core.c
@@ -0,0 +1,1327 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Noralf Tronnes
+ *
+ * This driver is inspired by:
+ * st7735fb.c, Copyright (C) 2011, Matt Porter
+ * broadsheetfb.c, Copyright (C) 2008, Jaya Kumar
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/fb.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/backlight.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/spinlock.h>
+
+#include <video/mipi_display.h>
+
+#include "fbtft.h"
+#include "internal.h"
+
+static unsigned long debug;
+module_param(debug, ulong, 0000);
+MODULE_PARM_DESC(debug, "override device debug level");
+
+int fbtft_write_buf_dc(struct fbtft_par *par, void *buf, size_t len, int dc)
+{
+ int ret;
+
+ gpiod_set_value(par->gpio.dc, dc);
+
+ ret = par->fbtftops.write(par, buf, len);
+ if (ret < 0)
+ dev_err(par->info->device,
+ "write() failed and returned %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL(fbtft_write_buf_dc);
+
+void fbtft_dbg_hex(const struct device *dev, int groupsize,
+ const void *buf, size_t len, const char *fmt, ...)
+{
+ va_list args;
+ static char textbuf[512];
+ char *text = textbuf;
+ size_t text_len;
+
+ va_start(args, fmt);
+ text_len = vscnprintf(text, sizeof(textbuf), fmt, args);
+ va_end(args);
+
+ hex_dump_to_buffer(buf, len, 32, groupsize, text + text_len,
+ 512 - text_len, false);
+
+ if (len > 32)
+ dev_info(dev, "%s ...\n", text);
+ else
+ dev_info(dev, "%s\n", text);
+}
+EXPORT_SYMBOL(fbtft_dbg_hex);
+
+static int fbtft_request_one_gpio(struct fbtft_par *par,
+ const char *name, int index,
+ struct gpio_desc **gpiop)
+{
+ struct device *dev = par->info->device;
+
+ *gpiop = devm_gpiod_get_index_optional(dev, name, index,
+ GPIOD_OUT_LOW);
+ if (IS_ERR(*gpiop))
+ return dev_err_probe(dev, PTR_ERR(*gpiop), "Failed to request %s GPIO\n", name);
+
+ fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' GPIO\n",
+ __func__, name);
+
+ return 0;
+}
+
+static int fbtft_request_gpios(struct fbtft_par *par)
+{
+ int i;
+ int ret;
+
+ ret = fbtft_request_one_gpio(par, "reset", 0, &par->gpio.reset);
+ if (ret)
+ return ret;
+ ret = fbtft_request_one_gpio(par, "dc", 0, &par->gpio.dc);
+ if (ret)
+ return ret;
+ ret = fbtft_request_one_gpio(par, "rd", 0, &par->gpio.rd);
+ if (ret)
+ return ret;
+ ret = fbtft_request_one_gpio(par, "wr", 0, &par->gpio.wr);
+ if (ret)
+ return ret;
+ ret = fbtft_request_one_gpio(par, "cs", 0, &par->gpio.cs);
+ if (ret)
+ return ret;
+ ret = fbtft_request_one_gpio(par, "latch", 0, &par->gpio.latch);
+ if (ret)
+ return ret;
+ for (i = 0; i < 16; i++) {
+ ret = fbtft_request_one_gpio(par, "db", i,
+ &par->gpio.db[i]);
+ if (ret)
+ return ret;
+ ret = fbtft_request_one_gpio(par, "led", i,
+ &par->gpio.led[i]);
+ if (ret)
+ return ret;
+ ret = fbtft_request_one_gpio(par, "aux", i,
+ &par->gpio.aux[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fbtft_backlight_update_status(struct backlight_device *bd)
+{
+ struct fbtft_par *par = bl_get_data(bd);
+ bool polarity = par->polarity;
+
+ fbtft_par_dbg(DEBUG_BACKLIGHT, par,
+ "%s: polarity=%d, power=%d, fb_blank=%d\n",
+ __func__, polarity, bd->props.power, bd->props.fb_blank);
+
+ if (!backlight_is_blank(bd))
+ gpiod_set_value(par->gpio.led[0], polarity);
+ else
+ gpiod_set_value(par->gpio.led[0], !polarity);
+
+ return 0;
+}
+
+static int fbtft_backlight_get_brightness(struct backlight_device *bd)
+{
+ return bd->props.brightness;
+}
+
+void fbtft_unregister_backlight(struct fbtft_par *par)
+{
+ if (par->info->bl_dev) {
+ par->info->bl_dev->props.power = FB_BLANK_POWERDOWN;
+ backlight_update_status(par->info->bl_dev);
+ backlight_device_unregister(par->info->bl_dev);
+ par->info->bl_dev = NULL;
+ }
+}
+EXPORT_SYMBOL(fbtft_unregister_backlight);
+
+static const struct backlight_ops fbtft_bl_ops = {
+ .get_brightness = fbtft_backlight_get_brightness,
+ .update_status = fbtft_backlight_update_status,
+};
+
+void fbtft_register_backlight(struct fbtft_par *par)
+{
+ struct backlight_device *bd;
+ struct backlight_properties bl_props = { 0, };
+
+ if (!par->gpio.led[0]) {
+ fbtft_par_dbg(DEBUG_BACKLIGHT, par,
+ "%s(): led pin not set, exiting.\n", __func__);
+ return;
+ }
+
+ bl_props.type = BACKLIGHT_RAW;
+ /* Assume backlight is off, get polarity from current state of pin */
+ bl_props.power = FB_BLANK_POWERDOWN;
+ if (!gpiod_get_value(par->gpio.led[0]))
+ par->polarity = true;
+
+ bd = backlight_device_register(dev_driver_string(par->info->device),
+ par->info->device, par,
+ &fbtft_bl_ops, &bl_props);
+ if (IS_ERR(bd)) {
+ dev_err(par->info->device,
+ "cannot register backlight device (%ld)\n",
+ PTR_ERR(bd));
+ return;
+ }
+ par->info->bl_dev = bd;
+
+ if (!par->fbtftops.unregister_backlight)
+ par->fbtftops.unregister_backlight = fbtft_unregister_backlight;
+}
+EXPORT_SYMBOL(fbtft_register_backlight);
+
+static void fbtft_set_addr_win(struct fbtft_par *par, int xs, int ys, int xe,
+ int ye)
+{
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ (xs >> 8) & 0xFF, xs & 0xFF, (xe >> 8) & 0xFF, xe & 0xFF);
+
+ write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+ (ys >> 8) & 0xFF, ys & 0xFF, (ye >> 8) & 0xFF, ye & 0xFF);
+
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
+static void fbtft_reset(struct fbtft_par *par)
+{
+ if (!par->gpio.reset)
+ return;
+
+ fbtft_par_dbg(DEBUG_RESET, par, "%s()\n", __func__);
+
+ gpiod_set_value_cansleep(par->gpio.reset, 1);
+ usleep_range(20, 40);
+ gpiod_set_value_cansleep(par->gpio.reset, 0);
+ msleep(120);
+
+ gpiod_set_value_cansleep(par->gpio.cs, 1); /* Activate chip */
+}
+
+static void fbtft_update_display(struct fbtft_par *par, unsigned int start_line,
+ unsigned int end_line)
+{
+ size_t offset, len;
+ ktime_t ts_start, ts_end;
+ long fps, throughput;
+ bool timeit = false;
+ int ret = 0;
+
+ if (unlikely(par->debug & (DEBUG_TIME_FIRST_UPDATE |
+ DEBUG_TIME_EACH_UPDATE))) {
+ if ((par->debug & DEBUG_TIME_EACH_UPDATE) ||
+ ((par->debug & DEBUG_TIME_FIRST_UPDATE) &&
+ !par->first_update_done)) {
+ ts_start = ktime_get();
+ timeit = true;
+ }
+ }
+
+ /* Sanity checks */
+ if (start_line > end_line) {
+ dev_warn(par->info->device,
+ "%s: start_line=%u is larger than end_line=%u. Shouldn't happen, will do full display update\n",
+ __func__, start_line, end_line);
+ start_line = 0;
+ end_line = par->info->var.yres - 1;
+ }
+ if (start_line > par->info->var.yres - 1 ||
+ end_line > par->info->var.yres - 1) {
+ dev_warn(par->info->device,
+ "%s: start_line=%u or end_line=%u is larger than max=%d. Shouldn't happen, will do full display update\n",
+ __func__, start_line,
+ end_line, par->info->var.yres - 1);
+ start_line = 0;
+ end_line = par->info->var.yres - 1;
+ }
+
+ fbtft_par_dbg(DEBUG_UPDATE_DISPLAY, par, "%s(start_line=%u, end_line=%u)\n",
+ __func__, start_line, end_line);
+
+ if (par->fbtftops.set_addr_win)
+ par->fbtftops.set_addr_win(par, 0, start_line,
+ par->info->var.xres - 1, end_line);
+
+ offset = start_line * par->info->fix.line_length;
+ len = (end_line - start_line + 1) * par->info->fix.line_length;
+ ret = par->fbtftops.write_vmem(par, offset, len);
+ if (ret < 0)
+ dev_err(par->info->device,
+ "%s: write_vmem failed to update display buffer\n",
+ __func__);
+
+ if (unlikely(timeit)) {
+ ts_end = ktime_get();
+ if (!ktime_to_ns(par->update_time))
+ par->update_time = ts_start;
+
+ fps = ktime_us_delta(ts_start, par->update_time);
+ par->update_time = ts_start;
+ fps = fps ? 1000000 / fps : 0;
+
+ throughput = ktime_us_delta(ts_end, ts_start);
+ throughput = throughput ? (len * 1000) / throughput : 0;
+ throughput = throughput * 1000 / 1024;
+
+ dev_info(par->info->device,
+ "Display update: %ld kB/s, fps=%ld\n",
+ throughput, fps);
+ par->first_update_done = true;
+ }
+}
+
+static void fbtft_mkdirty(struct fb_info *info, int y, int height)
+{
+ struct fbtft_par *par = info->par;
+ struct fb_deferred_io *fbdefio = info->fbdefio;
+
+ /* special case, needed ? */
+ if (y == -1) {
+ y = 0;
+ height = info->var.yres;
+ }
+
+ /* Mark display lines/area as dirty */
+ spin_lock(&par->dirty_lock);
+ if (y < par->dirty_lines_start)
+ par->dirty_lines_start = y;
+ if (y + height - 1 > par->dirty_lines_end)
+ par->dirty_lines_end = y + height - 1;
+ spin_unlock(&par->dirty_lock);
+
+ /* Schedule deferred_io to update display (no-op if already on queue)*/
+ schedule_delayed_work(&info->deferred_work, fbdefio->delay);
+}
+
+static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagereflist)
+{
+ struct fbtft_par *par = info->par;
+ unsigned int dirty_lines_start, dirty_lines_end;
+ struct fb_deferred_io_pageref *pageref;
+ unsigned int y_low = 0, y_high = 0;
+ int count = 0;
+
+ spin_lock(&par->dirty_lock);
+ dirty_lines_start = par->dirty_lines_start;
+ dirty_lines_end = par->dirty_lines_end;
+ /* set display line markers as clean */
+ par->dirty_lines_start = par->info->var.yres - 1;
+ par->dirty_lines_end = 0;
+ spin_unlock(&par->dirty_lock);
+
+ /* Mark display lines as dirty */
+ list_for_each_entry(pageref, pagereflist, list) {
+ count++;
+ y_low = pageref->offset / info->fix.line_length;
+ y_high = (pageref->offset + PAGE_SIZE - 1) / info->fix.line_length;
+ dev_dbg(info->device,
+ "page->index=%lu y_low=%d y_high=%d\n",
+ pageref->page->index, y_low, y_high);
+ if (y_high > info->var.yres - 1)
+ y_high = info->var.yres - 1;
+ if (y_low < dirty_lines_start)
+ dirty_lines_start = y_low;
+ if (y_high > dirty_lines_end)
+ dirty_lines_end = y_high;
+ }
+
+ par->fbtftops.update_display(info->par,
+ dirty_lines_start, dirty_lines_end);
+}
+
+static void fbtft_fb_fillrect(struct fb_info *info,
+ const struct fb_fillrect *rect)
+{
+ struct fbtft_par *par = info->par;
+
+ dev_dbg(info->dev,
+ "%s: dx=%d, dy=%d, width=%d, height=%d\n",
+ __func__, rect->dx, rect->dy, rect->width, rect->height);
+ sys_fillrect(info, rect);
+
+ par->fbtftops.mkdirty(info, rect->dy, rect->height);
+}
+
+static void fbtft_fb_copyarea(struct fb_info *info,
+ const struct fb_copyarea *area)
+{
+ struct fbtft_par *par = info->par;
+
+ dev_dbg(info->dev,
+ "%s: dx=%d, dy=%d, width=%d, height=%d\n",
+ __func__, area->dx, area->dy, area->width, area->height);
+ sys_copyarea(info, area);
+
+ par->fbtftops.mkdirty(info, area->dy, area->height);
+}
+
+static void fbtft_fb_imageblit(struct fb_info *info,
+ const struct fb_image *image)
+{
+ struct fbtft_par *par = info->par;
+
+ dev_dbg(info->dev,
+ "%s: dx=%d, dy=%d, width=%d, height=%d\n",
+ __func__, image->dx, image->dy, image->width, image->height);
+ sys_imageblit(info, image);
+
+ par->fbtftops.mkdirty(info, image->dy, image->height);
+}
+
+static ssize_t fbtft_fb_write(struct fb_info *info, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct fbtft_par *par = info->par;
+ ssize_t res;
+
+ dev_dbg(info->dev,
+ "%s: count=%zd, ppos=%llu\n", __func__, count, *ppos);
+ res = fb_sys_write(info, buf, count, ppos);
+
+ /* TODO: only mark changed area update all for now */
+ par->fbtftops.mkdirty(info, -1, 0);
+
+ return res;
+}
+
+/* from pxafb.c */
+static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
+{
+ chan &= 0xffff;
+ chan >>= 16 - bf->length;
+ return chan << bf->offset;
+}
+
+static int fbtft_fb_setcolreg(unsigned int regno, unsigned int red,
+ unsigned int green, unsigned int blue,
+ unsigned int transp, struct fb_info *info)
+{
+ unsigned int val;
+ int ret = 1;
+
+ dev_dbg(info->dev,
+ "%s(regno=%u, red=0x%X, green=0x%X, blue=0x%X, trans=0x%X)\n",
+ __func__, regno, red, green, blue, transp);
+
+ switch (info->fix.visual) {
+ case FB_VISUAL_TRUECOLOR:
+ if (regno < 16) {
+ u32 *pal = info->pseudo_palette;
+
+ val = chan_to_field(red, &info->var.red);
+ val |= chan_to_field(green, &info->var.green);
+ val |= chan_to_field(blue, &info->var.blue);
+
+ pal[regno] = val;
+ ret = 0;
+ }
+ break;
+ }
+ return ret;
+}
+
+static int fbtft_fb_blank(int blank, struct fb_info *info)
+{
+ struct fbtft_par *par = info->par;
+ int ret = -EINVAL;
+
+ dev_dbg(info->dev, "%s(blank=%d)\n",
+ __func__, blank);
+
+ if (!par->fbtftops.blank)
+ return ret;
+
+ switch (blank) {
+ case FB_BLANK_POWERDOWN:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ ret = par->fbtftops.blank(par, true);
+ break;
+ case FB_BLANK_UNBLANK:
+ ret = par->fbtftops.blank(par, false);
+ break;
+ }
+ return ret;
+}
+
+static void fbtft_merge_fbtftops(struct fbtft_ops *dst, struct fbtft_ops *src)
+{
+ if (src->write)
+ dst->write = src->write;
+ if (src->read)
+ dst->read = src->read;
+ if (src->write_vmem)
+ dst->write_vmem = src->write_vmem;
+ if (src->write_register)
+ dst->write_register = src->write_register;
+ if (src->set_addr_win)
+ dst->set_addr_win = src->set_addr_win;
+ if (src->reset)
+ dst->reset = src->reset;
+ if (src->mkdirty)
+ dst->mkdirty = src->mkdirty;
+ if (src->update_display)
+ dst->update_display = src->update_display;
+ if (src->init_display)
+ dst->init_display = src->init_display;
+ if (src->blank)
+ dst->blank = src->blank;
+ if (src->request_gpios_match)
+ dst->request_gpios_match = src->request_gpios_match;
+ if (src->request_gpios)
+ dst->request_gpios = src->request_gpios;
+ if (src->verify_gpios)
+ dst->verify_gpios = src->verify_gpios;
+ if (src->register_backlight)
+ dst->register_backlight = src->register_backlight;
+ if (src->unregister_backlight)
+ dst->unregister_backlight = src->unregister_backlight;
+ if (src->set_var)
+ dst->set_var = src->set_var;
+ if (src->set_gamma)
+ dst->set_gamma = src->set_gamma;
+}
+
+/**
+ * fbtft_framebuffer_alloc - creates a new frame buffer info structure
+ *
+ * @display: pointer to structure describing the display
+ * @dev: pointer to the device for this fb, this can be NULL
+ * @pdata: platform data for the display in use
+ *
+ * Creates a new frame buffer info structure.
+ *
+ * Also creates and populates the following structures:
+ * info->fbops
+ * info->fbdefio
+ * info->pseudo_palette
+ * par->fbtftops
+ * par->txbuf
+ *
+ * Returns the new structure, or NULL if an error occurred.
+ *
+ */
+struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display,
+ struct device *dev,
+ struct fbtft_platform_data *pdata)
+{
+ struct fb_info *info;
+ struct fbtft_par *par;
+ struct fb_ops *fbops = NULL;
+ struct fb_deferred_io *fbdefio = NULL;
+ u8 *vmem = NULL;
+ void *txbuf = NULL;
+ void *buf = NULL;
+ unsigned int width;
+ unsigned int height;
+ int txbuflen = display->txbuflen;
+ unsigned int bpp = display->bpp;
+ unsigned int fps = display->fps;
+ int vmem_size;
+ const s16 *init_sequence = display->init_sequence;
+ char *gamma = display->gamma;
+ u32 *gamma_curves = NULL;
+
+ /* sanity check */
+ if (display->gamma_num * display->gamma_len >
+ FBTFT_GAMMA_MAX_VALUES_TOTAL) {
+ dev_err(dev, "FBTFT_GAMMA_MAX_VALUES_TOTAL=%d is exceeded\n",
+ FBTFT_GAMMA_MAX_VALUES_TOTAL);
+ return NULL;
+ }
+
+ /* defaults */
+ if (!fps)
+ fps = 20;
+ if (!bpp)
+ bpp = 16;
+
+ if (!pdata) {
+ dev_err(dev, "platform data is missing\n");
+ return NULL;
+ }
+
+ /* override driver values? */
+ if (pdata->fps)
+ fps = pdata->fps;
+ if (pdata->txbuflen)
+ txbuflen = pdata->txbuflen;
+ if (pdata->display.init_sequence)
+ init_sequence = pdata->display.init_sequence;
+ if (pdata->gamma)
+ gamma = pdata->gamma;
+ if (pdata->display.debug)
+ display->debug = pdata->display.debug;
+ if (pdata->display.backlight)
+ display->backlight = pdata->display.backlight;
+ if (pdata->display.width)
+ display->width = pdata->display.width;
+ if (pdata->display.height)
+ display->height = pdata->display.height;
+ if (pdata->display.buswidth)
+ display->buswidth = pdata->display.buswidth;
+ if (pdata->display.regwidth)
+ display->regwidth = pdata->display.regwidth;
+
+ display->debug |= debug;
+ fbtft_expand_debug_value(&display->debug);
+
+ switch (pdata->rotate) {
+ case 90:
+ case 270:
+ width = display->height;
+ height = display->width;
+ break;
+ default:
+ width = display->width;
+ height = display->height;
+ }
+
+ vmem_size = display->width * display->height * bpp / 8;
+ vmem = vzalloc(vmem_size);
+ if (!vmem)
+ goto alloc_fail;
+
+ fbops = devm_kzalloc(dev, sizeof(struct fb_ops), GFP_KERNEL);
+ if (!fbops)
+ goto alloc_fail;
+
+ fbdefio = devm_kzalloc(dev, sizeof(struct fb_deferred_io), GFP_KERNEL);
+ if (!fbdefio)
+ goto alloc_fail;
+
+ buf = devm_kzalloc(dev, 128, GFP_KERNEL);
+ if (!buf)
+ goto alloc_fail;
+
+ if (display->gamma_num && display->gamma_len) {
+ gamma_curves = devm_kcalloc(dev,
+ display->gamma_num *
+ display->gamma_len,
+ sizeof(gamma_curves[0]),
+ GFP_KERNEL);
+ if (!gamma_curves)
+ goto alloc_fail;
+ }
+
+ info = framebuffer_alloc(sizeof(struct fbtft_par), dev);
+ if (!info)
+ goto alloc_fail;
+
+ info->screen_buffer = vmem;
+ info->fbops = fbops;
+ info->fbdefio = fbdefio;
+
+ fbops->owner = dev->driver->owner;
+ fbops->fb_read = fb_sys_read;
+ fbops->fb_write = fbtft_fb_write;
+ fbops->fb_fillrect = fbtft_fb_fillrect;
+ fbops->fb_copyarea = fbtft_fb_copyarea;
+ fbops->fb_imageblit = fbtft_fb_imageblit;
+ fbops->fb_setcolreg = fbtft_fb_setcolreg;
+ fbops->fb_blank = fbtft_fb_blank;
+ fbops->fb_mmap = fb_deferred_io_mmap;
+
+ fbdefio->delay = HZ / fps;
+ fbdefio->sort_pagereflist = true;
+ fbdefio->deferred_io = fbtft_deferred_io;
+
+ snprintf(info->fix.id, sizeof(info->fix.id), "%s", dev->driver->name);
+ info->fix.type = FB_TYPE_PACKED_PIXELS;
+ info->fix.visual = FB_VISUAL_TRUECOLOR;
+ info->fix.xpanstep = 0;
+ info->fix.ypanstep = 0;
+ info->fix.ywrapstep = 0;
+ info->fix.line_length = width * bpp / 8;
+ info->fix.accel = FB_ACCEL_NONE;
+ info->fix.smem_len = vmem_size;
+ fb_deferred_io_init(info);
+
+ info->var.rotate = pdata->rotate;
+ info->var.xres = width;
+ info->var.yres = height;
+ info->var.xres_virtual = info->var.xres;
+ info->var.yres_virtual = info->var.yres;
+ info->var.bits_per_pixel = bpp;
+ info->var.nonstd = 1;
+
+ /* RGB565 */
+ info->var.red.offset = 11;
+ info->var.red.length = 5;
+ info->var.green.offset = 5;
+ info->var.green.length = 6;
+ info->var.blue.offset = 0;
+ info->var.blue.length = 5;
+ info->var.transp.offset = 0;
+ info->var.transp.length = 0;
+
+ info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;
+
+ par = info->par;
+ par->info = info;
+ par->pdata = pdata;
+ par->debug = display->debug;
+ par->buf = buf;
+ spin_lock_init(&par->dirty_lock);
+ par->bgr = pdata->bgr;
+ par->startbyte = pdata->startbyte;
+ par->init_sequence = init_sequence;
+ par->gamma.curves = gamma_curves;
+ par->gamma.num_curves = display->gamma_num;
+ par->gamma.num_values = display->gamma_len;
+ mutex_init(&par->gamma.lock);
+ info->pseudo_palette = par->pseudo_palette;
+
+ if (par->gamma.curves && gamma) {
+ if (fbtft_gamma_parse_str(par, par->gamma.curves, gamma,
+ strlen(gamma)))
+ goto release_framebuf;
+ }
+
+ /* Transmit buffer */
+ if (txbuflen == -1)
+ txbuflen = vmem_size + 2; /* add in case startbyte is used */
+ if (txbuflen >= vmem_size + 2)
+ txbuflen = 0;
+
+#ifdef __LITTLE_ENDIAN
+ if ((!txbuflen) && (bpp > 8))
+ txbuflen = PAGE_SIZE; /* need buffer for byteswapping */
+#endif
+
+ if (txbuflen > 0) {
+ txbuf = devm_kzalloc(par->info->device, txbuflen, GFP_KERNEL);
+ if (!txbuf)
+ goto release_framebuf;
+ par->txbuf.buf = txbuf;
+ par->txbuf.len = txbuflen;
+ }
+
+ /* default fbtft operations */
+ par->fbtftops.write = fbtft_write_spi;
+ par->fbtftops.read = fbtft_read_spi;
+ par->fbtftops.write_vmem = fbtft_write_vmem16_bus8;
+ par->fbtftops.write_register = fbtft_write_reg8_bus8;
+ par->fbtftops.set_addr_win = fbtft_set_addr_win;
+ par->fbtftops.reset = fbtft_reset;
+ par->fbtftops.mkdirty = fbtft_mkdirty;
+ par->fbtftops.update_display = fbtft_update_display;
+ if (display->backlight)
+ par->fbtftops.register_backlight = fbtft_register_backlight;
+
+ /* use driver provided functions */
+ fbtft_merge_fbtftops(&par->fbtftops, &display->fbtftops);
+
+ return info;
+
+release_framebuf:
+ framebuffer_release(info);
+
+alloc_fail:
+ vfree(vmem);
+
+ return NULL;
+}
+EXPORT_SYMBOL(fbtft_framebuffer_alloc);
+
+/**
+ * fbtft_framebuffer_release - frees up all memory used by the framebuffer
+ *
+ * @info: frame buffer info structure
+ *
+ */
+void fbtft_framebuffer_release(struct fb_info *info)
+{
+ fb_deferred_io_cleanup(info);
+ vfree(info->screen_buffer);
+ framebuffer_release(info);
+}
+EXPORT_SYMBOL(fbtft_framebuffer_release);
+
+/**
+ * fbtft_register_framebuffer - registers a tft frame buffer device
+ * @fb_info: frame buffer info structure
+ *
+ * Sets SPI driverdata if needed
+ * Requests needed gpios.
+ * Initializes display
+ * Updates display.
+ * Registers a frame buffer device @fb_info.
+ *
+ * Returns negative errno on error, or zero for success.
+ *
+ */
+int fbtft_register_framebuffer(struct fb_info *fb_info)
+{
+ int ret;
+ char text1[50] = "";
+ char text2[50] = "";
+ struct fbtft_par *par = fb_info->par;
+ struct spi_device *spi = par->spi;
+
+ /* sanity checks */
+ if (!par->fbtftops.init_display) {
+ dev_err(fb_info->device, "missing fbtftops.init_display()\n");
+ return -EINVAL;
+ }
+
+ if (spi)
+ spi_set_drvdata(spi, fb_info);
+ if (par->pdev)
+ platform_set_drvdata(par->pdev, fb_info);
+
+ ret = par->fbtftops.request_gpios(par);
+ if (ret < 0)
+ goto reg_fail;
+
+ if (par->fbtftops.verify_gpios) {
+ ret = par->fbtftops.verify_gpios(par);
+ if (ret < 0)
+ goto reg_fail;
+ }
+
+ ret = par->fbtftops.init_display(par);
+ if (ret < 0)
+ goto reg_fail;
+ if (par->fbtftops.set_var) {
+ ret = par->fbtftops.set_var(par);
+ if (ret < 0)
+ goto reg_fail;
+ }
+
+ /* update the entire display */
+ par->fbtftops.update_display(par, 0, par->info->var.yres - 1);
+
+ if (par->fbtftops.set_gamma && par->gamma.curves) {
+ ret = par->fbtftops.set_gamma(par, par->gamma.curves);
+ if (ret)
+ goto reg_fail;
+ }
+
+ if (par->fbtftops.register_backlight)
+ par->fbtftops.register_backlight(par);
+
+ ret = register_framebuffer(fb_info);
+ if (ret < 0)
+ goto reg_fail;
+
+ fbtft_sysfs_init(par);
+
+ if (par->txbuf.buf && par->txbuf.len >= 1024)
+ sprintf(text1, ", %zu KiB buffer memory", par->txbuf.len >> 10);
+ if (spi)
+ sprintf(text2, ", spi%d.%d at %d MHz", spi->master->bus_num,
+ spi->chip_select, spi->max_speed_hz / 1000000);
+ dev_info(fb_info->dev,
+ "%s frame buffer, %dx%d, %d KiB video memory%s, fps=%lu%s\n",
+ fb_info->fix.id, fb_info->var.xres, fb_info->var.yres,
+ fb_info->fix.smem_len >> 10, text1,
+ HZ / fb_info->fbdefio->delay, text2);
+
+ /* Turn on backlight if available */
+ if (fb_info->bl_dev) {
+ fb_info->bl_dev->props.power = FB_BLANK_UNBLANK;
+ fb_info->bl_dev->ops->update_status(fb_info->bl_dev);
+ }
+
+ return 0;
+
+reg_fail:
+ if (par->fbtftops.unregister_backlight)
+ par->fbtftops.unregister_backlight(par);
+
+ return ret;
+}
+EXPORT_SYMBOL(fbtft_register_framebuffer);
+
+/**
+ * fbtft_unregister_framebuffer - releases a tft frame buffer device
+ * @fb_info: frame buffer info structure
+ *
+ * Frees SPI driverdata if needed
+ * Frees gpios.
+ * Unregisters frame buffer device.
+ *
+ */
+int fbtft_unregister_framebuffer(struct fb_info *fb_info)
+{
+ struct fbtft_par *par = fb_info->par;
+
+ if (par->fbtftops.unregister_backlight)
+ par->fbtftops.unregister_backlight(par);
+ fbtft_sysfs_exit(par);
+ unregister_framebuffer(fb_info);
+
+ return 0;
+}
+EXPORT_SYMBOL(fbtft_unregister_framebuffer);
+
+/**
+ * fbtft_init_display_from_property() - Device Tree init_display() function
+ * @par: Driver data
+ *
+ * Return: 0 if successful, negative if error
+ */
+static int fbtft_init_display_from_property(struct fbtft_par *par)
+{
+ struct device *dev = par->info->device;
+ int buf[64], count, index, i, j, ret;
+ u32 *values;
+ u32 val;
+
+ count = device_property_count_u32(dev, "init");
+ if (count < 0)
+ return count;
+ if (count == 0)
+ return -EINVAL;
+
+ values = kmalloc_array(count + 1, sizeof(*values), GFP_KERNEL);
+ if (!values)
+ return -ENOMEM;
+
+ ret = device_property_read_u32_array(dev, "init", values, count);
+ if (ret)
+ goto out_free;
+
+ par->fbtftops.reset(par);
+
+ index = -1;
+ val = values[++index];
+
+ while (index < count) {
+ if (val & FBTFT_OF_INIT_CMD) {
+ val &= 0xFFFF;
+ i = 0;
+ while ((index < count) && !(val & 0xFFFF0000)) {
+ if (i > 63) {
+ dev_err(dev,
+ "%s: Maximum register values exceeded\n",
+ __func__);
+ ret = -EINVAL;
+ goto out_free;
+ }
+ buf[i++] = val;
+ val = values[++index];
+ }
+ /* make debug message */
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
+ "init: write_register:\n");
+ for (j = 0; j < i; j++)
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
+ "buf[%d] = %02X\n", j, buf[j]);
+
+ par->fbtftops.write_register(par, i,
+ buf[0], buf[1], buf[2], buf[3],
+ buf[4], buf[5], buf[6], buf[7],
+ buf[8], buf[9], buf[10], buf[11],
+ buf[12], buf[13], buf[14], buf[15],
+ buf[16], buf[17], buf[18], buf[19],
+ buf[20], buf[21], buf[22], buf[23],
+ buf[24], buf[25], buf[26], buf[27],
+ buf[28], buf[29], buf[30], buf[31],
+ buf[32], buf[33], buf[34], buf[35],
+ buf[36], buf[37], buf[38], buf[39],
+ buf[40], buf[41], buf[42], buf[43],
+ buf[44], buf[45], buf[46], buf[47],
+ buf[48], buf[49], buf[50], buf[51],
+ buf[52], buf[53], buf[54], buf[55],
+ buf[56], buf[57], buf[58], buf[59],
+ buf[60], buf[61], buf[62], buf[63]);
+ } else if (val & FBTFT_OF_INIT_DELAY) {
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
+ "init: msleep(%u)\n", val & 0xFFFF);
+ msleep(val & 0xFFFF);
+ val = values[++index];
+ } else {
+ dev_err(dev, "illegal init value 0x%X\n", val);
+ ret = -EINVAL;
+ goto out_free;
+ }
+ }
+
+out_free:
+ kfree(values);
+ return ret;
+}
+
+/**
+ * fbtft_init_display() - Generic init_display() function
+ * @par: Driver data
+ *
+ * Uses par->init_sequence to do the initialization
+ *
+ * Return: 0 if successful, negative if error
+ */
+int fbtft_init_display(struct fbtft_par *par)
+{
+ int buf[64];
+ int i;
+ int j;
+
+ /* sanity check */
+ if (!par->init_sequence) {
+ dev_err(par->info->device,
+ "error: init_sequence is not set\n");
+ return -EINVAL;
+ }
+
+ /* make sure stop marker exists */
+ for (i = 0; i < FBTFT_MAX_INIT_SEQUENCE; i++) {
+ if (par->init_sequence[i] == -3)
+ break;
+ }
+
+ if (i == FBTFT_MAX_INIT_SEQUENCE) {
+ dev_err(par->info->device,
+ "missing stop marker at end of init sequence\n");
+ return -EINVAL;
+ }
+
+ par->fbtftops.reset(par);
+
+ i = 0;
+ while (i < FBTFT_MAX_INIT_SEQUENCE) {
+ if (par->init_sequence[i] == -3) {
+ /* done */
+ return 0;
+ }
+ if (par->init_sequence[i] >= 0) {
+ dev_err(par->info->device,
+ "missing delimiter at position %d\n", i);
+ return -EINVAL;
+ }
+ if (par->init_sequence[i + 1] < 0) {
+ dev_err(par->info->device,
+ "missing value after delimiter %d at position %d\n",
+ par->init_sequence[i], i);
+ return -EINVAL;
+ }
+ switch (par->init_sequence[i]) {
+ case -1:
+ i++;
+
+ /* make debug message */
+ for (j = 0; par->init_sequence[i + 1 + j] >= 0; j++)
+ ;
+
+ fbtft_par_dbg_hex(DEBUG_INIT_DISPLAY, par, par->info->device,
+ s16, &par->init_sequence[i + 1], j,
+ "init: write(0x%02X)", par->init_sequence[i]);
+
+ /* Write */
+ j = 0;
+ while (par->init_sequence[i] >= 0) {
+ if (j > 63) {
+ dev_err(par->info->device,
+ "%s: Maximum register values exceeded\n",
+ __func__);
+ return -EINVAL;
+ }
+ buf[j++] = par->init_sequence[i++];
+ }
+ par->fbtftops.write_register(par, j,
+ buf[0], buf[1], buf[2], buf[3],
+ buf[4], buf[5], buf[6], buf[7],
+ buf[8], buf[9], buf[10], buf[11],
+ buf[12], buf[13], buf[14], buf[15],
+ buf[16], buf[17], buf[18], buf[19],
+ buf[20], buf[21], buf[22], buf[23],
+ buf[24], buf[25], buf[26], buf[27],
+ buf[28], buf[29], buf[30], buf[31],
+ buf[32], buf[33], buf[34], buf[35],
+ buf[36], buf[37], buf[38], buf[39],
+ buf[40], buf[41], buf[42], buf[43],
+ buf[44], buf[45], buf[46], buf[47],
+ buf[48], buf[49], buf[50], buf[51],
+ buf[52], buf[53], buf[54], buf[55],
+ buf[56], buf[57], buf[58], buf[59],
+ buf[60], buf[61], buf[62], buf[63]);
+ break;
+ case -2:
+ i++;
+ fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
+ "init: mdelay(%d)\n",
+ par->init_sequence[i]);
+ mdelay(par->init_sequence[i++]);
+ break;
+ default:
+ dev_err(par->info->device,
+ "unknown delimiter %d at position %d\n",
+ par->init_sequence[i], i);
+ return -EINVAL;
+ }
+ }
+
+ dev_err(par->info->device,
+ "%s: something is wrong. Shouldn't get here.\n", __func__);
+ return -EINVAL;
+}
+EXPORT_SYMBOL(fbtft_init_display);
+
+/**
+ * fbtft_verify_gpios() - Generic verify_gpios() function
+ * @par: Driver data
+ *
+ * Uses @spi, @pdev and @buswidth to determine which GPIOs is needed
+ *
+ * Return: 0 if successful, negative if error
+ */
+static int fbtft_verify_gpios(struct fbtft_par *par)
+{
+ struct fbtft_platform_data *pdata = par->pdata;
+ int i;
+
+ fbtft_par_dbg(DEBUG_VERIFY_GPIOS, par, "%s()\n", __func__);
+
+ if (pdata->display.buswidth != 9 && par->startbyte == 0 &&
+ !par->gpio.dc) {
+ dev_err(par->info->device,
+ "Missing info about 'dc' gpio. Aborting.\n");
+ return -EINVAL;
+ }
+
+ if (!par->pdev)
+ return 0;
+
+ if (!par->gpio.wr) {
+ dev_err(par->info->device, "Missing 'wr' gpio. Aborting.\n");
+ return -EINVAL;
+ }
+ for (i = 0; i < pdata->display.buswidth; i++) {
+ if (!par->gpio.db[i]) {
+ dev_err(par->info->device,
+ "Missing 'db%02d' gpio. Aborting.\n", i);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* returns 0 if the property is not present */
+static u32 fbtft_property_value(struct device *dev, const char *propname)
+{
+ int ret;
+ u32 val = 0;
+
+ ret = device_property_read_u32(dev, propname, &val);
+ if (ret == 0)
+ dev_info(dev, "%s: %s = %u\n", __func__, propname, val);
+
+ return val;
+}
+
+static struct fbtft_platform_data *fbtft_properties_read(struct device *dev)
+{
+ struct fbtft_platform_data *pdata;
+
+ if (!dev_fwnode(dev)) {
+ dev_err(dev, "Missing platform data or properties\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->display.width = fbtft_property_value(dev, "width");
+ pdata->display.height = fbtft_property_value(dev, "height");
+ pdata->display.regwidth = fbtft_property_value(dev, "regwidth");
+ pdata->display.buswidth = fbtft_property_value(dev, "buswidth");
+ pdata->display.backlight = fbtft_property_value(dev, "backlight");
+ pdata->display.bpp = fbtft_property_value(dev, "bpp");
+ pdata->display.debug = fbtft_property_value(dev, "debug");
+ pdata->rotate = fbtft_property_value(dev, "rotate");
+ pdata->bgr = device_property_read_bool(dev, "bgr");
+ pdata->fps = fbtft_property_value(dev, "fps");
+ pdata->txbuflen = fbtft_property_value(dev, "txbuflen");
+ pdata->startbyte = fbtft_property_value(dev, "startbyte");
+ device_property_read_string(dev, "gamma", (const char **)&pdata->gamma);
+
+ if (device_property_present(dev, "led-gpios"))
+ pdata->display.backlight = 1;
+ if (device_property_present(dev, "init"))
+ pdata->display.fbtftops.init_display =
+ fbtft_init_display_from_property;
+
+ pdata->display.fbtftops.request_gpios = fbtft_request_gpios;
+
+ return pdata;
+}
+
+/**
+ * fbtft_probe_common() - Generic device probe() helper function
+ * @display: Display properties
+ * @sdev: SPI device
+ * @pdev: Platform device
+ *
+ * Allocates, initializes and registers a framebuffer
+ *
+ * Either @sdev or @pdev should be NULL
+ *
+ * Return: 0 if successful, negative if error
+ */
+int fbtft_probe_common(struct fbtft_display *display,
+ struct spi_device *sdev,
+ struct platform_device *pdev)
+{
+ struct device *dev;
+ struct fb_info *info;
+ struct fbtft_par *par;
+ struct fbtft_platform_data *pdata;
+ int ret;
+
+ if (sdev)
+ dev = &sdev->dev;
+ else
+ dev = &pdev->dev;
+
+ if (unlikely(display->debug & DEBUG_DRIVER_INIT_FUNCTIONS))
+ dev_info(dev, "%s()\n", __func__);
+
+ pdata = dev->platform_data;
+ if (!pdata) {
+ pdata = fbtft_properties_read(dev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ }
+
+ info = fbtft_framebuffer_alloc(display, dev, pdata);
+ if (!info)
+ return -ENOMEM;
+
+ par = info->par;
+ par->spi = sdev;
+ par->pdev = pdev;
+
+ if (display->buswidth == 0) {
+ dev_err(dev, "buswidth is not set\n");
+ return -EINVAL;
+ }
+
+ /* write register functions */
+ if (display->regwidth == 8 && display->buswidth == 8)
+ par->fbtftops.write_register = fbtft_write_reg8_bus8;
+ else if (display->regwidth == 8 && display->buswidth == 9 && par->spi)
+ par->fbtftops.write_register = fbtft_write_reg8_bus9;
+ else if (display->regwidth == 16 && display->buswidth == 8)
+ par->fbtftops.write_register = fbtft_write_reg16_bus8;
+ else if (display->regwidth == 16 && display->buswidth == 16)
+ par->fbtftops.write_register = fbtft_write_reg16_bus16;
+ else
+ dev_warn(dev,
+ "no default functions for regwidth=%d and buswidth=%d\n",
+ display->regwidth, display->buswidth);
+
+ /* write_vmem() functions */
+ if (display->buswidth == 8)
+ par->fbtftops.write_vmem = fbtft_write_vmem16_bus8;
+ else if (display->buswidth == 9)
+ par->fbtftops.write_vmem = fbtft_write_vmem16_bus9;
+ else if (display->buswidth == 16)
+ par->fbtftops.write_vmem = fbtft_write_vmem16_bus16;
+
+ /* GPIO write() functions */
+ if (par->pdev) {
+ if (display->buswidth == 8)
+ par->fbtftops.write = fbtft_write_gpio8_wr;
+ else if (display->buswidth == 16)
+ par->fbtftops.write = fbtft_write_gpio16_wr;
+ }
+
+ /* 9-bit SPI setup */
+ if (par->spi && display->buswidth == 9) {
+ if (par->spi->master->bits_per_word_mask & SPI_BPW_MASK(9)) {
+ par->spi->bits_per_word = 9;
+ } else {
+ dev_warn(&par->spi->dev,
+ "9-bit SPI not available, emulating using 8-bit.\n");
+ /* allocate buffer with room for dc bits */
+ par->extra = devm_kzalloc(par->info->device,
+ par->txbuf.len +
+ (par->txbuf.len / 8) + 8,
+ GFP_KERNEL);
+ if (!par->extra) {
+ ret = -ENOMEM;
+ goto out_release;
+ }
+ par->fbtftops.write = fbtft_write_spi_emulate_9;
+ }
+ }
+
+ if (!par->fbtftops.verify_gpios)
+ par->fbtftops.verify_gpios = fbtft_verify_gpios;
+
+ /* make sure we still use the driver provided functions */
+ fbtft_merge_fbtftops(&par->fbtftops, &display->fbtftops);
+
+ /* use init_sequence if provided */
+ if (par->init_sequence)
+ par->fbtftops.init_display = fbtft_init_display;
+
+ /* use platform_data provided functions above all */
+ fbtft_merge_fbtftops(&par->fbtftops, &pdata->display.fbtftops);
+
+ ret = fbtft_register_framebuffer(info);
+ if (ret < 0)
+ goto out_release;
+
+ return 0;
+
+out_release:
+ fbtft_framebuffer_release(info);
+
+ return ret;
+}
+EXPORT_SYMBOL(fbtft_probe_common);
+
+/**
+ * fbtft_remove_common() - Generic device remove() helper function
+ * @dev: Device
+ * @info: Framebuffer
+ *
+ * Unregisters and releases the framebuffer
+ */
+void fbtft_remove_common(struct device *dev, struct fb_info *info)
+{
+ struct fbtft_par *par;
+
+ par = info->par;
+ if (par)
+ fbtft_par_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, par,
+ "%s()\n", __func__);
+ fbtft_unregister_framebuffer(info);
+ fbtft_framebuffer_release(info);
+}
+EXPORT_SYMBOL(fbtft_remove_common);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/fbtft/fbtft-io.c b/drivers/staging/fbtft/fbtft-io.c
new file mode 100644
index 000000000..de1904a44
--- /dev/null
+++ b/drivers/staging/fbtft/fbtft-io.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/export.h>
+#include <linux/errno.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include "fbtft.h"
+
+int fbtft_write_spi(struct fbtft_par *par, void *buf, size_t len)
+{
+ struct spi_transfer t = {
+ .tx_buf = buf,
+ .len = len,
+ };
+ struct spi_message m;
+
+ fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
+ "%s(len=%zu): ", __func__, len);
+
+ if (!par->spi) {
+ dev_err(par->info->device,
+ "%s: par->spi is unexpectedly NULL\n", __func__);
+ return -1;
+ }
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ return spi_sync(par->spi, &m);
+}
+EXPORT_SYMBOL(fbtft_write_spi);
+
+/**
+ * fbtft_write_spi_emulate_9() - write SPI emulating 9-bit
+ * @par: Driver data
+ * @buf: Buffer to write
+ * @len: Length of buffer (must be divisible by 8)
+ *
+ * When 9-bit SPI is not available, this function can be used to emulate that.
+ * par->extra must hold a transformation buffer used for transfer.
+ */
+int fbtft_write_spi_emulate_9(struct fbtft_par *par, void *buf, size_t len)
+{
+ u16 *src = buf;
+ u8 *dst = par->extra;
+ size_t size = len / 2;
+ size_t added = 0;
+ int bits, i, j;
+ u64 val, dc, tmp;
+
+ fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
+ "%s(len=%zu): ", __func__, len);
+
+ if (!par->extra) {
+ dev_err(par->info->device, "%s: error: par->extra is NULL\n",
+ __func__);
+ return -EINVAL;
+ }
+ if ((len % 8) != 0) {
+ dev_err(par->info->device,
+ "error: len=%zu must be divisible by 8\n", len);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < size; i += 8) {
+ tmp = 0;
+ bits = 63;
+ for (j = 0; j < 7; j++) {
+ dc = (*src & 0x0100) ? 1 : 0;
+ val = *src & 0x00FF;
+ tmp |= dc << bits;
+ bits -= 8;
+ tmp |= val << bits--;
+ src++;
+ }
+ tmp |= ((*src & 0x0100) ? 1 : 0);
+ *(__be64 *)dst = cpu_to_be64(tmp);
+ dst += 8;
+ *dst++ = (u8)(*src++ & 0x00FF);
+ added++;
+ }
+
+ return spi_write(par->spi, par->extra, size + added);
+}
+EXPORT_SYMBOL(fbtft_write_spi_emulate_9);
+
+int fbtft_read_spi(struct fbtft_par *par, void *buf, size_t len)
+{
+ int ret;
+ u8 txbuf[32] = { 0, };
+ struct spi_transfer t = {
+ .speed_hz = 2000000,
+ .rx_buf = buf,
+ .len = len,
+ };
+ struct spi_message m;
+
+ if (!par->spi) {
+ dev_err(par->info->device,
+ "%s: par->spi is unexpectedly NULL\n", __func__);
+ return -ENODEV;
+ }
+
+ if (par->startbyte) {
+ if (len > 32) {
+ dev_err(par->info->device,
+ "len=%zu can't be larger than 32 when using 'startbyte'\n",
+ len);
+ return -EINVAL;
+ }
+ txbuf[0] = par->startbyte | 0x3;
+ t.tx_buf = txbuf;
+ fbtft_par_dbg_hex(DEBUG_READ, par, par->info->device, u8,
+ txbuf, len, "%s(len=%zu) txbuf => ",
+ __func__, len);
+ }
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(par->spi, &m);
+ fbtft_par_dbg_hex(DEBUG_READ, par, par->info->device, u8, buf, len,
+ "%s(len=%zu) buf <= ", __func__, len);
+
+ return ret;
+}
+EXPORT_SYMBOL(fbtft_read_spi);
+
+/*
+ * Optimized use of gpiolib is twice as fast as no optimization
+ * only one driver can use the optimized version at a time
+ */
+int fbtft_write_gpio8_wr(struct fbtft_par *par, void *buf, size_t len)
+{
+ u8 data;
+ int i;
+#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
+ static u8 prev_data;
+#endif
+
+ fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
+ "%s(len=%zu): ", __func__, len);
+
+ while (len--) {
+ data = *(u8 *)buf;
+
+ /* Start writing by pulling down /WR */
+ gpiod_set_value(par->gpio.wr, 1);
+
+ /* Set data */
+#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
+ if (data == prev_data) {
+ gpiod_set_value(par->gpio.wr, 1); /* used as delay */
+ } else {
+ for (i = 0; i < 8; i++) {
+ if ((data & 1) != (prev_data & 1))
+ gpiod_set_value(par->gpio.db[i],
+ data & 1);
+ data >>= 1;
+ prev_data >>= 1;
+ }
+ }
+#else
+ for (i = 0; i < 8; i++) {
+ gpiod_set_value(par->gpio.db[i], data & 1);
+ data >>= 1;
+ }
+#endif
+
+ /* Pullup /WR */
+ gpiod_set_value(par->gpio.wr, 0);
+
+#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
+ prev_data = *(u8 *)buf;
+#endif
+ buf++;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(fbtft_write_gpio8_wr);
+
+int fbtft_write_gpio16_wr(struct fbtft_par *par, void *buf, size_t len)
+{
+ u16 data;
+ int i;
+#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
+ static u16 prev_data;
+#endif
+
+ fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
+ "%s(len=%zu): ", __func__, len);
+
+ while (len) {
+ data = *(u16 *)buf;
+
+ /* Start writing by pulling down /WR */
+ gpiod_set_value(par->gpio.wr, 1);
+
+ /* Set data */
+#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
+ if (data == prev_data) {
+ gpiod_set_value(par->gpio.wr, 1); /* used as delay */
+ } else {
+ for (i = 0; i < 16; i++) {
+ if ((data & 1) != (prev_data & 1))
+ gpiod_set_value(par->gpio.db[i],
+ data & 1);
+ data >>= 1;
+ prev_data >>= 1;
+ }
+ }
+#else
+ for (i = 0; i < 16; i++) {
+ gpiod_set_value(par->gpio.db[i], data & 1);
+ data >>= 1;
+ }
+#endif
+
+ /* Pullup /WR */
+ gpiod_set_value(par->gpio.wr, 0);
+
+#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
+ prev_data = *(u16 *)buf;
+#endif
+ buf += 2;
+ len -= 2;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(fbtft_write_gpio16_wr);
+
+int fbtft_write_gpio16_wr_latched(struct fbtft_par *par, void *buf, size_t len)
+{
+ dev_err(par->info->device, "%s: function not implemented\n", __func__);
+ return -1;
+}
+EXPORT_SYMBOL(fbtft_write_gpio16_wr_latched);
diff --git a/drivers/staging/fbtft/fbtft-sysfs.c b/drivers/staging/fbtft/fbtft-sysfs.c
new file mode 100644
index 000000000..39e8d2806
--- /dev/null
+++ b/drivers/staging/fbtft/fbtft-sysfs.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "fbtft.h"
+#include "internal.h"
+
+static int get_next_ulong(char **str_p, unsigned long *val, char *sep, int base)
+{
+ char *p_val;
+
+ if (!str_p || !(*str_p))
+ return -EINVAL;
+
+ p_val = strsep(str_p, sep);
+
+ if (!p_val)
+ return -EINVAL;
+
+ return kstrtoul(p_val, base, val);
+}
+
+int fbtft_gamma_parse_str(struct fbtft_par *par, u32 *curves,
+ const char *str, int size)
+{
+ char *str_p, *curve_p = NULL;
+ char *tmp;
+ unsigned long val = 0;
+ int ret = 0;
+ int curve_counter, value_counter;
+ int _count;
+
+ fbtft_par_dbg(DEBUG_SYSFS, par, "%s() str=\n", __func__);
+
+ if (!str || !curves)
+ return -EINVAL;
+
+ fbtft_par_dbg(DEBUG_SYSFS, par, "%s\n", str);
+
+ tmp = kmemdup(str, size + 1, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ /* replace optional separators */
+ str_p = tmp;
+ while (*str_p) {
+ if (*str_p == ',')
+ *str_p = ' ';
+ if (*str_p == ';')
+ *str_p = '\n';
+ str_p++;
+ }
+
+ str_p = strim(tmp);
+
+ curve_counter = 0;
+ while (str_p) {
+ if (curve_counter == par->gamma.num_curves) {
+ dev_err(par->info->device, "Gamma: Too many curves\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ curve_p = strsep(&str_p, "\n");
+ value_counter = 0;
+ while (curve_p) {
+ if (value_counter == par->gamma.num_values) {
+ dev_err(par->info->device,
+ "Gamma: Too many values\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = get_next_ulong(&curve_p, &val, " ", 16);
+ if (ret)
+ goto out;
+
+ _count = curve_counter * par->gamma.num_values +
+ value_counter;
+ curves[_count] = val;
+ value_counter++;
+ }
+ if (value_counter != par->gamma.num_values) {
+ dev_err(par->info->device, "Gamma: Too few values\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ curve_counter++;
+ }
+ if (curve_counter != par->gamma.num_curves) {
+ dev_err(par->info->device, "Gamma: Too few curves\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+out:
+ kfree(tmp);
+ return ret;
+}
+
+static ssize_t
+sprintf_gamma(struct fbtft_par *par, u32 *curves, char *buf)
+{
+ ssize_t len = 0;
+ unsigned int i, j;
+
+ mutex_lock(&par->gamma.lock);
+ for (i = 0; i < par->gamma.num_curves; i++) {
+ for (j = 0; j < par->gamma.num_values; j++)
+ len += scnprintf(&buf[len], PAGE_SIZE,
+ "%04x ", curves[i * par->gamma.num_values + j]);
+ buf[len - 1] = '\n';
+ }
+ mutex_unlock(&par->gamma.lock);
+
+ return len;
+}
+
+static ssize_t store_gamma_curve(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ struct fbtft_par *par = fb_info->par;
+ u32 tmp_curves[FBTFT_GAMMA_MAX_VALUES_TOTAL];
+ int ret;
+
+ ret = fbtft_gamma_parse_str(par, tmp_curves, buf, count);
+ if (ret)
+ return ret;
+
+ ret = par->fbtftops.set_gamma(par, tmp_curves);
+ if (ret)
+ return ret;
+
+ mutex_lock(&par->gamma.lock);
+ memcpy(par->gamma.curves, tmp_curves,
+ par->gamma.num_curves * par->gamma.num_values *
+ sizeof(tmp_curves[0]));
+ mutex_unlock(&par->gamma.lock);
+
+ return count;
+}
+
+static ssize_t show_gamma_curve(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ struct fbtft_par *par = fb_info->par;
+
+ return sprintf_gamma(par, par->gamma.curves, buf);
+}
+
+static struct device_attribute gamma_device_attrs[] = {
+ __ATTR(gamma, 0660, show_gamma_curve, store_gamma_curve),
+};
+
+void fbtft_expand_debug_value(unsigned long *debug)
+{
+ switch (*debug & 0x7) {
+ case 1:
+ *debug |= DEBUG_LEVEL_1;
+ break;
+ case 2:
+ *debug |= DEBUG_LEVEL_2;
+ break;
+ case 3:
+ *debug |= DEBUG_LEVEL_3;
+ break;
+ case 4:
+ *debug |= DEBUG_LEVEL_4;
+ break;
+ case 5:
+ *debug |= DEBUG_LEVEL_5;
+ break;
+ case 6:
+ *debug |= DEBUG_LEVEL_6;
+ break;
+ case 7:
+ *debug = 0xFFFFFFFF;
+ break;
+ }
+}
+
+static ssize_t store_debug(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ struct fbtft_par *par = fb_info->par;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &par->debug);
+ if (ret)
+ return ret;
+ fbtft_expand_debug_value(&par->debug);
+
+ return count;
+}
+
+static ssize_t show_debug(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ struct fbtft_par *par = fb_info->par;
+
+ return sysfs_emit(buf, "%lu\n", par->debug);
+}
+
+static struct device_attribute debug_device_attr =
+ __ATTR(debug, 0660, show_debug, store_debug);
+
+void fbtft_sysfs_init(struct fbtft_par *par)
+{
+ device_create_file(par->info->dev, &debug_device_attr);
+ if (par->gamma.curves && par->fbtftops.set_gamma)
+ device_create_file(par->info->dev, &gamma_device_attrs[0]);
+}
+
+void fbtft_sysfs_exit(struct fbtft_par *par)
+{
+ device_remove_file(par->info->dev, &debug_device_attr);
+ if (par->gamma.curves && par->fbtftops.set_gamma)
+ device_remove_file(par->info->dev, &gamma_device_attrs[0]);
+}
diff --git a/drivers/staging/fbtft/fbtft.h b/drivers/staging/fbtft/fbtft.h
new file mode 100644
index 000000000..2c2b5f1c1
--- /dev/null
+++ b/drivers/staging/fbtft/fbtft.h
@@ -0,0 +1,444 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2013 Noralf Tronnes */
+
+#ifndef __LINUX_FBTFT_H
+#define __LINUX_FBTFT_H
+
+#include <linux/fb.h>
+#include <linux/spinlock.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+
+#define FBTFT_ONBOARD_BACKLIGHT 2
+
+#define FBTFT_GPIO_NO_MATCH 0xFFFF
+#define FBTFT_GPIO_NAME_SIZE 32
+#define FBTFT_MAX_INIT_SEQUENCE 512
+#define FBTFT_GAMMA_MAX_VALUES_TOTAL 128
+
+#define FBTFT_OF_INIT_CMD BIT(24)
+#define FBTFT_OF_INIT_DELAY BIT(25)
+
+/**
+ * struct fbtft_gpio - Structure that holds one pinname to gpio mapping
+ * @name: pinname (reset, dc, etc.)
+ * @gpio: GPIO number
+ *
+ */
+struct fbtft_gpio {
+ char name[FBTFT_GPIO_NAME_SIZE];
+ struct gpio_desc *gpio;
+};
+
+struct fbtft_par;
+
+/**
+ * struct fbtft_ops - FBTFT operations structure
+ * @write: Writes to interface bus
+ * @read: Reads from interface bus
+ * @write_vmem: Writes video memory to display
+ * @write_reg: Writes to controller register
+ * @set_addr_win: Set the GRAM update window
+ * @reset: Reset the LCD controller
+ * @mkdirty: Marks display lines for update
+ * @update_display: Updates the display
+ * @init_display: Initializes the display
+ * @blank: Blank the display (optional)
+ * @request_gpios_match: Do pinname to gpio matching
+ * @request_gpios: Request gpios from the kernel
+ * @free_gpios: Free previously requested gpios
+ * @verify_gpios: Verify that necessary gpios is present (optional)
+ * @register_backlight: Used to register backlight device (optional)
+ * @unregister_backlight: Unregister backlight device (optional)
+ * @set_var: Configure LCD with values from variables like @rotate and @bgr
+ * (optional)
+ * @set_gamma: Set Gamma curve (optional)
+ *
+ * Most of these operations have default functions assigned to them in
+ * fbtft_framebuffer_alloc()
+ */
+struct fbtft_ops {
+ int (*write)(struct fbtft_par *par, void *buf, size_t len);
+ int (*read)(struct fbtft_par *par, void *buf, size_t len);
+ int (*write_vmem)(struct fbtft_par *par, size_t offset, size_t len);
+ void (*write_register)(struct fbtft_par *par, int len, ...);
+
+ void (*set_addr_win)(struct fbtft_par *par,
+ int xs, int ys, int xe, int ye);
+ void (*reset)(struct fbtft_par *par);
+ void (*mkdirty)(struct fb_info *info, int from, int to);
+ void (*update_display)(struct fbtft_par *par,
+ unsigned int start_line, unsigned int end_line);
+ int (*init_display)(struct fbtft_par *par);
+ int (*blank)(struct fbtft_par *par, bool on);
+
+ unsigned long (*request_gpios_match)(struct fbtft_par *par,
+ const struct fbtft_gpio *gpio);
+ int (*request_gpios)(struct fbtft_par *par);
+ int (*verify_gpios)(struct fbtft_par *par);
+
+ void (*register_backlight)(struct fbtft_par *par);
+ void (*unregister_backlight)(struct fbtft_par *par);
+
+ int (*set_var)(struct fbtft_par *par);
+ int (*set_gamma)(struct fbtft_par *par, u32 *curves);
+};
+
+/**
+ * struct fbtft_display - Describes the display properties
+ * @width: Width of display in pixels
+ * @height: Height of display in pixels
+ * @regwidth: LCD Controller Register width in bits
+ * @buswidth: Display interface bus width in bits
+ * @backlight: Backlight type.
+ * @fbtftops: FBTFT operations provided by driver or device (platform_data)
+ * @bpp: Bits per pixel
+ * @fps: Frames per second
+ * @txbuflen: Size of transmit buffer
+ * @init_sequence: Pointer to LCD initialization array
+ * @gamma: String representation of Gamma curve(s)
+ * @gamma_num: Number of Gamma curves
+ * @gamma_len: Number of values per Gamma curve
+ * @debug: Initial debug value
+ *
+ * This structure is not stored by FBTFT except for init_sequence.
+ */
+struct fbtft_display {
+ unsigned int width;
+ unsigned int height;
+ unsigned int regwidth;
+ unsigned int buswidth;
+ unsigned int backlight;
+ struct fbtft_ops fbtftops;
+ unsigned int bpp;
+ unsigned int fps;
+ int txbuflen;
+ const s16 *init_sequence;
+ char *gamma;
+ int gamma_num;
+ int gamma_len;
+ unsigned long debug;
+};
+
+/**
+ * struct fbtft_platform_data - Passes display specific data to the driver
+ * @display: Display properties
+ * @gpios: Pointer to an array of pinname to gpio mappings
+ * @rotate: Display rotation angle
+ * @bgr: LCD Controller BGR bit
+ * @fps: Frames per second (this will go away, use @fps in @fbtft_display)
+ * @txbuflen: Size of transmit buffer
+ * @startbyte: When set, enables use of Startbyte in transfers
+ * @gamma: String representation of Gamma curve(s)
+ * @extra: A way to pass extra info
+ */
+struct fbtft_platform_data {
+ struct fbtft_display display;
+ unsigned int rotate;
+ bool bgr;
+ unsigned int fps;
+ int txbuflen;
+ u8 startbyte;
+ char *gamma;
+ void *extra;
+};
+
+/**
+ * struct fbtft_par - Main FBTFT data structure
+ *
+ * This structure holds all relevant data to operate the display
+ *
+ * See sourcefile for documentation since nested structs is not
+ * supported by kernel-doc.
+ *
+ */
+/* @spi: Set if it is a SPI device
+ * @pdev: Set if it is a platform device
+ * @info: Pointer to framebuffer fb_info structure
+ * @pdata: Pointer to platform data
+ * @ssbuf: Not used
+ * @pseudo_palette: Used by fb_set_colreg()
+ * @txbuf.buf: Transmit buffer
+ * @txbuf.len: Transmit buffer length
+ * @buf: Small buffer used when writing init data over SPI
+ * @startbyte: Used by some controllers when in SPI mode.
+ * Format: 6 bit Device id + RS bit + RW bit
+ * @fbtftops: FBTFT operations provided by driver or device (platform_data)
+ * @dirty_lock: Protects dirty_lines_start and dirty_lines_end
+ * @dirty_lines_start: Where to begin updating display
+ * @dirty_lines_end: Where to end updating display
+ * @gpio.reset: GPIO used to reset display
+ * @gpio.dc: Data/Command signal, also known as RS
+ * @gpio.rd: Read latching signal
+ * @gpio.wr: Write latching signal
+ * @gpio.latch: Bus latch signal, eg. 16->8 bit bus latch
+ * @gpio.cs: LCD Chip Select with parallel interface bus
+ * @gpio.db[16]: Parallel databus
+ * @gpio.led[16]: Led control signals
+ * @gpio.aux[16]: Auxiliary signals, not used by core
+ * @init_sequence: Pointer to LCD initialization array
+ * @gamma.lock: Mutex for Gamma curve locking
+ * @gamma.curves: Pointer to Gamma curve array
+ * @gamma.num_values: Number of values per Gamma curve
+ * @gamma.num_curves: Number of Gamma curves
+ * @debug: Pointer to debug value
+ * @current_debug:
+ * @first_update_done: Used to only time the first display update
+ * @update_time: Used to calculate 'fps' in debug output
+ * @bgr: BGR mode/\n
+ * @extra: Extra info needed by driver
+ */
+struct fbtft_par {
+ struct spi_device *spi;
+ struct platform_device *pdev;
+ struct fb_info *info;
+ struct fbtft_platform_data *pdata;
+ u16 *ssbuf;
+ u32 pseudo_palette[16];
+ struct {
+ void *buf;
+ size_t len;
+ } txbuf;
+ u8 *buf;
+ u8 startbyte;
+ struct fbtft_ops fbtftops;
+ spinlock_t dirty_lock;
+ unsigned int dirty_lines_start;
+ unsigned int dirty_lines_end;
+ struct {
+ struct gpio_desc *reset;
+ struct gpio_desc *dc;
+ struct gpio_desc *rd;
+ struct gpio_desc *wr;
+ struct gpio_desc *latch;
+ struct gpio_desc *cs;
+ struct gpio_desc *db[16];
+ struct gpio_desc *led[16];
+ struct gpio_desc *aux[16];
+ } gpio;
+ const s16 *init_sequence;
+ struct {
+ struct mutex lock;
+ u32 *curves;
+ int num_values;
+ int num_curves;
+ } gamma;
+ unsigned long debug;
+ bool first_update_done;
+ ktime_t update_time;
+ bool bgr;
+ void *extra;
+ bool polarity;
+};
+
+#define NUMARGS(...) (sizeof((int[]){__VA_ARGS__}) / sizeof(int))
+
+#define write_reg(par, ...) \
+ ((par)->fbtftops.write_register(par, NUMARGS(__VA_ARGS__), __VA_ARGS__))
+
+/* fbtft-core.c */
+int fbtft_write_buf_dc(struct fbtft_par *par, void *buf, size_t len, int dc);
+__printf(5, 6)
+void fbtft_dbg_hex(const struct device *dev, int groupsize,
+ const void *buf, size_t len, const char *fmt, ...);
+struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display,
+ struct device *dev,
+ struct fbtft_platform_data *pdata);
+void fbtft_framebuffer_release(struct fb_info *info);
+int fbtft_register_framebuffer(struct fb_info *fb_info);
+int fbtft_unregister_framebuffer(struct fb_info *fb_info);
+void fbtft_register_backlight(struct fbtft_par *par);
+void fbtft_unregister_backlight(struct fbtft_par *par);
+int fbtft_init_display(struct fbtft_par *par);
+int fbtft_probe_common(struct fbtft_display *display, struct spi_device *sdev,
+ struct platform_device *pdev);
+void fbtft_remove_common(struct device *dev, struct fb_info *info);
+
+/* fbtft-io.c */
+int fbtft_write_spi(struct fbtft_par *par, void *buf, size_t len);
+int fbtft_write_spi_emulate_9(struct fbtft_par *par, void *buf, size_t len);
+int fbtft_read_spi(struct fbtft_par *par, void *buf, size_t len);
+int fbtft_write_gpio8_wr(struct fbtft_par *par, void *buf, size_t len);
+int fbtft_write_gpio16_wr(struct fbtft_par *par, void *buf, size_t len);
+int fbtft_write_gpio16_wr_latched(struct fbtft_par *par, void *buf, size_t len);
+
+/* fbtft-bus.c */
+int fbtft_write_vmem8_bus8(struct fbtft_par *par, size_t offset, size_t len);
+int fbtft_write_vmem16_bus16(struct fbtft_par *par, size_t offset, size_t len);
+int fbtft_write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len);
+int fbtft_write_vmem16_bus9(struct fbtft_par *par, size_t offset, size_t len);
+void fbtft_write_reg8_bus8(struct fbtft_par *par, int len, ...);
+void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...);
+void fbtft_write_reg16_bus8(struct fbtft_par *par, int len, ...);
+void fbtft_write_reg16_bus16(struct fbtft_par *par, int len, ...);
+
+#define FBTFT_DT_TABLE(_compatible) \
+static const struct of_device_id dt_ids[] = { \
+ { .compatible = _compatible }, \
+ {}, \
+}; \
+MODULE_DEVICE_TABLE(of, dt_ids);
+
+#define FBTFT_SPI_DRIVER(_name, _compatible, _display, _spi_ids) \
+ \
+static int fbtft_driver_probe_spi(struct spi_device *spi) \
+{ \
+ return fbtft_probe_common(_display, spi, NULL); \
+} \
+ \
+static void fbtft_driver_remove_spi(struct spi_device *spi) \
+{ \
+ struct fb_info *info = spi_get_drvdata(spi); \
+ \
+ fbtft_remove_common(&spi->dev, info); \
+} \
+ \
+static struct spi_driver fbtft_driver_spi_driver = { \
+ .driver = { \
+ .name = _name, \
+ .of_match_table = dt_ids, \
+ }, \
+ .id_table = _spi_ids, \
+ .probe = fbtft_driver_probe_spi, \
+ .remove = fbtft_driver_remove_spi, \
+};
+
+#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display) \
+ \
+static int fbtft_driver_probe_pdev(struct platform_device *pdev) \
+{ \
+ return fbtft_probe_common(_display, NULL, pdev); \
+} \
+ \
+static int fbtft_driver_remove_pdev(struct platform_device *pdev) \
+{ \
+ struct fb_info *info = platform_get_drvdata(pdev); \
+ \
+ fbtft_remove_common(&pdev->dev, info); \
+ return 0; \
+} \
+ \
+FBTFT_DT_TABLE(_compatible) \
+ \
+FBTFT_SPI_DRIVER(_name, _compatible, _display, NULL) \
+ \
+static struct platform_driver fbtft_driver_platform_driver = { \
+ .driver = { \
+ .name = _name, \
+ .owner = THIS_MODULE, \
+ .of_match_table = dt_ids, \
+ }, \
+ .probe = fbtft_driver_probe_pdev, \
+ .remove = fbtft_driver_remove_pdev, \
+}; \
+ \
+static int __init fbtft_driver_module_init(void) \
+{ \
+ int ret; \
+ \
+ ret = spi_register_driver(&fbtft_driver_spi_driver); \
+ if (ret < 0) \
+ return ret; \
+ ret = platform_driver_register(&fbtft_driver_platform_driver); \
+ if (ret < 0) \
+ spi_unregister_driver(&fbtft_driver_spi_driver); \
+ return ret; \
+} \
+ \
+static void __exit fbtft_driver_module_exit(void) \
+{ \
+ spi_unregister_driver(&fbtft_driver_spi_driver); \
+ platform_driver_unregister(&fbtft_driver_platform_driver); \
+} \
+ \
+module_init(fbtft_driver_module_init); \
+module_exit(fbtft_driver_module_exit);
+
+#define FBTFT_REGISTER_SPI_DRIVER(_name, _comp_vend, _comp_dev, _display) \
+ \
+FBTFT_DT_TABLE(_comp_vend "," _comp_dev) \
+ \
+static const struct spi_device_id spi_ids[] = { \
+ { .name = _comp_dev }, \
+ {}, \
+}; \
+MODULE_DEVICE_TABLE(spi, spi_ids); \
+ \
+FBTFT_SPI_DRIVER(_name, _comp_vend "," _comp_dev, _display, spi_ids) \
+ \
+module_spi_driver(fbtft_driver_spi_driver);
+
+/* Debug macros */
+
+/* shorthand debug levels */
+#define DEBUG_LEVEL_1 DEBUG_REQUEST_GPIOS
+#define DEBUG_LEVEL_2 (DEBUG_LEVEL_1 | DEBUG_DRIVER_INIT_FUNCTIONS \
+ | DEBUG_TIME_FIRST_UPDATE)
+#define DEBUG_LEVEL_3 (DEBUG_LEVEL_2 | DEBUG_RESET | DEBUG_INIT_DISPLAY \
+ | DEBUG_BLANK | DEBUG_REQUEST_GPIOS \
+ | DEBUG_FREE_GPIOS \
+ | DEBUG_VERIFY_GPIOS \
+ | DEBUG_BACKLIGHT | DEBUG_SYSFS)
+#define DEBUG_LEVEL_4 (DEBUG_LEVEL_2 | DEBUG_FB_READ | DEBUG_FB_WRITE \
+ | DEBUG_FB_FILLRECT \
+ | DEBUG_FB_COPYAREA \
+ | DEBUG_FB_IMAGEBLIT | DEBUG_FB_BLANK)
+#define DEBUG_LEVEL_5 (DEBUG_LEVEL_3 | DEBUG_UPDATE_DISPLAY)
+#define DEBUG_LEVEL_6 (DEBUG_LEVEL_4 | DEBUG_LEVEL_5)
+#define DEBUG_LEVEL_7 0xFFFFFFFF
+
+#define DEBUG_DRIVER_INIT_FUNCTIONS BIT(3)
+#define DEBUG_TIME_FIRST_UPDATE BIT(4)
+#define DEBUG_TIME_EACH_UPDATE BIT(5)
+#define DEBUG_DEFERRED_IO BIT(6)
+#define DEBUG_FBTFT_INIT_FUNCTIONS BIT(7)
+
+/* fbops */
+#define DEBUG_FB_READ BIT(8)
+#define DEBUG_FB_WRITE BIT(9)
+#define DEBUG_FB_FILLRECT BIT(10)
+#define DEBUG_FB_COPYAREA BIT(11)
+#define DEBUG_FB_IMAGEBLIT BIT(12)
+#define DEBUG_FB_SETCOLREG BIT(13)
+#define DEBUG_FB_BLANK BIT(14)
+
+#define DEBUG_SYSFS BIT(16)
+
+/* fbtftops */
+#define DEBUG_BACKLIGHT BIT(17)
+#define DEBUG_READ BIT(18)
+#define DEBUG_WRITE BIT(19)
+#define DEBUG_WRITE_VMEM BIT(20)
+#define DEBUG_WRITE_REGISTER BIT(21)
+#define DEBUG_SET_ADDR_WIN BIT(22)
+#define DEBUG_RESET BIT(23)
+#define DEBUG_MKDIRTY BIT(24)
+#define DEBUG_UPDATE_DISPLAY BIT(25)
+#define DEBUG_INIT_DISPLAY BIT(26)
+#define DEBUG_BLANK BIT(27)
+#define DEBUG_REQUEST_GPIOS BIT(28)
+#define DEBUG_FREE_GPIOS BIT(29)
+#define DEBUG_REQUEST_GPIOS_MATCH BIT(30)
+#define DEBUG_VERIFY_GPIOS BIT(31)
+
+#define fbtft_init_dbg(dev, format, arg...) \
+do { \
+ if (unlikely((dev)->platform_data && \
+ (((struct fbtft_platform_data *)(dev)->platform_data)->display.debug & DEBUG_DRIVER_INIT_FUNCTIONS))) \
+ dev_info(dev, format, ##arg); \
+} while (0)
+
+#define fbtft_par_dbg(level, par, format, arg...) \
+do { \
+ if (unlikely((par)->debug & (level))) \
+ dev_info((par)->info->device, format, ##arg); \
+} while (0)
+
+#define fbtft_par_dbg_hex(level, par, dev, type, buf, num, format, arg...) \
+do { \
+ if (unlikely((par)->debug & (level))) \
+ fbtft_dbg_hex(dev, sizeof(type), buf,\
+ (num) * sizeof(type), format, ##arg); \
+} while (0)
+
+#endif /* __LINUX_FBTFT_H */
diff --git a/drivers/staging/fbtft/internal.h b/drivers/staging/fbtft/internal.h
new file mode 100644
index 000000000..ae2ff4a4a
--- /dev/null
+++ b/drivers/staging/fbtft/internal.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2013 Noralf Tronnes */
+
+#ifndef __LINUX_FBTFT_INTERNAL_H
+#define __LINUX_FBTFT_INTERNAL_H
+
+void fbtft_sysfs_init(struct fbtft_par *par);
+void fbtft_sysfs_exit(struct fbtft_par *par);
+void fbtft_expand_debug_value(unsigned long *debug);
+int fbtft_gamma_parse_str(struct fbtft_par *par, u32 *curves,
+ const char *str, int size);
+
+#endif /* __LINUX_FBTFT_INTERNAL_H */