Add SPI bus support to lcdreg.
Supports the following protocols:
- MIPI DBI type C interface option 1 (3-wire) and option 3 (4-wire).
- Option 3 also fits some controllers where all registers are 16-bit.
- 8/16-bit register transfers that need to start with a special startbyte.
It also supports emulation for 16 and 9-bit words if the SPI controller
doesn't support it.

Signed-off-by: Noralf Trønnes <noralf at tronnes.org>
---
 drivers/gpu/drm/tinydrm/lcdreg/Kconfig      |   5 +
 drivers/gpu/drm/tinydrm/lcdreg/Makefile     |   2 +
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c | 720 ++++++++++++++++++++++++++++
 include/drm/tinydrm/lcdreg-spi.h            |  63 +++
 4 files changed, 790 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
 create mode 100644 include/drm/tinydrm/lcdreg-spi.h

diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig 
b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
index 41383b1..ade465f 100644
--- a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
@@ -1,3 +1,8 @@
 config LCDREG
        tristate
        depends on GPIOLIB || COMPILE_TEST
+
+config LCDREG_SPI
+       tristate
+       depends on SPI
+       select LCDREG
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Makefile 
b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
index c9ea774..4e14571 100644
--- a/drivers/gpu/drm/tinydrm/lcdreg/Makefile
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
@@ -1,3 +1,5 @@
 obj-$(CONFIG_LCDREG)                   += lcdreg.o
 lcdreg-y                               += lcdreg-core.o
 lcdreg-$(CONFIG_DEBUG_FS)              += lcdreg-debugfs.o
