All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Noralf Trønnes" <noralf@tronnes.org>
To: dri-devel@lists.freedesktop.org
Cc: thomas.petazzoni@free-electrons.com
Subject: [RFC v2 6/8] drm/tinydrm/lcdreg: Add SPI support
Date: Fri,  8 Apr 2016 19:05:08 +0200	[thread overview]
Message-ID: <1460135110-24121-7-git-send-email-noralf@tronnes.org> (raw)
In-Reply-To: <1460135110-24121-1-git-send-email-noralf@tronnes.org>

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@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

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

  parent reply	other threads:[~2016-04-08 17:05 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
2016-04-08 17:05 ` [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support Noralf Trønnes
2016-04-13 10:57   ` Daniel Vetter
2016-04-13 11:09   ` Daniel Vetter
2016-04-18 15:15     ` Noralf Trønnes
2016-04-20 11:12       ` Daniel Vetter
2016-04-20 15:22         ` Noralf Trønnes
2016-04-20 22:29       ` Dave Airlie
2016-04-21  6:53         ` Daniel Vetter
2016-04-08 17:05 ` [RFC v2 2/8] drm/fb-cma-helper: " Noralf Trønnes
2016-04-13 11:19   ` Daniel Vetter
2016-04-08 17:05 ` [RFC v2 3/8] drm: Add helper for simple kms drivers Noralf Trønnes
2016-04-13 11:05   ` Daniel Vetter
2016-05-02 15:55     ` Noralf Trønnes
2016-05-02 20:20       ` Daniel Vetter
2016-04-08 17:05 ` [RFC v2 4/8] drm: Add DRM support for tiny LCD displays Noralf Trønnes
2016-04-08 17:05 ` [RFC v2 5/8] drm/tinydrm: Add lcd register abstraction Noralf Trønnes
2016-04-08 17:05 ` Noralf Trønnes [this message]
2016-04-08 17:05 ` [RFC v2 7/8] drm/tinydrm: Add mipi-dbi support Noralf Trønnes
2016-04-08 17:05 ` [RFC v2 8/8] drm/tinydrm: Add support for several Adafruit TFT displays Noralf Trønnes
2016-04-13 11:11 ` [RFC v2 0/8] drm: Add support for tiny LCD displays Daniel Vetter

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1460135110-24121-7-git-send-email-noralf@tronnes.org \
    --to=noralf@tronnes.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=thomas.petazzoni@free-electrons.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.