mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 19:00:25 +00:00
![Andy Shevchenko](/assets/img/avatar_default.png)
The infamous commitc440eee1a7
("Staging: staging: fbtft: Switch to the GPIO descriptor interface") broke GPIO handling completely. It has already four commits to rectify and it seems not enough. In order to fix the mess here we: 1) Set default to "inactive" for all requested pins 2) Fix CS#, RD#, and WR# pins polarity since it's active low and GPIO descriptor interface takes it into consideration from the Device Tree or ACPI 3) Consolidate chip activation (CS# assertion) under default ->reset() callback To summarize the expectations about polarity for GPIOs: RD# Low WR# Low CS# Low RESET# Low DC or RS High RW High Data 0 .. 15 High See also Adafruit learning course [1] for the example of the schematics. While at it, drop unneeded NULL checks, since GPIO API is tolerant to that. [1]: https://learn.adafruit.com/adafruit-2-8-and-3-2-color-tft-touchscreen-breakout-v2/downloads Fixes:92e3e88488
("Staging: fbtft: Fix GPIO handling") Fixes:b918d1c270
("Staging: fbtft: Fix reset assertion when using gpio descriptor") Fixes:dbc4f989c8
("Staging: fbtft: Fix probing of gpio descriptor") Fixes:c440eee1a7
("Staging: fbtft: Switch to the gpio descriptor interface") Cc: Jan Sebastian Götte <linux@jaseg.net> Cc: Nishad Kamdar <nishadkamdar@gmail.com> Reviewed-by: Phil Reid <preid@electromag.com.au> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Link: https://lore.kernel.org/r/20210503172114.27891-2-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
236 lines
5.2 KiB
C
236 lines
5.2 KiB
C
// 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);
|