| // 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, 0); |
| |
| /* Set data */ |
| #ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO |
| if (data == prev_data) { |
| gpiod_set_value(par->gpio.wr, 0); /* 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, 1); |
| |
| #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, 0); |
| |
| /* Set data */ |
| #ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO |
| if (data == prev_data) { |
| gpiod_set_value(par->gpio.wr, 0); /* 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, 1); |
| |
| #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); |