+
+obj-$(CONFIG_LCDREG_SPI)               += lcdreg-spi.o
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c 
b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
new file mode 100644
index 0000000..5e9d8fe1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
@@ -0,0 +1,720 @@
+//#define VERBOSE_DEBUG
+//#define DEBUG
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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.
+ */
+
+#include <asm/unaligned.h>
+#include <drm/tinydrm/lcdreg-spi.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+static unsigned txlen;
+module_param(txlen, uint, 0);
+MODULE_PARM_DESC(txlen, "Transmit chunk length");
+
+static unsigned long bpwm;
+module_param(bpwm, ulong, 0);
+MODULE_PARM_DESC(bpwm, "Override SPI master bits_per_word_mask");
+
+struct lcdreg_spi {
+       struct lcdreg reg;
+       enum lcdreg_spi_mode mode;
+unsigned txbuflen;
+       void *txbuf_dc;
+       unsigned id;
+       u32 quirks;
+       u8 (*startbyte)(struct lcdreg *reg, struct lcdreg_transfer *tr,
+                       bool read);
+       struct gpio_desc *dc;
+       struct gpio_desc *reset;
+};
+
+static inline struct lcdreg_spi *to_lcdreg_spi(struct lcdreg *reg)
+{
+       return reg ? container_of(reg, struct lcdreg_spi, reg) : NULL;
+}
+
+#ifdef VERBOSE_DEBUG
+static void lcdreg_vdbg_dump_spi(const struct device *dev, struct spi_message 
*m, u8 *startbyte)
+{
+       struct spi_transfer *tmp;
+       struct list_head *pos;
+       int i = 0;
+
+       if (startbyte)
+               dev_dbg(dev, "spi_message: startbyte=0x%02X\n", startbyte[0]);
+       else
+               dev_dbg(dev, "spi_message:\n");
+
+       list_for_each(pos, &m->transfers) {
+               tmp = list_entry(pos, struct spi_transfer, transfer_list);
+               if (tmp->tx_buf)
+                       pr_debug("    tr%i: bpw=%i, len=%u, 
tx_buf(%p)=[%*ph]\n", i, tmp->bits_per_word, tmp->len, tmp->tx_buf, tmp->len > 
64 ? 64 : tmp->len, tmp->tx_buf);
+               if (tmp->rx_buf)
+                       pr_debug("    tr%i: bpw=%i, len=%u, 
rx_buf(%p)=[%*ph]\n", i, tmp->bits_per_word, tmp->len, tmp->rx_buf, tmp->len > 
64 ? 64 : tmp->len, tmp->rx_buf);
+               i++;
+       }
+}
+#else
+static void lcdreg_vdbg_dump_spi(const struct device *dev, struct spi_message 
*m, u8 *startbyte)
+{
+}
+#endif
+
+static int lcdreg_spi_do_transfer(struct lcdreg *reg,
+                                 struct lcdreg_transfer *transfer)
+{
+       struct spi_device *sdev = to_spi_device(reg->dev);
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+       void *buf = transfer->buf;
+       size_t len = transfer->count * lcdreg_bytes_per_word(transfer->width);
+       size_t max = txlen ? : sdev->master->max_dma_len;
+       size_t room_left_in_page = PAGE_SIZE - offset_in_page(buf);
+       size_t chunk = min_t(size_t, len, max);
+       struct spi_message m;
+       struct spi_transfer *tr;
+       u8 *startbuf = NULL;
+       int ret, i;
+
+       dev_dbg(reg->dev, "%s: index=%u, count=%u, width=%u\n",
+               __func__, transfer->index, transfer->count, transfer->width);
+       lcdreg_dbg_transfer_buf(transfer);
+
+       tr = kzalloc(2 * sizeof(*tr), GFP_KERNEL);
+       if (!tr)
+               return -ENOMEM;
+
+       /* slow down commands? */
+       if (!transfer->index && (spi->quirks & LCDREG_SLOW_INDEX0_WRITE))
+               for (i = 0; i < 2; i++)
+                       tr[i].speed_hz = min_t(u32, 2000000,
+                                              sdev->max_speed_hz / 2);
+
+       if (spi->mode == LCDREG_SPI_STARTBYTE) {
+               startbuf = kmalloc(1, GFP_KERNEL);
+               if (!startbuf) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+               *startbuf = spi->startbyte(reg, transfer, false);
+       }
+
+       /*
+        * transfer->buf can be unaligned to the page boundary for partial
+        * updates when videomem is sent directly (no buffering).
+        * Spi core can sg map the buffer for dma and relies on vmalloc'ed
+        * memory to be page aligned.
+        */
+//pr_debug("%s: PAGE_ALIGNED=%d, len > room_left_in_page= %d > %d = %d, 
chunk=%zu\n", __func__, PAGE_ALIGNED(buf), len, room_left_in_page, len > 
room_left_in_page, chunk);
+       if (!PAGE_ALIGNED(buf) && len > room_left_in_page) {
+//size_t chunk0 = chunk;
+
+               if (chunk >= room_left_in_page) {
+                       chunk = room_left_in_page;
+//pr_debug("%s: chunk: %zu -> %zu, room_left_in_page=%zu\n\n", __func__, 
chunk0, chunk, room_left_in_page);
+               } else {
+                       chunk = room_left_in_page % chunk ? : chunk;
+//pr_debug("%s: chunk: %zu -> %zu, room_left_in_page=%zu, room_left_in_page %% 
chunk=%zu\n\n", __func__, chunk0, chunk, room_left_in_page, room_left_in_page % 
chunk);
+               }
+       }
+
+       do {
+               i = 0;
+               spi_message_init(&m);
+
+               if (spi->mode == LCDREG_SPI_STARTBYTE) {
+                       tr[i].tx_buf = startbuf;
+                       tr[i].len = 1;
+                       tr[i].bits_per_word = 8;
+                       spi_message_add_tail(&tr[i++], &m);
+               }
+
+               tr[i].tx_buf = buf;
+               tr[i].len = chunk;
+               tr[i].bits_per_word = transfer->width;
+               buf += chunk;
+               len -= chunk;
+               spi_message_add_tail(&tr[i], &m);
+
+               lcdreg_vdbg_dump_spi(&sdev->dev, &m, startbuf);
+               ret = spi_sync(sdev, &m);
+               if (ret)
+                       goto out;
+
+               chunk = min_t(size_t, len, max);
+       } while (len);
+
+out:
+       kfree(tr);
+       kfree(startbuf);
+
+       return ret;
+}
+
+static int lcdreg_spi_transfer_emulate9(struct lcdreg *reg,
+                                       struct lcdreg_transfer *transfer)
+{
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+       struct lcdreg_transfer tr = {
+               .index = transfer->index,
+               .width = 8,
+               .count = transfer->count,
+       };
+       u16 *src = transfer->buf;
+       unsigned added = 0;
+       int i, ret;
+       u8 *dst;
+
+       if (transfer->count % 8) {
+               dev_err_once(reg->dev,
+                            "transfer->count=%u must be divisible by 8\n",
+                            transfer->count);
+               return -EINVAL;
+       }
+
+       dst = kzalloc(spi->txbuflen, GFP_KERNEL);
+       if (!dst)
+               return -ENOMEM;
+
+       tr.buf = dst;
+
+       for (i = 0; i < transfer->count; i += 8) {
+               u64 tmp = 0;
+               int j, bits = 63;
+
+               for (j = 0; j < 7; j++) {
+                       u64 bit9 = (*src & 0x100) ? 1 : 0;
+                       u64 val = *src++ & 0xFF;
+
+                       tmp |= bit9 << bits;
+                       bits -= 8;
+                       tmp |= val << bits--;
+               }
+               tmp |= ((*src & 0x100) ? 1 : 0);
+               *(u64 *)dst = cpu_to_be64(tmp);
+               dst += 8;
+               *dst++ = *src++ & 0xFF;
+               added++;
+       }
+       tr.count += added;
+       ret = lcdreg_spi_do_transfer(reg, &tr);
+       kfree(tr.buf);
+
+       return ret;
+}
+
+static int lcdreg_spi_transfer_emulate16(struct lcdreg *reg,
+                                        struct lcdreg_transfer *transfer)
+{
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+       unsigned to_copy, remain = transfer->count;
+       struct lcdreg_transfer tr = {
+               .index = transfer->index,
+               .width = 8,
+       };
+       u16 *data16 = transfer->buf;
+       u16 *txbuf16;
+       int i, ret = 0;
+
+       txbuf16 = kzalloc(spi->txbuflen, GFP_KERNEL);
+       if (!txbuf16)
+               return -ENOMEM;
+
+       tr.buf = txbuf16;
+
+       while (remain) {
+               to_copy = min(remain, spi->txbuflen / 2);
+               dev_dbg(reg->dev, "    to_copy=%zu, remain=%zu\n",
+                                       to_copy, remain - to_copy);
+
+               for (i = 0; i < to_copy; i++)
+                       txbuf16[i] = swab16(data16[i]);
+
+               data16 = data16 + to_copy;
+               tr.count = to_copy * 2;
+               ret = lcdreg_spi_do_transfer(reg, &tr);
+               if (ret < 0)
+                       goto out;
+               remain -= to_copy;
+       }
+
+out:
+       kfree(tr.buf);
+
+       return ret;
+}
+
+static int lcdreg_spi_transfer(struct lcdreg *reg,
+                              struct lcdreg_transfer *transfer)
+{
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+       bool mach_little_endian;
+
+#ifdef __LITTLE_ENDIAN
+       mach_little_endian = true;
+#endif
+       if (spi->dc)
+               gpiod_set_value_cansleep(spi->dc, transfer->index);
+
+       if (lcdreg_bpw_supported(reg, transfer->width))
+               return lcdreg_spi_do_transfer(reg, transfer);
+
+       if (transfer->width == 9)
+               return lcdreg_spi_transfer_emulate9(reg, transfer);
+
+       if ((mach_little_endian == reg->little_endian) &&
+           (transfer->width % 8 == 0)) {
+               /* the byte order matches */
+               transfer->count *= transfer->width / 8;
+               transfer->width = 8;
+               return lcdreg_spi_do_transfer(reg, transfer);
+       }
+
+       if (mach_little_endian != reg->little_endian && transfer->width == 16)
+               return lcdreg_spi_transfer_emulate16(reg, transfer);
+
+       dev_err_once(reg->dev, "width=%u is not supported (%u:%u)\n",
+                    transfer->width, mach_little_endian, reg->little_endian);
+
+       return -EINVAL;
+}
+
+static int lcdreg_spi_write_9bit_dc(struct lcdreg *reg,
+                                   struct lcdreg_transfer *transfer)
+{
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+       struct lcdreg_transfer tr = {
+               .index = transfer->index,
+       };
+       u8 *data8 = transfer->buf;
+       u16 *data16 = transfer->buf;
+       unsigned width;
+       u16 *txbuf16;
+       unsigned remain;
+       unsigned tx_array_size;
+       unsigned to_copy;
+       int pad, i, ret;
+
+width = transfer->width;
+
+       if (width != 8 && width != 16) {
+               dev_err(reg->dev, "transfer width %u is not supported\n",
+                                                               width);
+               return -EINVAL;
+       }
+
+       if (!spi->txbuf_dc) {
+               spi->txbuf_dc = devm_kzalloc(reg->dev, spi->txbuflen,
+                                                       GFP_KERNEL);
+               if (!spi->txbuf_dc)
+                       return -ENOMEM;
+               dev_info(reg->dev, "allocated %u KiB 9-bit dc buffer\n",
+                                               spi->txbuflen / 1024);
+       }
+
+       tr.buf = spi->txbuf_dc;
+       txbuf16 = spi->txbuf_dc;
+       remain = transfer->count;
+       if (width == 8)
+               tx_array_size = spi->txbuflen / 2;
+       else
+               tx_array_size = spi->txbuflen / 4;
+
+       /* If we're emulating 9-bit, the buffer has to be divisible by 8.
+          Pad with no-ops if necessary (assuming here that zero is a no-op)
+          FIX: If video buf isn't divisible by 8, it will break.
+        */
+       if (!lcdreg_bpw_supported(reg, 9) && width == 8 &&
+                                               remain < tx_array_size) {
+               pad = (transfer->count % 8) ? 8 - (transfer->count % 8) : 0;
+               if (transfer->index == 0)
+                       for (i = 0; i < pad; i++)
+                               *txbuf16++ = 0x000;
+               for (i = 0; i < remain; i++) {
+                       *txbuf16 = *data8++;
+                       if (transfer->index)
+                               *txbuf16++ |= 0x0100;
+               }
+               if (transfer->index == 1)
+                       for (i = 0; i < pad; i++)
+                               *txbuf16++ = 0x000;
+               tr.width = 9;
+               tr.count = pad + remain;
+               return lcdreg_spi_transfer(reg, &tr);
+       }
+
+       while (remain) {
+               to_copy = remain > tx_array_size ? tx_array_size : remain;
+               remain -= to_copy;
+               dev_dbg(reg->dev, "    to_copy=%zu, remain=%zu\n",
+                                       to_copy, remain);
+
+               if (width == 8) {
+                       for (i = 0; i < to_copy; i++) {
+                               txbuf16[i] = *data8++;
+                               if (transfer->index)
+                                       txbuf16[i] |= 0x0100;
+                       }
+               } else {
+                       for (i = 0; i < (to_copy * 2); i += 2) {
+                               txbuf16[i]     = *data16 >> 8;
+                               txbuf16[i + 1] = *data16++ & 0xFF;
+                               if (transfer->index) {
+                                       txbuf16[i]     |= 0x0100;
+                                       txbuf16[i + 1] |= 0x0100;
+                               }
+                       }
+               }
+               tr.buf = spi->txbuf_dc;
+               tr.width = 9;
+               tr.count = to_copy * 2;
+               ret = lcdreg_spi_transfer(reg, &tr);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int lcdreg_spi_write(struct lcdreg *reg, unsigned regnr,
+                           struct lcdreg_transfer *transfer)
+{
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+       struct lcdreg_transfer tr = {
+               .width = reg->def_width,
+               .count = 1,
+       };
+       int ret;
+
+       tr.buf = kmalloc(sizeof(u32), GFP_KERNEL);
+       if (!tr.buf)
+               return -ENOMEM;
+
+       if (reg->def_width <= 8)
+               ((u8 *)tr.buf)[0] = regnr;
+       else
+               ((u16 *)tr.buf)[0] = regnr;
+
+       if (spi->mode == LCDREG_SPI_3WIRE)
+               ret = lcdreg_spi_write_9bit_dc(reg, &tr);
+       else
+               ret = lcdreg_spi_transfer(reg, &tr);
+       kfree(tr.buf);
+       if (ret || !transfer || !transfer->count)
+               return ret;
+
+       if (!transfer->width)
+               transfer->width = reg->def_width;
+       if (spi->mode == LCDREG_SPI_3WIRE)
+               ret = lcdreg_spi_write_9bit_dc(reg, transfer);
+       else
+               ret = lcdreg_spi_transfer(reg, transfer);
+
+       return ret;
+}
+
+/*
+   <CMD> <DM> <PA>
+   CMD = Command
+   DM = Dummy read
+   PA = Parameter or display data
+
+   ST7735R read:
+     Parallel: <CMD> <DM> <PA>
+     SPI: 8-bit plain, 24- and 32-bit needs 1 dummy clock cycle
+
+   ILI9320:
+     Parallel: no dummy read, page 51 in datasheet
+     SPI (startbyte): One byte of invalid dummy data read after the start byte.
+
+   ILI9340:
+     Parallel: no info about dummy read
+     SPI: same as ST7735R
+
+   ILI9341:
+     Parallel: no info about dummy read
+     SPI: same as ST7735R
+
+   SSD1289:
+     Parallel: 1 dummy read
+
+ */
+
+static int lcdreg_spi_read_startbyte(struct lcdreg *reg, unsigned regnr,
+                                    struct lcdreg_transfer *transfer)
+{
+       struct spi_device *sdev = to_spi_device(reg->dev);
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+       struct spi_message m;
+       struct spi_transfer trtx = {
+               .speed_hz = min_t(u32, 2000000, sdev->max_speed_hz / 2),
+               .bits_per_word = 8,
+               .len = 1,
+       };
+       struct spi_transfer trrx = {
+               .speed_hz = trtx.speed_hz,
+               .bits_per_word = 8,
+               .len = (transfer->count * 2) + 1,
+       };
+       u8 *txbuf, *rxbuf;
+       int i, ret;
+
+       if (!reg->readable)
+               return -EACCES;
+
+       if (!transfer || !transfer->count)
+               return -EINVAL;
+
+       transfer->width = transfer->width ? : reg->def_width;
+       if (WARN_ON(transfer->width != 16))
+               return -EINVAL;
+
+       ret = lcdreg_writereg(reg, regnr);
+       if (ret)
+               return ret;
+
+       txbuf = kzalloc(1, GFP_KERNEL);
+       if (!txbuf)
+               return -ENOMEM;
+
+       rxbuf = kzalloc(trrx.len, GFP_KERNEL);
+       if (!rxbuf) {
+               kfree(txbuf);
+               return -ENOMEM;
+       }
+
+       *txbuf = spi->startbyte(reg, transfer, true);
+
+       trtx.tx_buf = txbuf;
+       trrx.rx_buf = rxbuf;
+       spi_message_init(&m);
+       spi_message_add_tail(&trtx, &m);
+       spi_message_add_tail(&trrx, &m);
+       ret = spi_sync(sdev, &m);
+       lcdreg_vdbg_dump_spi(&sdev->dev, &m, txbuf);
+       kfree(txbuf);
+       if (ret) {
+               kfree(rxbuf);
+               return ret;
+       }
+
+       rxbuf++;
+       for (i = 0; i < transfer->count; i++) {
+               ((u16 *)transfer->buf)[i] = get_unaligned_be16(rxbuf);
+               rxbuf += 2;
+       }
+       kfree(trrx.rx_buf);
+
+       return 0;
+}
+
+static int lcdreg_spi_read(struct lcdreg *reg, unsigned regnr,
+                          struct lcdreg_transfer *transfer)
+{
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+       struct spi_device *sdev = to_spi_device(reg->dev);
+       struct spi_message m;
+       struct spi_transfer trtx = {
+               .speed_hz = min_t(u32, 2000000, sdev->max_speed_hz / 2),
+               .bits_per_word = reg->def_width,
+               .len = 1,
+       };
+       struct spi_transfer trrx = {
+               .speed_hz = trtx.speed_hz,
+               .rx_buf = transfer->buf,
+               .len = transfer->count,
+       };
+       void *txbuf = NULL;
+       int i, ret;
+
+       transfer->width = transfer->width ? : reg->def_width;
+       if (WARN_ON(transfer->width != reg->def_width || !transfer->count))
+               return -EINVAL;
+
+       if (!reg->readable)
+               return -EACCES;
+
+       txbuf = kzalloc(16, GFP_KERNEL);
+       if (!txbuf)
+               return -ENOMEM;
+
+       spi_message_init(&m);
+       trtx.tx_buf = txbuf;
+       trrx.bits_per_word = transfer->width;
+
+       if (spi->mode == LCDREG_SPI_4WIRE) {
+               if (trtx.bits_per_word == 8) {
+                       *(u8 *)txbuf = regnr;
+               } else if (trtx.bits_per_word == 16) {
+                       if (lcdreg_bpw_supported(reg, trtx.bits_per_word)) {
+                               *(u16 *)txbuf = regnr;
+                       } else {
+                               *(u16 *)txbuf = cpu_to_be16(regnr);
+                               trtx.bits_per_word = 8;
+                               trtx.len = 2;
+                       }
+               } else {
+                       return -EINVAL;
+               }
+               gpiod_set_value_cansleep(spi->dc, 0);
+       } else if (spi->mode == LCDREG_SPI_3WIRE) {
+               if (lcdreg_bpw_supported(reg, 9)) {
+                       trtx.bits_per_word = 9;
+                       *(u16 *)txbuf = regnr; /* dc=0 */
+               } else {
+                       /* 8x 9-bit words, pad with leading zeroes (no-ops) */
+                       ((u8 *)txbuf)[8] = regnr;
+               }
+       } else {
+               kfree(txbuf);
+               return -EINVAL;
+       }
+       spi_message_add_tail(&trtx, &m);
+
+       if (spi->mode == LCDREG_SPI_4WIRE && transfer->index &&
+           !(spi->quirks & LCDREG_INDEX0_ON_READ)) {
+               trtx.cs_change = 1; /* not always supported */
+               lcdreg_vdbg_dump_spi(&sdev->dev, &m, NULL);
+               ret = spi_sync(sdev, &m);
+               if (ret) {
+                       kfree(txbuf);
+                       return ret;
+               }
+               gpiod_set_value_cansleep(spi->dc, 1);
+               spi_message_init(&m);
+       }
+
+       spi_message_add_tail(&trrx, &m);
+       ret = spi_sync(sdev, &m);
+       lcdreg_vdbg_dump_spi(&sdev->dev, &m, NULL);
+       kfree(txbuf);
+       if (ret)
+               return ret;
+
+       if (!lcdreg_bpw_supported(reg, trrx.bits_per_word) &&
+                                               (trrx.bits_per_word == 16))
+               for (i = 0; i < transfer->count; i++)
+                       ((u16 *)transfer->buf)[i] = be16_to_cpu(((u16 
*)transfer->buf)[i]);
+
+       return 0;
+}
+
+static void lcdreg_spi_reset(struct lcdreg *reg)
+{
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+
+       if (!spi->reset)
+               return;
+
+       dev_info(reg->dev, "%s()\n", __func__);
+       gpiod_set_value_cansleep(spi->reset, 0);
+       msleep(20);
+       gpiod_set_value_cansleep(spi->reset, 1);
+       msleep(120);
+}
+
+/* Default startbyte implementation: | 0 | 1 | 1 | 1 | 0 | ID | RS | RW | */
+static u8 lcdreg_spi_startbyte(struct lcdreg *reg, struct lcdreg_transfer *tr,
+                              bool read)
+{
+       struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+
+       return 0x70 | (!!spi->id << 2) | (!!tr->index << 1) | read;
+}
+
+struct lcdreg *devm_lcdreg_spi_init(struct spi_device *sdev,
+                                   const struct lcdreg_spi_config *config)
+{
+       char *dc_name = config->dc_name ? : "dc";
+       struct device *dev = &sdev->dev;
+       struct lcdreg_spi *spi;
+       struct lcdreg *reg;
+
+       if (txlen) {
+               if (txlen < PAGE_SIZE) {
+                       txlen = rounddown_pow_of_two(txlen);
+                       if (txlen < 64)
+                               txlen = 64;
+               } else {
+                       txlen &= PAGE_MASK;
+               }
+       }
+dev_info(dev, "txlen: %u\n", txlen);
+
+       spi = devm_kzalloc(dev, sizeof(*spi), GFP_KERNEL);
+       if (!spi)
+               return ERR_PTR(-ENOMEM);
+
+       reg = &spi->reg;
+       if (bpwm) {
+               reg->bits_per_word_mask = bpwm;
+       } else {
+               if (sdev->master->bits_per_word_mask)
+                       reg->bits_per_word_mask = 
sdev->master->bits_per_word_mask;
+               else
+                       reg->bits_per_word_mask = SPI_BPW_MASK(8);
+       }
+       dev_dbg(dev, "bits_per_word_mask: 0x%08x", reg->bits_per_word_mask);
+
+       reg->def_width = config->def_width;
+       reg->readable = config->readable;
+       reg->reset = lcdreg_spi_reset;
+       reg->write = lcdreg_spi_write;
+       reg->read = lcdreg_spi_read;
+
+       spi->mode = config->mode;
+       spi->quirks = config->quirks;
+       spi->id = config->id;
+       if (!spi->txbuflen)
+               spi->txbuflen = PAGE_SIZE;
+
+       switch (spi->mode) {
+       case LCDREG_SPI_4WIRE:
+               spi->dc = devm_gpiod_get(dev, dc_name, GPIOD_OUT_LOW);
+               if (IS_ERR(spi->dc)) {
+                       dev_err(dev, "Failed to get gpio '%s'\n", dc_name);
+                       return ERR_CAST(spi->dc);
+               }
+               break;
+       case LCDREG_SPI_3WIRE:
+               break;
+       case LCDREG_SPI_STARTBYTE:
+               reg->read = lcdreg_spi_read_startbyte;
+               spi->startbyte = config->startbyte ? : lcdreg_spi_startbyte;
+               break;
+       default:
+               dev_err(dev, "Mode is not supported: %u\n", spi->mode);
+               return ERR_PTR(-EINVAL);
+       }
+
+       spi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+       if (IS_ERR(spi->reset)) {
+               dev_err(dev, "Failed to get gpio 'reset'\n");
+               return ERR_CAST(spi->reset);
+       }
+
+       pr_debug("spi->reg.def_width: %u\n", reg->def_width);
+       if (spi->reset)
+               pr_debug("spi->reset: %i\n", desc_to_gpio(spi->reset));
+       if (spi->dc)
+               pr_debug("spi->dc: %i\n", desc_to_gpio(spi->dc));
+       pr_debug("spi->mode: %u\n", spi->mode);
+
+       return devm_lcdreg_init(dev, reg);
+}
+EXPORT_SYMBOL_GPL(devm_lcdreg_spi_init);
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/lcdreg-spi.h b/include/drm/tinydrm/lcdreg-spi.h
new file mode 100644
index 0000000..ba5d492
--- /dev/null
+++ b/include/drm/tinydrm/lcdreg-spi.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_LCDREG_SPI_H
+#define __LINUX_LCDREG_SPI_H
+
+#include <drm/tinydrm/lcdreg.h>
+#include <linux/spi/spi.h>
+
+/**
+ * enum lcdreg_spi_mode - SPI interface mode
+ * @LCDREG_SPI_4WIRE: 8-bit + D/CX line, MIPI DBI Type C option 3
+ * @LCDREG_SPI_3WIRE: 9-bit inc. D/CX bit, MIPI DBI Type C option 1
+ * @LCDREG_SPI_STARTBYTE: Startbyte header on every transaction (non MIPI)
+ */
+enum lcdreg_spi_mode {
+       LCDREG_SPI_NOMODE = 0,
+       LCDREG_SPI_4WIRE,
+       LCDREG_SPI_3WIRE,
+       LCDREG_SPI_STARTBYTE,
+};
+
+/**
+ * struct lcdreg_spi_config - SPI interface configuration
+ * @mode: Register interface mode
+ * @def_width: Default register width
+ * @readable: Is the register readable, not all displays have MISO wired.
+ * @id: Display id used with LCDREG_SPI_STARTBYTE
+ * @dc_name: Index pin name, usually dc, rs or di (default is 'dc').
+ * @quirks: Deviations from the MIPI DBI standard
+ * @startbyte: Used with LCDREG_SPI_STARTBYTE to get the startbyte
+ *             (default is lcdreg_spi_startbyte).
+ */
+struct lcdreg_spi_config {
+       enum lcdreg_spi_mode mode;
+       unsigned def_width;
+       bool readable;
+       u32 id;
+       char *dc_name;
+       u32 quirks;
+/* slowdown command (index=0) */
+#define LCDREG_SLOW_INDEX0_WRITE       BIT(0)
+/*
+ * The MIPI DBI spec states that D/C should be HIGH during register reading.
+ * However, not all SPI master drivers support cs_change on last transfer and
+ * there are LCD controllers that ignore D/C on read.
+ */
+#define LCDREG_INDEX0_ON_READ          BIT(1)
+
+       u8 (*startbyte)(struct lcdreg *reg, struct lcdreg_transfer *tr,
+                       bool read);
+};
+
+struct lcdreg *devm_lcdreg_spi_init(struct spi_device *sdev,
+                                   const struct lcdreg_spi_config *config);
+
+#endif /* __LINUX_LCDREG_SPI_H */
-- 
2.2.2

Reply via email to