From patchwork Wed Mar 16 13:34:17 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Noralf_Tr=C3=B8nnes?= X-Patchwork-Id: 8600261 Return-Path: X-Original-To: patchwork-dri-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 23156C0553 for ; Wed, 16 Mar 2016 13:41:13 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 4B98B2034B for ; Wed, 16 Mar 2016 13:41:11 +0000 (UTC) Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by mail.kernel.org (Postfix) with ESMTP id 3F4DA20361 for ; Wed, 16 Mar 2016 13:41:09 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id D46256E92C; Wed, 16 Mar 2016 13:40:58 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from asav22.altibox.net (asav22.altibox.net [109.247.116.9]) by gabe.freedesktop.org (Postfix) with ESMTPS id D9A3A6E92C for ; Wed, 16 Mar 2016 13:40:55 +0000 (UTC) Received: from localhost.localdomain (151.79-160-56.customer.lyse.net [79.160.56.151]) by asav22.altibox.net (Postfix) with ESMTP id 8BBED20205; Wed, 16 Mar 2016 14:34:42 +0100 (CET) From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= To: dri-devel@lists.freedesktop.org Subject: [RFC 3/5] drm/tinydrm/lcdreg: Add SPI support Date: Wed, 16 Mar 2016 14:34:17 +0100 Message-Id: <1458135259-31050-4-git-send-email-noralf@tronnes.org> X-Mailer: git-send-email 2.2.2 In-Reply-To: <1458135259-31050-1-git-send-email-noralf@tronnes.org> References: <1458135259-31050-1-git-send-email-noralf@tronnes.org> MIME-Version: 1.0 X-CMAE-Score: 0 X-CMAE-Analysis: v=2.1 cv=WpKLSYrv c=1 sm=1 tr=0 a=gFHx44SYZz5JQKQKbGEAEQ==:117 a=gFHx44SYZz5JQKQKbGEAEQ==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=IkcTkHD0fZMA:10 a=SJz97ENfAAAA:8 a=g9uC3mK0G0bfYiTyhskA:9 a=QEXdDO2ut3YA:10 Cc: thomas.petazzoni@free-electrons.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 = Command + DM = Dummy read + PA = Parameter or display data + + ST7735R read: + Parallel: + 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 +#include + +/** + * 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 */