diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 815de379a1309c9acc49366ccd9a2a6ef3cf44b3..ce8f420e9fa892bb9c451cdbfad703f04931f05e 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -106,4 +106,6 @@ source "drivers/staging/unisys/Kconfig" source "drivers/staging/clocking-wizard/Kconfig" +source "drivers/staging/fbtft/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 33c640b4956649cea2ecd50e6dc5439669004842..c242787308b83686c315be6789bdfa3e167e9adb 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -45,3 +45,4 @@ obj-$(CONFIG_GS_FPGABOOT) += gs_fpgaboot/ obj-$(CONFIG_CRYPTO_SKEIN) += skein/ obj-$(CONFIG_UNISYSSPAR) += unisys/ obj-$(CONFIG_COMMON_CLK_XLNX_CLKWZRD) += clocking-wizard/ +obj-$(CONFIG_FB_TFT) += fbtft/ diff --git a/drivers/staging/fbtft/Kconfig b/drivers/staging/fbtft/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..3d6598b311819bb12df54dd814234efa428891db --- /dev/null +++ b/drivers/staging/fbtft/Kconfig @@ -0,0 +1,9 @@ +menuconfig FB_TFT + tristate "Support for small TFT LCD display modules" + depends on FB && SPI && GPIOLIB + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + select FB_BACKLIGHT diff --git a/drivers/staging/fbtft/Makefile b/drivers/staging/fbtft/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9132dde4a152b531bfce5fb9abf3badc46805608 --- /dev/null +++ b/drivers/staging/fbtft/Makefile @@ -0,0 +1,3 @@ +# Core module +obj-$(CONFIG_FB_TFT) += fbtft.o +fbtft-y += fbtft-core.o fbtft-sysfs.o fbtft-bus.o fbtft-io.o diff --git a/drivers/staging/fbtft/README b/drivers/staging/fbtft/README new file mode 100644 index 0000000000000000000000000000000000000000..bc89b5805f7bcf15beefc08656e3dcd608af4a39 --- /dev/null +++ b/drivers/staging/fbtft/README @@ -0,0 +1,32 @@ + FBTFT +========= + +Linux Framebuffer drivers for small TFT LCD display modules. +The module 'fbtft' makes writing drivers for some of these displays very easy. + +Development is done on a Raspberry Pi running the Raspbian "wheezy" distribution. + +INSTALLATION + Download kernel sources + + From Linux 3.15 + cd drivers/video/fbdev/fbtft + git clone https://github.com/notro/fbtft.git + + Add to drivers/video/fbdev/Kconfig: source "drivers/video/fbdev/fbtft/Kconfig" + Add to drivers/video/fbdev/Makefile: obj-y += fbtft/ + + Before Linux 3.15 + cd drivers/video + git clone https://github.com/notro/fbtft.git + + Add to drivers/video/Kconfig: source "drivers/video/fbtft/Kconfig" + Add to drivers/video/Makefile: obj-y += fbtft/ + + Enable driver(s) in menuconfig and build the kernel + + +See wiki for more information: https://github.com/notro/fbtft/wiki + + +Source: https://github.com/notro/fbtft/ diff --git a/drivers/staging/fbtft/fbtft-bus.c b/drivers/staging/fbtft/fbtft-bus.c new file mode 100644 index 0000000000000000000000000000000000000000..b3cddb0b3d698a9d606ece34e49288348f9e769b --- /dev/null +++ b/drivers/staging/fbtft/fbtft-bus.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include "fbtft.h" + + + + +/***************************************************************************** + * + * void (*write_reg)(struct fbtft_par *par, int len, ...); + * + *****************************************************************************/ + +#define define_fbtft_write_reg(func, type, modifier) \ +void func(struct fbtft_par *par, int len, ...) \ +{ \ + va_list args; \ + int i, ret; \ + int offset = 0; \ + type *buf = (type *)par->buf; \ + \ + if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { \ + va_start(args, len); \ + for (i = 0; i < len; i++) { \ + buf[i] = (type)va_arg(args, unsigned int); \ + } \ + va_end(args); \ + fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, par->info->device, type, buf, len, "%s: ", __func__); \ + } \ + \ + va_start(args, len); \ + \ + if (par->startbyte) { \ + *(u8 *)par->buf = par->startbyte; \ + buf = (type *)(par->buf + 1); \ + offset = 1; \ + } \ + \ + *buf = modifier((type)va_arg(args, unsigned int)); \ + if (par->gpio.dc != -1) \ + gpio_set_value(par->gpio.dc, 0); \ + ret = par->fbtftops.write(par, par->buf, sizeof(type)+offset); \ + if (ret < 0) { \ + va_end(args); \ + dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret); \ + return; \ + } \ + len--; \ + \ + if (par->startbyte) \ + *(u8 *)par->buf = par->startbyte | 0x2; \ + \ + if (len) { \ + i = len; \ + while (i--) { \ + *buf++ = modifier((type)va_arg(args, unsigned int)); \ + } \ + if (par->gpio.dc != -1) \ + gpio_set_value(par->gpio.dc, 1); \ + ret = par->fbtftops.write(par, par->buf, len * (sizeof(type)+offset)); \ + if (ret < 0) { \ + va_end(args); \ + dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret); \ + return; \ + } \ + } \ + va_end(args); \ +} \ +EXPORT_SYMBOL(func); + +define_fbtft_write_reg(fbtft_write_reg8_bus8, u8, ) +define_fbtft_write_reg(fbtft_write_reg16_bus8, u16, cpu_to_be16) +define_fbtft_write_reg(fbtft_write_reg16_bus16, u16, ) + +void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...) +{ + va_list args; + int i, ret; + int pad = 0; + u16 *buf = (u16 *)par->buf; + + if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { + va_start(args, len); + for (i = 0; i < len; i++) + *(((u8 *)buf) + i) = (u8)va_arg(args, unsigned int); + va_end(args); + fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, + par->info->device, u8, buf, len, "%s: ", __func__); + } + if (len <= 0) + return; + + if (par->spi && (par->spi->bits_per_word == 8)) { + /* we're emulating 9-bit, pad start of buffer with no-ops + (assuming here that zero is a no-op) */ + pad = (len % 4) ? 4 - (len % 4) : 0; + for (i = 0; i < pad; i++) + *buf++ = 0x000; + } + + va_start(args, len); + *buf++ = (u8)va_arg(args, unsigned int); + i = len - 1; + while (i--) { + *buf = (u8)va_arg(args, unsigned int); + *buf++ |= 0x100; /* dc=1 */ + } + va_end(args); + ret = par->fbtftops.write(par, par->buf, (len + pad) * sizeof(u16)); + if (ret < 0) { + dev_err(par->info->device, + "%s: write() failed and returned %d\n", __func__, ret); + return; + } +} +EXPORT_SYMBOL(fbtft_write_reg8_bus9); + + + + +/***************************************************************************** + * + * int (*write_vmem)(struct fbtft_par *par); + * + *****************************************************************************/ + +/* 16 bit pixel over 8-bit databus */ +int fbtft_write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16; + u16 *txbuf16 = (u16 *)par->txbuf.buf; + size_t remain; + size_t to_copy; + size_t tx_array_size; + int i; + int ret = 0; + size_t startbyte_size = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n", + __func__, offset, len); + + remain = len / 2; + vmem16 = (u16 *)(par->info->screen_base + offset); + + if (par->gpio.dc != -1) + gpio_set_value(par->gpio.dc, 1); + + /* non buffered write */ + if (!par->txbuf.buf) + return par->fbtftops.write(par, vmem16, len); + + /* buffered write */ + tx_array_size = par->txbuf.len / 2; + + if (par->startbyte) { + txbuf16 = (u16 *)(par->txbuf.buf + 1); + tx_array_size -= 2; + *(u8 *)(par->txbuf.buf) = par->startbyte | 0x2; + startbyte_size = 1; + } + + while (remain) { + to_copy = remain > tx_array_size ? tx_array_size : remain; + dev_dbg(par->info->device, " to_copy=%zu, remain=%zu\n", + to_copy, remain - to_copy); + + for (i = 0; i < to_copy; i++) + txbuf16[i] = cpu_to_be16(vmem16[i]); + + vmem16 = vmem16 + to_copy; + ret = par->fbtftops.write(par, par->txbuf.buf, + startbyte_size + to_copy * 2); + if (ret < 0) + return ret; + remain -= to_copy; + } + + return ret; +} +EXPORT_SYMBOL(fbtft_write_vmem16_bus8); + +/* 16 bit pixel over 9-bit SPI bus: dc + high byte, dc + low byte */ +int fbtft_write_vmem16_bus9(struct fbtft_par *par, size_t offset, size_t len) +{ + u8 *vmem8; + u16 *txbuf16 = par->txbuf.buf; + size_t remain; + size_t to_copy; + size_t tx_array_size; + int i; + int ret = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n", + __func__, offset, len); + + if (!par->txbuf.buf) { + dev_err(par->info->device, "%s: txbuf.buf is NULL\n", __func__); + return -1; + } + + remain = len; + vmem8 = par->info->screen_base + offset; + + tx_array_size = par->txbuf.len / 2; + + while (remain) { + to_copy = remain > tx_array_size ? tx_array_size : remain; + dev_dbg(par->info->device, " to_copy=%zu, remain=%zu\n", + to_copy, remain - to_copy); + +#ifdef __LITTLE_ENDIAN + for (i = 0; i < to_copy; i += 2) { + txbuf16[i] = 0x0100 | vmem8[i+1]; + txbuf16[i+1] = 0x0100 | vmem8[i]; + } +#else + for (i = 0; i < to_copy; i++) + txbuf16[i] = 0x0100 | vmem8[i]; +#endif + vmem8 = vmem8 + to_copy; + ret = par->fbtftops.write(par, par->txbuf.buf, to_copy*2); + if (ret < 0) + return ret; + remain -= to_copy; + } + + return ret; +} +EXPORT_SYMBOL(fbtft_write_vmem16_bus9); + +int fbtft_write_vmem8_bus8(struct fbtft_par *par, size_t offset, size_t len) +{ + dev_err(par->info->device, "%s: function not implemented\n", __func__); + return -1; +} +EXPORT_SYMBOL(fbtft_write_vmem8_bus8); + +/* 16 bit pixel over 16-bit databus */ +int fbtft_write_vmem16_bus16(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n", + __func__, offset, len); + + vmem16 = (u16 *)(par->info->screen_base + offset); + + if (par->gpio.dc != -1) + gpio_set_value(par->gpio.dc, 1); + + /* no need for buffered write with 16-bit bus */ + return par->fbtftops.write(par, vmem16, len); +} +EXPORT_SYMBOL(fbtft_write_vmem16_bus16); diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c new file mode 100644 index 0000000000000000000000000000000000000000..873e2c72a4d72d82cb456b653184c9149f931a0c --- /dev/null +++ b/drivers/staging/fbtft/fbtft-core.c @@ -0,0 +1,1516 @@ +/* + * Copyright (C) 2013 Noralf Tronnes + * + * This driver is inspired by: + * st7735fb.c, Copyright (C) 2011, Matt Porter + * broadsheetfb.c, Copyright (C) 2008, Jaya Kumar + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +extern void fbtft_sysfs_init(struct fbtft_par *par); +extern void fbtft_sysfs_exit(struct fbtft_par *par); +extern void fbtft_expand_debug_value(unsigned long *debug); +extern int fbtft_gamma_parse_str(struct fbtft_par *par, unsigned long *curves, + const char *str, int size); + +static unsigned long debug; +module_param(debug, ulong , 0); +MODULE_PARM_DESC(debug, "override device debug level"); + +static bool dma = true; +module_param(dma, bool, 0); +MODULE_PARM_DESC(dma, "Use DMA buffer"); + + +void fbtft_dbg_hex(const struct device *dev, int groupsize, + void *buf, size_t len, const char *fmt, ...) +{ + va_list args; + static char textbuf[512]; + char *text = textbuf; + size_t text_len; + + va_start(args, fmt); + text_len = vscnprintf(text, sizeof(textbuf), fmt, args); + va_end(args); + + hex_dump_to_buffer(buf, len, 32, groupsize, text + text_len, + 512 - text_len, false); + + if (len > 32) + dev_info(dev, "%s ...\n", text); + else + dev_info(dev, "%s\n", text); +} +EXPORT_SYMBOL(fbtft_dbg_hex); + +unsigned long fbtft_request_gpios_match(struct fbtft_par *par, + const struct fbtft_gpio *gpio) +{ + int ret; + long val; + + fbtft_par_dbg(DEBUG_REQUEST_GPIOS_MATCH, par, "%s('%s')\n", + __func__, gpio->name); + + if (strcasecmp(gpio->name, "reset") == 0) { + par->gpio.reset = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "dc") == 0) { + par->gpio.dc = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } else if (strcasecmp(gpio->name, "cs") == 0) { + par->gpio.cs = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "wr") == 0) { + par->gpio.wr = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "rd") == 0) { + par->gpio.rd = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "latch") == 0) { + par->gpio.latch = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } else if (gpio->name[0] == 'd' && gpio->name[1] == 'b') { + ret = kstrtol(&gpio->name[2], 10, &val); + if (ret == 0 && val < 16) { + par->gpio.db[val] = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } + } else if (strcasecmp(gpio->name, "led") == 0) { + par->gpio.led[0] = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } else if (strcasecmp(gpio->name, "led_") == 0) { + par->gpio.led[0] = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } + + return FBTFT_GPIO_NO_MATCH; +} + +int fbtft_request_gpios(struct fbtft_par *par) +{ + struct fbtft_platform_data *pdata = par->pdata; + const struct fbtft_gpio *gpio; + unsigned long flags; + int ret; + + if (pdata && pdata->gpios) { + gpio = pdata->gpios; + while (gpio->name[0]) { + flags = FBTFT_GPIO_NO_MATCH; + /* if driver provides match function, try it first, + if no match use our own */ + if (par->fbtftops.request_gpios_match) + flags = par->fbtftops.request_gpios_match(par, gpio); + if (flags == FBTFT_GPIO_NO_MATCH) + flags = fbtft_request_gpios_match(par, gpio); + if (flags != FBTFT_GPIO_NO_MATCH) { + ret = devm_gpio_request_one(par->info->device, + gpio->gpio, flags, + par->info->device->driver->name); + if (ret < 0) { + dev_err(par->info->device, + "%s: gpio_request_one('%s'=%d) failed with %d\n", + __func__, gpio->name, + gpio->gpio, ret); + return ret; + } + fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, + "%s: '%s' = GPIO%d\n", + __func__, gpio->name, gpio->gpio); + } + gpio++; + } + } + + return 0; +} + +#ifdef CONFIG_OF +static int fbtft_request_one_gpio(struct fbtft_par *par, + const char *name, int index, int *gpiop) +{ + struct device *dev = par->info->device; + struct device_node *node = dev->of_node; + int gpio, flags, ret = 0; + enum of_gpio_flags of_flags; + + if (of_find_property(node, name, NULL)) { + gpio = of_get_named_gpio_flags(node, name, index, &of_flags); + if (gpio == -ENOENT) + return 0; + if (gpio == -EPROBE_DEFER) + return gpio; + if (gpio < 0) { + dev_err(dev, + "failed to get '%s' from DT\n", name); + return gpio; + } + + /* active low translates to initially low */ + flags = (of_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_LOW : + GPIOF_OUT_INIT_HIGH; + ret = devm_gpio_request_one(dev, gpio, flags, + dev->driver->name); + if (ret) { + dev_err(dev, + "gpio_request_one('%s'=%d) failed with %d\n", + name, gpio, ret); + return ret; + } + if (gpiop) + *gpiop = gpio; + fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' = GPIO%d\n", + __func__, name, gpio); + } + + return ret; +} + +static int fbtft_request_gpios_dt(struct fbtft_par *par) +{ + int i; + int ret; + + if (!par->info->device->of_node) + return -EINVAL; + + ret = fbtft_request_one_gpio(par, "reset-gpios", 0, &par->gpio.reset); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "dc-gpios", 0, &par->gpio.dc); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "rd-gpios", 0, &par->gpio.rd); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "wr-gpios", 0, &par->gpio.wr); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "cs-gpios", 0, &par->gpio.cs); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "latch-gpios", 0, &par->gpio.latch); + if (ret) + return ret; + for (i = 0; i < 16; i++) { + ret = fbtft_request_one_gpio(par, "db-gpios", i, + &par->gpio.db[i]); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "led-gpios", i, + &par->gpio.led[i]); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "aux-gpios", i, + &par->gpio.aux[i]); + if (ret) + return ret; + } + + return 0; +} +#endif + +#ifdef CONFIG_FB_BACKLIGHT +int fbtft_backlight_update_status(struct backlight_device *bd) +{ + struct fbtft_par *par = bl_get_data(bd); + bool polarity = !!(bd->props.state & BL_CORE_DRIVER1); + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, + "%s: polarity=%d, power=%d, fb_blank=%d\n", + __func__, polarity, bd->props.power, bd->props.fb_blank); + + if ((bd->props.power == FB_BLANK_UNBLANK) && (bd->props.fb_blank == FB_BLANK_UNBLANK)) + gpio_set_value(par->gpio.led[0], polarity); + else + gpio_set_value(par->gpio.led[0], !polarity); + + return 0; +} + +int fbtft_backlight_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +void fbtft_unregister_backlight(struct fbtft_par *par) +{ + const struct backlight_ops *bl_ops; + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, "%s()\n", __func__); + + if (par->info->bl_dev) { + par->info->bl_dev->props.power = FB_BLANK_POWERDOWN; + backlight_update_status(par->info->bl_dev); + bl_ops = par->info->bl_dev->ops; + backlight_device_unregister(par->info->bl_dev); + par->info->bl_dev = NULL; + } +} + +void fbtft_register_backlight(struct fbtft_par *par) +{ + struct backlight_device *bd; + struct backlight_properties bl_props = { 0, }; + struct backlight_ops *bl_ops; + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, "%s()\n", __func__); + + if (par->gpio.led[0] == -1) { + fbtft_par_dbg(DEBUG_BACKLIGHT, par, + "%s(): led pin not set, exiting.\n", __func__); + return; + } + + bl_ops = devm_kzalloc(par->info->device, sizeof(struct backlight_ops), + GFP_KERNEL); + if (!bl_ops) { + dev_err(par->info->device, + "%s: could not allocate memeory for backlight operations.\n", + __func__); + return; + } + + bl_ops->get_brightness = fbtft_backlight_get_brightness; + bl_ops->update_status = fbtft_backlight_update_status; + bl_props.type = BACKLIGHT_RAW; + /* Assume backlight is off, get polarity from current state of pin */ + bl_props.power = FB_BLANK_POWERDOWN; + if (!gpio_get_value(par->gpio.led[0])) + bl_props.state |= BL_CORE_DRIVER1; + + bd = backlight_device_register(dev_driver_string(par->info->device), + par->info->device, par, bl_ops, &bl_props); + if (IS_ERR(bd)) { + dev_err(par->info->device, + "cannot register backlight device (%ld)\n", + PTR_ERR(bd)); + return; + } + par->info->bl_dev = bd; + + if (!par->fbtftops.unregister_backlight) + par->fbtftops.unregister_backlight = fbtft_unregister_backlight; +} +#else +void fbtft_register_backlight(struct fbtft_par *par) { }; +void fbtft_unregister_backlight(struct fbtft_par *par) { }; +#endif +EXPORT_SYMBOL(fbtft_register_backlight); +EXPORT_SYMBOL(fbtft_unregister_backlight); + +void fbtft_set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Column address set */ + write_reg(par, 0x2A, + (xs >> 8) & 0xFF, xs & 0xFF, (xe >> 8) & 0xFF, xe & 0xFF); + + /* Row adress set */ + write_reg(par, 0x2B, + (ys >> 8) & 0xFF, ys & 0xFF, (ye >> 8) & 0xFF, ye & 0xFF); + + /* Memory write */ + write_reg(par, 0x2C); +} + + +void fbtft_reset(struct fbtft_par *par) +{ + if (par->gpio.reset == -1) + return; + fbtft_par_dbg(DEBUG_RESET, par, "%s()\n", __func__); + gpio_set_value(par->gpio.reset, 0); + udelay(20); + gpio_set_value(par->gpio.reset, 1); + mdelay(120); +} + + +void fbtft_update_display(struct fbtft_par *par, unsigned start_line, unsigned end_line) +{ + size_t offset, len; + struct timespec ts_start, ts_end, ts_fps, ts_duration; + long fps_ms, fps_us, duration_ms, duration_us; + long fps, throughput; + bool timeit = false; + int ret = 0; + + if (unlikely(par->debug & (DEBUG_TIME_FIRST_UPDATE | DEBUG_TIME_EACH_UPDATE))) { + if ((par->debug & DEBUG_TIME_EACH_UPDATE) || \ + ((par->debug & DEBUG_TIME_FIRST_UPDATE) && !par->first_update_done)) { + getnstimeofday(&ts_start); + timeit = true; + } + } + + /* Sanity checks */ + if (start_line > end_line) { + dev_warn(par->info->device, + "%s: start_line=%u is larger than end_line=%u. Shouldn't happen, will do full display update\n", + __func__, start_line, end_line); + start_line = 0; + end_line = par->info->var.yres - 1; + } + if (start_line > par->info->var.yres - 1 || end_line > par->info->var.yres - 1) { + dev_warn(par->info->device, + "%s: start_line=%u or end_line=%u is larger than max=%d. Shouldn't happen, will do full display update\n", + __func__, start_line, end_line, par->info->var.yres - 1); + start_line = 0; + end_line = par->info->var.yres - 1; + } + + fbtft_par_dbg(DEBUG_UPDATE_DISPLAY, par, "%s(start_line=%u, end_line=%u)\n", + __func__, start_line, end_line); + + if (par->fbtftops.set_addr_win) + par->fbtftops.set_addr_win(par, 0, start_line, + par->info->var.xres-1, end_line); + + offset = start_line * par->info->fix.line_length; + len = (end_line - start_line + 1) * par->info->fix.line_length; + ret = par->fbtftops.write_vmem(par, offset, len); + if (ret < 0) + dev_err(par->info->device, + "%s: write_vmem failed to update display buffer\n", + __func__); + + if (unlikely(timeit)) { + getnstimeofday(&ts_end); + if (par->update_time.tv_nsec == 0 && par->update_time.tv_sec == 0) { + par->update_time.tv_sec = ts_start.tv_sec; + par->update_time.tv_nsec = ts_start.tv_nsec; + } + ts_fps = timespec_sub(ts_start, par->update_time); + par->update_time.tv_sec = ts_start.tv_sec; + par->update_time.tv_nsec = ts_start.tv_nsec; + fps_ms = (ts_fps.tv_sec * 1000) + ((ts_fps.tv_nsec / 1000000) % 1000); + fps_us = (ts_fps.tv_nsec / 1000) % 1000; + fps = fps_ms * 1000 + fps_us; + fps = fps ? 1000000 / fps : 0; + + ts_duration = timespec_sub(ts_end, ts_start); + duration_ms = (ts_duration.tv_sec * 1000) + ((ts_duration.tv_nsec / 1000000) % 1000); + duration_us = (ts_duration.tv_nsec / 1000) % 1000; + throughput = duration_ms * 1000 + duration_us; + throughput = throughput ? (len * 1000) / throughput : 0; + throughput = throughput * 1000 / 1024; + + dev_info(par->info->device, + "Display update: %ld kB/s (%ld.%.3ld ms), fps=%ld (%ld.%.3ld ms)\n", + throughput, duration_ms, duration_us, + fps, fps_ms, fps_us); + par->first_update_done = true; + } +} + + +void fbtft_mkdirty(struct fb_info *info, int y, int height) +{ + struct fbtft_par *par = info->par; + struct fb_deferred_io *fbdefio = info->fbdefio; + + /* special case, needed ? */ + if (y == -1) { + y = 0; + height = info->var.yres - 1; + } + + /* Mark display lines/area as dirty */ + spin_lock(&par->dirty_lock); + if (y < par->dirty_lines_start) + par->dirty_lines_start = y; + if (y + height - 1 > par->dirty_lines_end) + par->dirty_lines_end = y + height - 1; + spin_unlock(&par->dirty_lock); + + /* Schedule deferred_io to update display (no-op if already on queue)*/ + schedule_delayed_work(&info->deferred_work, fbdefio->delay); +} + +void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist) +{ + struct fbtft_par *par = info->par; + unsigned dirty_lines_start, dirty_lines_end; + struct page *page; + unsigned long index; + unsigned y_low = 0, y_high = 0; + int count = 0; + + spin_lock(&par->dirty_lock); + dirty_lines_start = par->dirty_lines_start; + dirty_lines_end = par->dirty_lines_end; + /* set display line markers as clean */ + par->dirty_lines_start = par->info->var.yres - 1; + par->dirty_lines_end = 0; + spin_unlock(&par->dirty_lock); + + /* Mark display lines as dirty */ + list_for_each_entry(page, pagelist, lru) { + count++; + index = page->index << PAGE_SHIFT; + y_low = index / info->fix.line_length; + y_high = (index + PAGE_SIZE - 1) / info->fix.line_length; + fbtft_dev_dbg(DEBUG_DEFERRED_IO, par, info->device, + "page->index=%lu y_low=%d y_high=%d\n", + page->index, y_low, y_high); + if (y_high > info->var.yres - 1) + y_high = info->var.yres - 1; + if (y_low < dirty_lines_start) + dirty_lines_start = y_low; + if (y_high > dirty_lines_end) + dirty_lines_end = y_high; + } + + par->fbtftops.update_display(info->par, + dirty_lines_start, dirty_lines_end); +} + + +void fbtft_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct fbtft_par *par = info->par; + + fbtft_dev_dbg(DEBUG_FB_FILLRECT, par, info->dev, + "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, rect->dx, rect->dy, rect->width, rect->height); + sys_fillrect(info, rect); + + par->fbtftops.mkdirty(info, rect->dy, rect->height); +} + +void fbtft_fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct fbtft_par *par = info->par; + + fbtft_dev_dbg(DEBUG_FB_COPYAREA, par, info->dev, + "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, area->dx, area->dy, area->width, area->height); + sys_copyarea(info, area); + + par->fbtftops.mkdirty(info, area->dy, area->height); +} + +void fbtft_fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct fbtft_par *par = info->par; + + fbtft_dev_dbg(DEBUG_FB_IMAGEBLIT, par, info->dev, + "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, image->dx, image->dy, image->width, image->height); + sys_imageblit(info, image); + + par->fbtftops.mkdirty(info, image->dy, image->height); +} + +ssize_t fbtft_fb_write(struct fb_info *info, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct fbtft_par *par = info->par; + ssize_t res; + + fbtft_dev_dbg(DEBUG_FB_WRITE, par, info->dev, + "%s: count=%zd, ppos=%llu\n", __func__, count, *ppos); + res = fb_sys_write(info, buf, count, ppos); + + /* TODO: only mark changed area + update all for now */ + par->fbtftops.mkdirty(info, -1, 0); + + return res; +} + +/* from pxafb.c */ +unsigned int chan_to_field(unsigned chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +int fbtft_fb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct fbtft_par *par = info->par; + unsigned val; + int ret = 1; + + fbtft_dev_dbg(DEBUG_FB_SETCOLREG, par, info->dev, + "%s(regno=%u, red=0x%X, green=0x%X, blue=0x%X, trans=0x%X)\n", + __func__, regno, red, green, blue, transp); + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + } + return ret; +} + +int fbtft_fb_blank(int blank, struct fb_info *info) +{ + struct fbtft_par *par = info->par; + int ret = -EINVAL; + + fbtft_dev_dbg(DEBUG_FB_BLANK, par, info->dev, "%s(blank=%d)\n", + __func__, blank); + + if (!par->fbtftops.blank) + return ret; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ret = par->fbtftops.blank(par, true); + break; + case FB_BLANK_UNBLANK: + ret = par->fbtftops.blank(par, false); + break; + } + return ret; +} + +void fbtft_merge_fbtftops(struct fbtft_ops *dst, struct fbtft_ops *src) +{ + if (src->write) + dst->write = src->write; + if (src->read) + dst->read = src->read; + if (src->write_vmem) + dst->write_vmem = src->write_vmem; + if (src->write_register) + dst->write_register = src->write_register; + if (src->set_addr_win) + dst->set_addr_win = src->set_addr_win; + if (src->reset) + dst->reset = src->reset; + if (src->mkdirty) + dst->mkdirty = src->mkdirty; + if (src->update_display) + dst->update_display = src->update_display; + if (src->init_display) + dst->init_display = src->init_display; + if (src->blank) + dst->blank = src->blank; + if (src->request_gpios_match) + dst->request_gpios_match = src->request_gpios_match; + if (src->request_gpios) + dst->request_gpios = src->request_gpios; + if (src->verify_gpios) + dst->verify_gpios = src->verify_gpios; + if (src->register_backlight) + dst->register_backlight = src->register_backlight; + if (src->unregister_backlight) + dst->unregister_backlight = src->unregister_backlight; + if (src->set_var) + dst->set_var = src->set_var; + if (src->set_gamma) + dst->set_gamma = src->set_gamma; +} + +/** + * fbtft_framebuffer_alloc - creates a new frame buffer info structure + * + * @display: pointer to structure describing the display + * @dev: pointer to the device for this fb, this can be NULL + * + * Creates a new frame buffer info structure. + * + * Also creates and populates the following structures: + * info->fbops + * info->fbdefio + * info->pseudo_palette + * par->fbtftops + * par->txbuf + * + * Returns the new structure, or NULL if an error occurred. + * + */ +struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display, + struct device *dev) +{ + struct fb_info *info; + struct fbtft_par *par; + struct fb_ops *fbops = NULL; + struct fb_deferred_io *fbdefio = NULL; + struct fbtft_platform_data *pdata = dev->platform_data; + u8 *vmem = NULL; + void *txbuf = NULL; + void *buf = NULL; + unsigned width; + unsigned height; + int txbuflen = display->txbuflen; + unsigned bpp = display->bpp; + unsigned fps = display->fps; + int vmem_size, i; + int *init_sequence = display->init_sequence; + char *gamma = display->gamma; + unsigned long *gamma_curves = NULL; + + /* sanity check */ + if (display->gamma_num * display->gamma_len > FBTFT_GAMMA_MAX_VALUES_TOTAL) { + dev_err(dev, + "%s: FBTFT_GAMMA_MAX_VALUES_TOTAL=%d is exceeded\n", + __func__, FBTFT_GAMMA_MAX_VALUES_TOTAL); + return NULL; + } + + /* defaults */ + if (!fps) + fps = 20; + if (!bpp) + bpp = 16; + + if (!pdata) { + dev_err(dev, "platform data is missing\n"); + return NULL; + } + + /* override driver values? */ + if (pdata->fps) + fps = pdata->fps; + if (pdata->txbuflen) + txbuflen = pdata->txbuflen; + if (pdata->display.init_sequence) + init_sequence = pdata->display.init_sequence; + if (pdata->gamma) + gamma = pdata->gamma; + if (pdata->display.debug) + display->debug = pdata->display.debug; + if (pdata->display.backlight) + display->backlight = pdata->display.backlight; + if (pdata->display.width) + display->width = pdata->display.width; + if (pdata->display.height) + display->height = pdata->display.height; + if (pdata->display.buswidth) + display->buswidth = pdata->display.buswidth; + if (pdata->display.regwidth) + display->regwidth = pdata->display.regwidth; + + display->debug |= debug; + fbtft_expand_debug_value(&display->debug); + + switch (pdata->rotate) { + case 90: + case 270: + width = display->height; + height = display->width; + break; + default: + width = display->width; + height = display->height; + } + + vmem_size = display->width * display->height * bpp / 8; + vmem = vzalloc(vmem_size); + if (!vmem) + goto alloc_fail; + + fbops = devm_kzalloc(dev, sizeof(struct fb_ops), GFP_KERNEL); + if (!fbops) + goto alloc_fail; + + fbdefio = devm_kzalloc(dev, sizeof(struct fb_deferred_io), GFP_KERNEL); + if (!fbdefio) + goto alloc_fail; + + buf = devm_kzalloc(dev, 128, GFP_KERNEL); + if (!buf) + goto alloc_fail; + + if (display->gamma_num && display->gamma_len) { + gamma_curves = devm_kzalloc(dev, display->gamma_num * display->gamma_len * sizeof(gamma_curves[0]), + GFP_KERNEL); + if (!gamma_curves) + goto alloc_fail; + } + + info = framebuffer_alloc(sizeof(struct fbtft_par), dev); + if (!info) + goto alloc_fail; + + info->screen_base = (u8 __force __iomem *)vmem; + info->fbops = fbops; + info->fbdefio = fbdefio; + + fbops->owner = dev->driver->owner; + fbops->fb_read = fb_sys_read; + fbops->fb_write = fbtft_fb_write; + fbops->fb_fillrect = fbtft_fb_fillrect; + fbops->fb_copyarea = fbtft_fb_copyarea; + fbops->fb_imageblit = fbtft_fb_imageblit; + fbops->fb_setcolreg = fbtft_fb_setcolreg; + fbops->fb_blank = fbtft_fb_blank; + + fbdefio->delay = HZ/fps; + fbdefio->deferred_io = fbtft_deferred_io; + fb_deferred_io_init(info); + + strncpy(info->fix.id, dev->driver->name, 16); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + info->fix.line_length = width*bpp/8; + info->fix.accel = FB_ACCEL_NONE; + info->fix.smem_len = vmem_size; + + info->var.rotate = pdata->rotate; + info->var.xres = width; + info->var.yres = height; + info->var.xres_virtual = info->var.xres; + info->var.yres_virtual = info->var.yres; + info->var.bits_per_pixel = bpp; + info->var.nonstd = 1; + + /* RGB565 */ + info->var.red.offset = 11; + info->var.red.length = 5; + info->var.green.offset = 5; + info->var.green.length = 6; + info->var.blue.offset = 0; + info->var.blue.length = 5; + info->var.transp.offset = 0; + info->var.transp.length = 0; + + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; + + par = info->par; + par->info = info; + par->pdata = dev->platform_data; + par->debug = display->debug; + par->buf = buf; + spin_lock_init(&par->dirty_lock); + par->bgr = pdata->bgr; + par->startbyte = pdata->startbyte; + par->init_sequence = init_sequence; + par->gamma.curves = gamma_curves; + par->gamma.num_curves = display->gamma_num; + par->gamma.num_values = display->gamma_len; + mutex_init(&par->gamma.lock); + info->pseudo_palette = par->pseudo_palette; + + if (par->gamma.curves && gamma) { + if (fbtft_gamma_parse_str(par, + par->gamma.curves, gamma, strlen(gamma))) + goto alloc_fail; + } + + /* Transmit buffer */ + if (txbuflen == -1) + txbuflen = vmem_size + 2; /* add in case startbyte is used */ + +#ifdef __LITTLE_ENDIAN + if ((!txbuflen) && (bpp > 8)) + txbuflen = PAGE_SIZE; /* need buffer for byteswapping */ +#endif + + if (txbuflen > 0) { + if (dma) { + dev->coherent_dma_mask = ~0; + txbuf = dmam_alloc_coherent(dev, txbuflen, &par->txbuf.dma, GFP_DMA); + } else { + txbuf = devm_kzalloc(par->info->device, txbuflen, GFP_KERNEL); + } + if (!txbuf) + goto alloc_fail; + par->txbuf.buf = txbuf; + par->txbuf.len = txbuflen; + } + + /* Initialize gpios to disabled */ + par->gpio.reset = -1; + par->gpio.dc = -1; + par->gpio.rd = -1; + par->gpio.wr = -1; + par->gpio.cs = -1; + par->gpio.latch = -1; + for (i = 0; i < 16; i++) { + par->gpio.db[i] = -1; + par->gpio.led[i] = -1; + par->gpio.aux[i] = -1; + } + + /* default fbtft operations */ + par->fbtftops.write = fbtft_write_spi; + par->fbtftops.read = fbtft_read_spi; + par->fbtftops.write_vmem = fbtft_write_vmem16_bus8; + par->fbtftops.write_register = fbtft_write_reg8_bus8; + par->fbtftops.set_addr_win = fbtft_set_addr_win; + par->fbtftops.reset = fbtft_reset; + par->fbtftops.mkdirty = fbtft_mkdirty; + par->fbtftops.update_display = fbtft_update_display; + par->fbtftops.request_gpios = fbtft_request_gpios; + if (display->backlight) + par->fbtftops.register_backlight = fbtft_register_backlight; + + /* use driver provided functions */ + fbtft_merge_fbtftops(&par->fbtftops, &display->fbtftops); + + return info; + +alloc_fail: + vfree(vmem); + + return NULL; +} +EXPORT_SYMBOL(fbtft_framebuffer_alloc); + +/** + * fbtft_framebuffer_release - frees up all memory used by the framebuffer + * + * @info: frame buffer info structure + * + */ +void fbtft_framebuffer_release(struct fb_info *info) +{ + fb_deferred_io_cleanup(info); + vfree(info->screen_base); + framebuffer_release(info); +} +EXPORT_SYMBOL(fbtft_framebuffer_release); + +/** + * fbtft_register_framebuffer - registers a tft frame buffer device + * @fb_info: frame buffer info structure + * + * Sets SPI driverdata if needed + * Requests needed gpios. + * Initializes display + * Updates display. + * Registers a frame buffer device @fb_info. + * + * Returns negative errno on error, or zero for success. + * + */ +int fbtft_register_framebuffer(struct fb_info *fb_info) +{ + int ret; + char text1[50] = ""; + char text2[50] = ""; + struct fbtft_par *par = fb_info->par; + struct spi_device *spi = par->spi; + + /* sanity checks */ + if (!par->fbtftops.init_display) { + dev_err(fb_info->device, "missing fbtftops.init_display()\n"); + return -EINVAL; + } + + if (spi) + spi_set_drvdata(spi, fb_info); + if (par->pdev) + platform_set_drvdata(par->pdev, fb_info); + + ret = par->fbtftops.request_gpios(par); + if (ret < 0) + goto reg_fail; + + if (par->fbtftops.verify_gpios) { + ret = par->fbtftops.verify_gpios(par); + if (ret < 0) + goto reg_fail; + } + + ret = par->fbtftops.init_display(par); + if (ret < 0) + goto reg_fail; + if (par->fbtftops.set_var) { + ret = par->fbtftops.set_var(par); + if (ret < 0) + goto reg_fail; + } + + /* update the entire display */ + par->fbtftops.update_display(par, 0, par->info->var.yres - 1); + + if (par->fbtftops.set_gamma && par->gamma.curves) { + ret = par->fbtftops.set_gamma(par, par->gamma.curves); + if (ret) + goto reg_fail; + } + + if (par->fbtftops.register_backlight) + par->fbtftops.register_backlight(par); + + ret = register_framebuffer(fb_info); + if (ret < 0) + goto reg_fail; + + fbtft_sysfs_init(par); + + if (par->txbuf.buf) + sprintf(text1, ", %d KiB %sbuffer memory", + par->txbuf.len >> 10, par->txbuf.dma ? "DMA " : ""); + if (spi) + sprintf(text2, ", spi%d.%d at %d MHz", spi->master->bus_num, + spi->chip_select, spi->max_speed_hz/1000000); + dev_info(fb_info->dev, + "%s frame buffer, %dx%d, %d KiB video memory%s, fps=%lu%s\n", + fb_info->fix.id, fb_info->var.xres, fb_info->var.yres, + fb_info->fix.smem_len >> 10, text1, + HZ/fb_info->fbdefio->delay, text2); + +#ifdef CONFIG_FB_BACKLIGHT + /* Turn on backlight if available */ + if (fb_info->bl_dev) { + fb_info->bl_dev->props.power = FB_BLANK_UNBLANK; + fb_info->bl_dev->ops->update_status(fb_info->bl_dev); + } +#endif + + return 0; + +reg_fail: + if (par->fbtftops.unregister_backlight) + par->fbtftops.unregister_backlight(par); + if (spi) + spi_set_drvdata(spi, NULL); + if (par->pdev) + platform_set_drvdata(par->pdev, NULL); + + return ret; +} +EXPORT_SYMBOL(fbtft_register_framebuffer); + +/** + * fbtft_unregister_framebuffer - releases a tft frame buffer device + * @fb_info: frame buffer info structure + * + * Frees SPI driverdata if needed + * Frees gpios. + * Unregisters frame buffer device. + * + */ +int fbtft_unregister_framebuffer(struct fb_info *fb_info) +{ + struct fbtft_par *par = fb_info->par; + struct spi_device *spi = par->spi; + int ret; + + if (spi) + spi_set_drvdata(spi, NULL); + if (par->pdev) + platform_set_drvdata(par->pdev, NULL); + if (par->fbtftops.unregister_backlight) + par->fbtftops.unregister_backlight(par); + fbtft_sysfs_exit(par); + ret = unregister_framebuffer(fb_info); + return ret; +} +EXPORT_SYMBOL(fbtft_unregister_framebuffer); + +#ifdef CONFIG_OF +/** + * fbtft_init_display_dt() - Device Tree init_display() function + * @par: Driver data + * + * Return: 0 if successful, negative if error + */ +static int fbtft_init_display_dt(struct fbtft_par *par) +{ + struct device_node *node = par->info->device->of_node; + struct property *prop; + const __be32 *p; + u32 val; + int buf[64], i, j; + char msg[128]; + char str[16]; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + if (!node) + return -EINVAL; + + prop = of_find_property(node, "init", NULL); + p = of_prop_next_u32(prop, NULL, &val); + if (!p) + return -EINVAL; + while (p) { + if (val & FBTFT_OF_INIT_CMD) { + val &= 0xFFFF; + i = 0; + while (p && !(val & 0xFFFF0000)) { + if (i > 63) { + dev_err(par->info->device, + "%s: Maximum register values exceeded\n", + __func__); + return -EINVAL; + } + buf[i++] = val; + p = of_prop_next_u32(prop, p, &val); + } + /* make debug message */ + msg[0] = '\0'; + for (j = 0; j < i; j++) { + snprintf(str, 128, " %02X", buf[j]); + strcat(msg, str); + } + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "init: write_register:%s\n", msg); + + par->fbtftops.write_register(par, i, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], buf[15], + buf[16], buf[17], buf[18], buf[19], + buf[20], buf[21], buf[22], buf[23], + buf[24], buf[25], buf[26], buf[27], + buf[28], buf[29], buf[30], buf[31], + buf[32], buf[33], buf[34], buf[35], + buf[36], buf[37], buf[38], buf[39], + buf[40], buf[41], buf[42], buf[43], + buf[44], buf[45], buf[46], buf[47], + buf[48], buf[49], buf[50], buf[51], + buf[52], buf[53], buf[54], buf[55], + buf[56], buf[57], buf[58], buf[59], + buf[60], buf[61], buf[62], buf[63]); + } else if (val & FBTFT_OF_INIT_DELAY) { + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "init: msleep(%u)\n", val & 0xFFFF); + msleep(val & 0xFFFF); + p = of_prop_next_u32(prop, p, &val); + } else { + dev_err(par->info->device, "illegal init value 0x%X\n", + val); + return -EINVAL; + } + } + + return 0; +} +#endif + +/** + * fbtft_init_display() - Generic init_display() function + * @par: Driver data + * + * Uses par->init_sequence to do the initialization + * + * Return: 0 if successful, negative if error + */ +int fbtft_init_display(struct fbtft_par *par) +{ + int buf[64]; + char msg[128]; + char str[16]; + int i = 0; + int j; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* sanity check */ + if (!par->init_sequence) { + dev_err(par->info->device, + "error: init_sequence is not set\n"); + return -EINVAL; + } + + /* make sure stop marker exists */ + for (i = 0; i < FBTFT_MAX_INIT_SEQUENCE; i++) + if (par->init_sequence[i] == -3) + break; + if (i == FBTFT_MAX_INIT_SEQUENCE) { + dev_err(par->info->device, + "missing stop marker at end of init sequence\n"); + return -EINVAL; + } + + par->fbtftops.reset(par); + if (par->gpio.cs != -1) + gpio_set_value(par->gpio.cs, 0); /* Activate chip */ + + i = 0; + while (i < FBTFT_MAX_INIT_SEQUENCE) { + if (par->init_sequence[i] == -3) { + /* done */ + return 0; + } + if (par->init_sequence[i] >= 0) { + dev_err(par->info->device, + "missing delimiter at position %d\n", i); + return -EINVAL; + } + if (par->init_sequence[i+1] < 0) { + dev_err(par->info->device, + "missing value after delimiter %d at position %d\n", + par->init_sequence[i], i); + return -EINVAL; + } + switch (par->init_sequence[i]) { + case -1: + i++; + /* make debug message */ + strcpy(msg, ""); + j = i + 1; + while (par->init_sequence[j] >= 0) { + sprintf(str, "0x%02X ", par->init_sequence[j]); + strcat(msg, str); + j++; + } + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "init: write(0x%02X) %s\n", + par->init_sequence[i], msg); + + /* Write */ + j = 0; + while (par->init_sequence[i] >= 0) { + if (j > 63) { + dev_err(par->info->device, + "%s: Maximum register values exceeded\n", + __func__); + return -EINVAL; + } + buf[j++] = par->init_sequence[i++]; + } + par->fbtftops.write_register(par, j, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], buf[15], + buf[16], buf[17], buf[18], buf[19], + buf[20], buf[21], buf[22], buf[23], + buf[24], buf[25], buf[26], buf[27], + buf[28], buf[29], buf[30], buf[31], + buf[32], buf[33], buf[34], buf[35], + buf[36], buf[37], buf[38], buf[39], + buf[40], buf[41], buf[42], buf[43], + buf[44], buf[45], buf[46], buf[47], + buf[48], buf[49], buf[50], buf[51], + buf[52], buf[53], buf[54], buf[55], + buf[56], buf[57], buf[58], buf[59], + buf[60], buf[61], buf[62], buf[63]); + break; + case -2: + i++; + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "init: mdelay(%d)\n", par->init_sequence[i]); + mdelay(par->init_sequence[i++]); + break; + default: + dev_err(par->info->device, + "unknown delimiter %d at position %d\n", + par->init_sequence[i], i); + return -EINVAL; + } + } + + dev_err(par->info->device, + "%s: something is wrong. Shouldn't get here.\n", __func__); + return -EINVAL; +} +EXPORT_SYMBOL(fbtft_init_display); + +/** + * fbtft_verify_gpios() - Generic verify_gpios() function + * @par: Driver data + * + * Uses @spi, @pdev and @buswidth to determine which GPIOs is needed + * + * Return: 0 if successful, negative if error + */ +int fbtft_verify_gpios(struct fbtft_par *par) +{ + struct fbtft_platform_data *pdata; + int i; + + fbtft_par_dbg(DEBUG_VERIFY_GPIOS, par, "%s()\n", __func__); + + pdata = par->info->device->platform_data; + if (pdata->display.buswidth != 9 && par->startbyte == 0 && \ + par->gpio.dc < 0) { + dev_err(par->info->device, + "Missing info about 'dc' gpio. Aborting.\n"); + return -EINVAL; + } + + if (!par->pdev) + return 0; + + if (par->gpio.wr < 0) { + dev_err(par->info->device, "Missing 'wr' gpio. Aborting.\n"); + return -EINVAL; + } + for (i = 0; i < pdata->display.buswidth; i++) { + if (par->gpio.db[i] < 0) { + dev_err(par->info->device, + "Missing 'db%02d' gpio. Aborting.\n", i); + return -EINVAL; + } + } + + return 0; +} + +#ifdef CONFIG_OF +/* returns 0 if the property is not present */ +static u32 fbtft_of_value(struct device_node *node, const char *propname) +{ + int ret; + u32 val = 0; + + ret = of_property_read_u32(node, propname, &val); + if (ret == 0) + pr_info("%s: %s = %u\n", __func__, propname, val); + + return val; +} + +static struct fbtft_platform_data *fbtft_probe_dt(struct device *dev) +{ + struct device_node *node = dev->of_node; + struct fbtft_platform_data *pdata; + + if (!node) { + dev_err(dev, "Missing platform data or DT\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->display.width = fbtft_of_value(node, "width"); + pdata->display.height = fbtft_of_value(node, "height"); + pdata->display.regwidth = fbtft_of_value(node, "regwidth"); + pdata->display.buswidth = fbtft_of_value(node, "buswidth"); + pdata->display.backlight = fbtft_of_value(node, "backlight"); + pdata->display.bpp = fbtft_of_value(node, "bpp"); + pdata->display.debug = fbtft_of_value(node, "debug"); + pdata->rotate = fbtft_of_value(node, "rotate"); + pdata->bgr = of_property_read_bool(node, "bgr"); + pdata->fps = fbtft_of_value(node, "fps"); + pdata->txbuflen = fbtft_of_value(node, "txbuflen"); + pdata->startbyte = fbtft_of_value(node, "startbyte"); + of_property_read_string(node, "gamma", (const char **)&pdata->gamma); + + if (of_find_property(node, "led-gpios", NULL)) + pdata->display.backlight = 1; + if (of_find_property(node, "init", NULL)) + pdata->display.fbtftops.init_display = fbtft_init_display_dt; + pdata->display.fbtftops.request_gpios = fbtft_request_gpios_dt; + + return pdata; +} +#else +static struct fbtft_platform_data *fbtft_probe_dt(struct device *dev) +{ + dev_err(dev, "Missing platform data\n"); + return ERR_PTR(-EINVAL); +} +#endif + +/** + * fbtft_probe_common() - Generic device probe() helper function + * @display: Display properties + * @sdev: SPI device + * @pdev: Platform device + * + * Allocates, initializes and registers a framebuffer + * + * Either @sdev or @pdev should be NULL + * + * Return: 0 if successful, negative if error + */ +int fbtft_probe_common(struct fbtft_display *display, + struct spi_device *sdev, struct platform_device *pdev) +{ + struct device *dev; + struct fb_info *info; + struct fbtft_par *par; + struct fbtft_platform_data *pdata; + int ret; + + if (sdev) + dev = &sdev->dev; + else + dev = &pdev->dev; + + if (unlikely(display->debug & DEBUG_DRIVER_INIT_FUNCTIONS)) + dev_info(dev, "%s()\n", __func__); + + pdata = dev->platform_data; + if (!pdata) { + pdata = fbtft_probe_dt(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + dev->platform_data = pdata; + } + + info = fbtft_framebuffer_alloc(display, dev); + if (!info) + return -ENOMEM; + + par = info->par; + par->spi = sdev; + par->pdev = pdev; + + if (display->buswidth == 0) { + dev_err(dev, "buswidth is not set\n"); + return -EINVAL; + } + + /* write register functions */ + if (display->regwidth == 8 && display->buswidth == 8) { + par->fbtftops.write_register = fbtft_write_reg8_bus8; + } else + if (display->regwidth == 8 && display->buswidth == 9 && par->spi) { + par->fbtftops.write_register = fbtft_write_reg8_bus9; + } else if (display->regwidth == 16 && display->buswidth == 8) { + par->fbtftops.write_register = fbtft_write_reg16_bus8; + } else if (display->regwidth == 16 && display->buswidth == 16) { + par->fbtftops.write_register = fbtft_write_reg16_bus16; + } else { + dev_warn(dev, + "no default functions for regwidth=%d and buswidth=%d\n", + display->regwidth, display->buswidth); + } + + /* write_vmem() functions */ + if (display->buswidth == 8) + par->fbtftops.write_vmem = fbtft_write_vmem16_bus8; + else if (display->buswidth == 9) + par->fbtftops.write_vmem = fbtft_write_vmem16_bus9; + else if (display->buswidth == 16) + par->fbtftops.write_vmem = fbtft_write_vmem16_bus16; + + /* GPIO write() functions */ + if (par->pdev) { + if (display->buswidth == 8) + par->fbtftops.write = fbtft_write_gpio8_wr; + else if (display->buswidth == 16) + par->fbtftops.write = fbtft_write_gpio16_wr; + } + + /* 9-bit SPI setup */ + if (par->spi && display->buswidth == 9) { + par->spi->bits_per_word = 9; + ret = par->spi->master->setup(par->spi); + if (ret) { + dev_warn(&par->spi->dev, + "9-bit SPI not available, emulating using 8-bit.\n"); + par->spi->bits_per_word = 8; + ret = par->spi->master->setup(par->spi); + if (ret) + goto out_release; + /* allocate buffer with room for dc bits */ + par->extra = devm_kzalloc(par->info->device, + par->txbuf.len + (par->txbuf.len / 8) + 8, + GFP_KERNEL); + if (!par->extra) { + ret = -ENOMEM; + goto out_release; + } + par->fbtftops.write = fbtft_write_spi_emulate_9; + } + } + + if (!par->fbtftops.verify_gpios) + par->fbtftops.verify_gpios = fbtft_verify_gpios; + + /* make sure we still use the driver provided functions */ + fbtft_merge_fbtftops(&par->fbtftops, &display->fbtftops); + + /* use init_sequence if provided */ + if (par->init_sequence) + par->fbtftops.init_display = fbtft_init_display; + + /* use platform_data provided functions above all */ + fbtft_merge_fbtftops(&par->fbtftops, &pdata->display.fbtftops); + + ret = fbtft_register_framebuffer(info); + if (ret < 0) + goto out_release; + + return 0; + +out_release: + fbtft_framebuffer_release(info); + + return ret; +} +EXPORT_SYMBOL(fbtft_probe_common); + +/** + * fbtft_remove_common() - Generic device remove() helper function + * @dev: Device + * @info: Framebuffer + * + * Unregisters and releases the framebuffer + * + * Return: 0 if successful, negative if error + */ +int fbtft_remove_common(struct device *dev, struct fb_info *info) +{ + struct fbtft_par *par; + + if (!info) + return -EINVAL; + par = info->par; + if (par) + fbtft_par_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, par, + "%s()\n", __func__); + fbtft_unregister_framebuffer(info); + fbtft_framebuffer_release(info); + + return 0; +} +EXPORT_SYMBOL(fbtft_remove_common); + +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/fbtft/fbtft-io.c b/drivers/staging/fbtft/fbtft-io.c new file mode 100644 index 0000000000000000000000000000000000000000..dfa2c468454a2ba7cf6736259f3245a94674753b --- /dev/null +++ b/drivers/staging/fbtft/fbtft-io.c @@ -0,0 +1,409 @@ +#include +#include +#include +#include +#ifdef CONFIG_ARCH_BCM2708 +#include +#endif +#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=%d): ", __func__, len); + + if (!par->spi) { + dev_err(par->info->device, + "%s: par->spi is unexpectedly NULL\n", __func__); + return -1; + } + + spi_message_init(&m); + if (par->txbuf.dma && buf == par->txbuf.buf) { + t.tx_dma = par->txbuf.dma; + m.is_dma_mapped = 1; + } + 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=%d): ", __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, + "%s: error: len=%d must be divisible by 8\n", + __func__, 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); + *(u64 *)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, + "%s: len=%d can't be larger than 32 when using 'startbyte'\n", + __func__, 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=%d) 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=%d) buf <= ", __func__, len); + + return ret; +} +EXPORT_SYMBOL(fbtft_read_spi); + + +#ifdef CONFIG_ARCH_BCM2708 + +/* + * Raspberry Pi + * - writing directly to the registers is 40-50% faster than + * optimized use of gpiolib + */ + +#define GPIOSET(no, ishigh) \ +do { \ + if (ishigh) \ + set |= (1 << (no)); \ + else \ + reset |= (1 << (no)); \ +} while (0) + +int fbtft_write_gpio8_wr(struct fbtft_par *par, void *buf, size_t len) +{ + unsigned int set = 0; + unsigned int reset = 0; + u8 data; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len--) { + data = *(u8 *) buf; + buf++; + + /* Set data */ + GPIOSET(par->gpio.db[0], (data&0x01)); + GPIOSET(par->gpio.db[1], (data&0x02)); + GPIOSET(par->gpio.db[2], (data&0x04)); + GPIOSET(par->gpio.db[3], (data&0x08)); + GPIOSET(par->gpio.db[4], (data&0x10)); + GPIOSET(par->gpio.db[5], (data&0x20)); + GPIOSET(par->gpio.db[6], (data&0x40)); + GPIOSET(par->gpio.db[7], (data&0x80)); + writel(set, __io_address(GPIO_BASE+0x1C)); + writel(reset, __io_address(GPIO_BASE+0x28)); + + /* Pulse /WR low */ + writel((1<gpio.wr), __io_address(GPIO_BASE+0x28)); + writel(0, __io_address(GPIO_BASE+0x28)); /* used as a delay */ + writel((1<gpio.wr), __io_address(GPIO_BASE+0x1C)); + + set = 0; + reset = 0; + } + + return 0; +} +EXPORT_SYMBOL(fbtft_write_gpio8_wr); + +int fbtft_write_gpio16_wr(struct fbtft_par *par, void *buf, size_t len) +{ + unsigned int set = 0; + unsigned int reset = 0; + u16 data; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len) { + len -= 2; + data = *(u16 *) buf; + buf += 2; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Set data */ + GPIOSET(par->gpio.db[0], (data&0x0001)); + GPIOSET(par->gpio.db[1], (data&0x0002)); + GPIOSET(par->gpio.db[2], (data&0x0004)); + GPIOSET(par->gpio.db[3], (data&0x0008)); + GPIOSET(par->gpio.db[4], (data&0x0010)); + GPIOSET(par->gpio.db[5], (data&0x0020)); + GPIOSET(par->gpio.db[6], (data&0x0040)); + GPIOSET(par->gpio.db[7], (data&0x0080)); + + GPIOSET(par->gpio.db[8], (data&0x0100)); + GPIOSET(par->gpio.db[9], (data&0x0200)); + GPIOSET(par->gpio.db[10], (data&0x0400)); + GPIOSET(par->gpio.db[11], (data&0x0800)); + GPIOSET(par->gpio.db[12], (data&0x1000)); + GPIOSET(par->gpio.db[13], (data&0x2000)); + GPIOSET(par->gpio.db[14], (data&0x4000)); + GPIOSET(par->gpio.db[15], (data&0x8000)); + + writel(set, __io_address(GPIO_BASE+0x1C)); + writel(reset, __io_address(GPIO_BASE+0x28)); + + /* Pullup /WR */ + gpio_set_value(par->gpio.wr, 1); + + set = 0; + reset = 0; + } + + return 0; +} +EXPORT_SYMBOL(fbtft_write_gpio16_wr); + +int fbtft_write_gpio16_wr_latched(struct fbtft_par *par, void *buf, size_t len) +{ + unsigned int set = 0; + unsigned int reset = 0; + u16 data; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len) { + len -= 2; + data = *(u16 *) buf; + buf += 2; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Low byte */ + GPIOSET(par->gpio.db[0], (data&0x0001)); + GPIOSET(par->gpio.db[1], (data&0x0002)); + GPIOSET(par->gpio.db[2], (data&0x0004)); + GPIOSET(par->gpio.db[3], (data&0x0008)); + GPIOSET(par->gpio.db[4], (data&0x0010)); + GPIOSET(par->gpio.db[5], (data&0x0020)); + GPIOSET(par->gpio.db[6], (data&0x0040)); + GPIOSET(par->gpio.db[7], (data&0x0080)); + writel(set, __io_address(GPIO_BASE+0x1C)); + writel(reset, __io_address(GPIO_BASE+0x28)); + + /* Pulse 'latch' high */ + gpio_set_value(par->gpio.latch, 1); + gpio_set_value(par->gpio.latch, 0); + + /* High byte */ + GPIOSET(par->gpio.db[0], (data&0x0100)); + GPIOSET(par->gpio.db[1], (data&0x0200)); + GPIOSET(par->gpio.db[2], (data&0x0400)); + GPIOSET(par->gpio.db[3], (data&0x0800)); + GPIOSET(par->gpio.db[4], (data&0x1000)); + GPIOSET(par->gpio.db[5], (data&0x2000)); + GPIOSET(par->gpio.db[6], (data&0x4000)); + GPIOSET(par->gpio.db[7], (data&0x8000)); + writel(set, __io_address(GPIO_BASE+0x1C)); + writel(reset, __io_address(GPIO_BASE+0x28)); + + /* Pullup /WR */ + gpio_set_value(par->gpio.wr, 1); + + set = 0; + reset = 0; + } + + return 0; +} +EXPORT_SYMBOL(fbtft_write_gpio16_wr_latched); + +#undef GPIOSET + +#else + +/* + * 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=%d): ", __func__, len); + + while (len--) { + data = *(u8 *) buf; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Set data */ +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + if (data == prev_data) { + gpio_set_value(par->gpio.wr, 0); /* used as delay */ + } else { + for (i = 0; i < 8; i++) { + if ((data & 1) != (prev_data & 1)) + gpio_set_value(par->gpio.db[i], + (data & 1)); + data >>= 1; + prev_data >>= 1; + } + } +#else + for (i = 0; i < 8; i++) { + gpio_set_value(par->gpio.db[i], (data & 1)); + data >>= 1; + } +#endif + + /* Pullup /WR */ + gpio_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=%d): ", __func__, len); + + while (len) { + data = *(u16 *) buf; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Set data */ +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + if (data == prev_data) { + gpio_set_value(par->gpio.wr, 0); /* used as delay */ + } else { + for (i = 0; i < 16; i++) { + if ((data & 1) != (prev_data & 1)) + gpio_set_value(par->gpio.db[i], + (data & 1)); + data >>= 1; + prev_data >>= 1; + } + } +#else + for (i = 0; i < 16; i++) { + gpio_set_value(par->gpio.db[i], (data & 1)); + data >>= 1; + } +#endif + + /* Pullup /WR */ + gpio_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); + +#endif /* CONFIG_ARCH_BCM2708 */ diff --git a/drivers/staging/fbtft/fbtft-sysfs.c b/drivers/staging/fbtft/fbtft-sysfs.c new file mode 100644 index 0000000000000000000000000000000000000000..45f8de3d11ad45d0b9e49364550e957f23f9d1e2 --- /dev/null +++ b/drivers/staging/fbtft/fbtft-sysfs.c @@ -0,0 +1,222 @@ +#include "fbtft.h" + + +static int get_next_ulong(char **str_p, unsigned long *val, char *sep, int base) +{ + char *p_val; + int ret; + + if (!str_p || !(*str_p)) + return -EINVAL; + + p_val = strsep(str_p, sep); + + if (!p_val) + return -EINVAL; + + ret = kstrtoul(p_val, base, val); + if (ret) + return -EINVAL; + + return 0; +} + +int fbtft_gamma_parse_str(struct fbtft_par *par, unsigned long *curves, + const char *str, int size) +{ + char *str_p, *curve_p = NULL; + char *tmp; + unsigned long val = 0; + int ret = 0; + int curve_counter, value_counter; + + fbtft_par_dbg(DEBUG_SYSFS, par, "%s() str=\n", __func__); + + if (!str || !curves) + return -EINVAL; + + fbtft_par_dbg(DEBUG_SYSFS, par, "%s\n", str); + + tmp = kmalloc(size+1, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + memcpy(tmp, str, size+1); + + /* replace optional separators */ + str_p = tmp; + while (*str_p) { + if (*str_p == ',') + *str_p = ' '; + if (*str_p == ';') + *str_p = '\n'; + str_p++; + } + + str_p = strim(tmp); + + curve_counter = 0; + while (str_p) { + if (curve_counter == par->gamma.num_curves) { + dev_err(par->info->device, "Gamma: Too many curves\n"); + ret = -EINVAL; + goto out; + } + curve_p = strsep(&str_p, "\n"); + value_counter = 0; + while (curve_p) { + if (value_counter == par->gamma.num_values) { + dev_err(par->info->device, + "Gamma: Too many values\n"); + ret = -EINVAL; + goto out; + } + ret = get_next_ulong(&curve_p, &val, " ", 16); + if (ret) + goto out; + curves[curve_counter * par->gamma.num_values + value_counter] = val; + value_counter++; + } + if (value_counter != par->gamma.num_values) { + dev_err(par->info->device, "Gamma: Too few values\n"); + ret = -EINVAL; + goto out; + } + curve_counter++; + } + if (curve_counter != par->gamma.num_curves) { + dev_err(par->info->device, "Gamma: Too few curves\n"); + ret = -EINVAL; + goto out; + } + +out: + kfree(tmp); + return ret; +} + +static ssize_t +sprintf_gamma(struct fbtft_par *par, unsigned long *curves, char *buf) +{ + ssize_t len = 0; + unsigned int i, j; + + mutex_lock(&par->gamma.lock); + for (i = 0; i < par->gamma.num_curves; i++) { + for (j = 0; j < par->gamma.num_values; j++) + len += scnprintf(&buf[len], PAGE_SIZE, + "%04lx ", curves[i*par->gamma.num_values + j]); + buf[len-1] = '\n'; + } + mutex_unlock(&par->gamma.lock); + + return len; +} + +static ssize_t store_gamma_curve(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fbtft_par *par = fb_info->par; + unsigned long tmp_curves[FBTFT_GAMMA_MAX_VALUES_TOTAL]; + int ret; + + ret = fbtft_gamma_parse_str(par, tmp_curves, buf, count); + if (ret) + return ret; + + ret = par->fbtftops.set_gamma(par, tmp_curves); + if (ret) + return ret; + + mutex_lock(&par->gamma.lock); + memcpy(par->gamma.curves, tmp_curves, + par->gamma.num_curves * par->gamma.num_values * sizeof(tmp_curves[0])); + mutex_unlock(&par->gamma.lock); + + return count; +} + +static ssize_t show_gamma_curve(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fbtft_par *par = fb_info->par; + + return sprintf_gamma(par, par->gamma.curves, buf); +} + +static struct device_attribute gamma_device_attrs[] = { + __ATTR(gamma, 0660, show_gamma_curve, store_gamma_curve), +}; + + +void fbtft_expand_debug_value(unsigned long *debug) +{ + switch (*debug & 0b111) { + case 1: + *debug |= DEBUG_LEVEL_1; + break; + case 2: + *debug |= DEBUG_LEVEL_2; + break; + case 3: + *debug |= DEBUG_LEVEL_3; + break; + case 4: + *debug |= DEBUG_LEVEL_4; + break; + case 5: + *debug |= DEBUG_LEVEL_5; + break; + case 6: + *debug |= DEBUG_LEVEL_6; + break; + case 7: + *debug = 0xFFFFFFFF; + break; + } +} + +static ssize_t store_debug(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fbtft_par *par = fb_info->par; + int ret; + + ret = kstrtoul(buf, 10, &par->debug); + if (ret) + return ret; + fbtft_expand_debug_value(&par->debug); + + return count; +} + +static ssize_t show_debug(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fbtft_par *par = fb_info->par; + + return snprintf(buf, PAGE_SIZE, "%lu\n", par->debug); +} + +static struct device_attribute debug_device_attr = \ + __ATTR(debug, 0660, show_debug, store_debug); + + +void fbtft_sysfs_init(struct fbtft_par *par) +{ + device_create_file(par->info->dev, &debug_device_attr); + if (par->gamma.curves && par->fbtftops.set_gamma) + device_create_file(par->info->dev, &gamma_device_attrs[0]); +} + +void fbtft_sysfs_exit(struct fbtft_par *par) +{ + device_remove_file(par->info->dev, &debug_device_attr); + if (par->gamma.curves && par->fbtftops.set_gamma) + device_remove_file(par->info->dev, &gamma_device_attrs[0]); +} diff --git a/drivers/staging/fbtft/fbtft.h b/drivers/staging/fbtft/fbtft.h new file mode 100644 index 0000000000000000000000000000000000000000..0dbf3f95fe786ce50a9206cd754c4e78b58c45ee --- /dev/null +++ b/drivers/staging/fbtft/fbtft.h @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2013 Noralf Tronnes + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_FBTFT_H +#define __LINUX_FBTFT_H + +#include +#include +#include +#include + + +#define FBTFT_NOP 0x00 +#define FBTFT_SWRESET 0x01 +#define FBTFT_RDDID 0x04 +#define FBTFT_RDDST 0x09 +#define FBTFT_CASET 0x2A +#define FBTFT_RASET 0x2B +#define FBTFT_RAMWR 0x2C + +#define FBTFT_ONBOARD_BACKLIGHT 2 + +#define FBTFT_GPIO_NO_MATCH 0xFFFF +#define FBTFT_GPIO_NAME_SIZE 32 +#define FBTFT_MAX_INIT_SEQUENCE 512 +#define FBTFT_GAMMA_MAX_VALUES_TOTAL 128 + +#define FBTFT_OF_INIT_CMD BIT(24) +#define FBTFT_OF_INIT_DELAY BIT(25) + +/** + * struct fbtft_gpio - Structure that holds one pinname to gpio mapping + * @name: pinname (reset, dc, etc.) + * @gpio: GPIO number + * + */ +struct fbtft_gpio { + char name[FBTFT_GPIO_NAME_SIZE]; + unsigned gpio; +}; + +struct fbtft_par; + +/** + * struct fbtft_ops - FBTFT operations structure + * @write: Writes to interface bus + * @read: Reads from interface bus + * @write_vmem: Writes video memory to display + * @write_reg: Writes to controller register + * @set_addr_win: Set the GRAM update window + * @reset: Reset the LCD controller + * @mkdirty: Marks display lines for update + * @update_display: Updates the display + * @init_display: Initializes the display + * @blank: Blank the display (optional) + * @request_gpios_match: Do pinname to gpio matching + * @request_gpios: Request gpios from the kernel + * @free_gpios: Free previously requested gpios + * @verify_gpios: Verify that necessary gpios is present (optional) + * @register_backlight: Used to register backlight device (optional) + * @unregister_backlight: Unregister backlight device (optional) + * @set_var: Configure LCD with values from variables like @rotate and @bgr + * (optional) + * @set_gamma: Set Gamma curve (optional) + * + * Most of these operations have default functions assigned to them in + * fbtft_framebuffer_alloc() + */ +struct fbtft_ops { + int (*write)(struct fbtft_par *par, void *buf, size_t len); + int (*read)(struct fbtft_par *par, void *buf, size_t len); + int (*write_vmem)(struct fbtft_par *par, size_t offset, size_t len); + void (*write_register)(struct fbtft_par *par, int len, ...); + + void (*set_addr_win)(struct fbtft_par *par, + int xs, int ys, int xe, int ye); + void (*reset)(struct fbtft_par *par); + void (*mkdirty)(struct fb_info *info, int from, int to); + void (*update_display)(struct fbtft_par *par, + unsigned start_line, unsigned end_line); + int (*init_display)(struct fbtft_par *par); + int (*blank)(struct fbtft_par *par, bool on); + + unsigned long (*request_gpios_match)(struct fbtft_par *par, + const struct fbtft_gpio *gpio); + int (*request_gpios)(struct fbtft_par *par); + int (*verify_gpios)(struct fbtft_par *par); + + void (*register_backlight)(struct fbtft_par *par); + void (*unregister_backlight)(struct fbtft_par *par); + + int (*set_var)(struct fbtft_par *par); + int (*set_gamma)(struct fbtft_par *par, unsigned long *curves); +}; + +/** + * struct fbtft_display - Describes the display properties + * @width: Width of display in pixels + * @height: Height of display in pixels + * @regwidth: LCD Controller Register width in bits + * @buswidth: Display interface bus width in bits + * @backlight: Backlight type. + * @fbtftops: FBTFT operations provided by driver or device (platform_data) + * @bpp: Bits per pixel + * @fps: Frames per second + * @txbuflen: Size of transmit buffer + * @init_sequence: Pointer to LCD initialization array + * @gamma: String representation of Gamma curve(s) + * @gamma_num: Number of Gamma curves + * @gamma_len: Number of values per Gamma curve + * @debug: Initial debug value + * + * This structure is not stored by FBTFT except for init_sequence. + */ +struct fbtft_display { + unsigned width; + unsigned height; + unsigned regwidth; + unsigned buswidth; + unsigned backlight; + struct fbtft_ops fbtftops; + unsigned bpp; + unsigned fps; + int txbuflen; + int *init_sequence; + char *gamma; + int gamma_num; + int gamma_len; + unsigned long debug; +}; + +/** + * struct fbtft_platform_data - Passes display specific data to the driver + * @display: Display properties + * @gpios: Pointer to an array of piname to gpio mappings + * @rotate: Display rotation angle + * @bgr: LCD Controller BGR bit + * @fps: Frames per second (this will go away, use @fps in @fbtft_display) + * @txbuflen: Size of transmit buffer + * @startbyte: When set, enables use of Startbyte in transfers + * @gamma: String representation of Gamma curve(s) + * @extra: A way to pass extra info + */ +struct fbtft_platform_data { + struct fbtft_display display; + const struct fbtft_gpio *gpios; + unsigned rotate; + bool bgr; + unsigned fps; + int txbuflen; + u8 startbyte; + char *gamma; + void *extra; +}; + +/** + * struct fbtft_par - Main FBTFT data structure + * + * This structure holds all relevant data to operate the display + * + * See sourcefile for documentation since nested structs is not + * supported by kernel-doc. + * + */ +/* @spi: Set if it is a SPI device + * @pdev: Set if it is a platform device + * @info: Pointer to framebuffer fb_info structure + * @pdata: Pointer to platform data + * @ssbuf: Not used + * @pseudo_palette: Used by fb_set_colreg() + * @txbuf.buf: Transmit buffer + * @txbuf.len: Transmit buffer length + * @buf: Small buffer used when writing init data over SPI + * @startbyte: Used by some controllers when in SPI mode. + * Format: 6 bit Device id + RS bit + RW bit + * @fbtftops: FBTFT operations provided by driver or device (platform_data) + * @dirty_lock: Protects dirty_lines_start and dirty_lines_end + * @dirty_lines_start: Where to begin updating display + * @dirty_lines_end: Where to end updating display + * @gpio.reset: GPIO used to reset display + * @gpio.dc: Data/Command signal, also known as RS + * @gpio.rd: Read latching signal + * @gpio.wr: Write latching signal + * @gpio.latch: Bus latch signal, eg. 16->8 bit bus latch + * @gpio.cs: LCD Chip Select with parallel interface bus + * @gpio.db[16]: Parallel databus + * @gpio.led[16]: Led control signals + * @gpio.aux[16]: Auxillary signals, not used by core + * @init_sequence: Pointer to LCD initialization array + * @gamma.lock: Mutex for Gamma curve locking + * @gamma.curves: Pointer to Gamma curve array + * @gamma.num_values: Number of values per Gamma curve + * @gamma.num_curves: Number of Gamma curves + * @debug: Pointer to debug value + * @current_debug: + * @first_update_done: Used to only time the first display update + * @update_time: Used to calculate 'fps' in debug output + * @bgr: BGR mode/\n + * @extra: Extra info needed by driver + */ +struct fbtft_par { + struct spi_device *spi; + struct platform_device *pdev; + struct fb_info *info; + struct fbtft_platform_data *pdata; + u16 *ssbuf; + u32 pseudo_palette[16]; + struct { + void *buf; + dma_addr_t dma; + size_t len; + } txbuf; + u8 *buf; + u8 startbyte; + struct fbtft_ops fbtftops; + spinlock_t dirty_lock; + unsigned dirty_lines_start; + unsigned dirty_lines_end; + struct { + int reset; + int dc; + int rd; + int wr; + int latch; + int cs; + int db[16]; + int led[16]; + int aux[16]; + } gpio; + int *init_sequence; + struct { + struct mutex lock; + unsigned long *curves; + int num_values; + int num_curves; + } gamma; + unsigned long debug; + bool first_update_done; + struct timespec update_time; + bool bgr; + void *extra; +}; + +#define NUMARGS(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int)) + +#define write_reg(par, ...) \ +do { \ + par->fbtftops.write_register(par, NUMARGS(__VA_ARGS__), __VA_ARGS__); \ +} while (0) + +/* fbtft-core.c */ +extern void fbtft_dbg_hex(const struct device *dev, + int groupsize, void *buf, size_t len, const char *fmt, ...); +extern struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display, + struct device *dev); +extern void fbtft_framebuffer_release(struct fb_info *info); +extern int fbtft_register_framebuffer(struct fb_info *fb_info); +extern int fbtft_unregister_framebuffer(struct fb_info *fb_info); +extern void fbtft_register_backlight(struct fbtft_par *par); +extern void fbtft_unregister_backlight(struct fbtft_par *par); +extern int fbtft_init_display(struct fbtft_par *par); +extern int fbtft_probe_common(struct fbtft_display *display, + struct spi_device *sdev, struct platform_device *pdev); +extern int fbtft_remove_common(struct device *dev, struct fb_info *info); + +/* fbtft-io.c */ +extern int fbtft_write_spi(struct fbtft_par *par, void *buf, size_t len); +extern int fbtft_write_spi_emulate_9(struct fbtft_par *par, + void *buf, size_t len); +extern int fbtft_read_spi(struct fbtft_par *par, void *buf, size_t len); +extern int fbtft_write_gpio8_wr(struct fbtft_par *par, void *buf, size_t len); +extern int fbtft_write_gpio16_wr(struct fbtft_par *par, void *buf, size_t len); +extern int fbtft_write_gpio16_wr_latched(struct fbtft_par *par, + void *buf, size_t len); + +/* fbtft-bus.c */ +extern int fbtft_write_vmem8_bus8(struct fbtft_par *par, size_t offset, size_t len); +extern int fbtft_write_vmem16_bus16(struct fbtft_par *par, size_t offset, size_t len); +extern int fbtft_write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len); +extern int fbtft_write_vmem16_bus9(struct fbtft_par *par, size_t offset, size_t len); +extern void fbtft_write_reg8_bus8(struct fbtft_par *par, int len, ...); +extern void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...); +extern void fbtft_write_reg16_bus8(struct fbtft_par *par, int len, ...); +extern void fbtft_write_reg16_bus16(struct fbtft_par *par, int len, ...); + + +#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display) \ + \ +static int fbtft_driver_probe_spi(struct spi_device *spi) \ +{ \ + return fbtft_probe_common(_display, spi, NULL); \ +} \ + \ +static int fbtft_driver_remove_spi(struct spi_device *spi) \ +{ \ + struct fb_info *info = spi_get_drvdata(spi); \ + \ + return fbtft_remove_common(&spi->dev, info); \ +} \ + \ +static int fbtft_driver_probe_pdev(struct platform_device *pdev) \ +{ \ + return fbtft_probe_common(_display, NULL, pdev); \ +} \ + \ +static int fbtft_driver_remove_pdev(struct platform_device *pdev) \ +{ \ + struct fb_info *info = platform_get_drvdata(pdev); \ + \ + return fbtft_remove_common(&pdev->dev, info); \ +} \ + \ +static const struct of_device_id dt_ids[] = { \ + { .compatible = _compatible }, \ + {}, \ +}; \ + \ +MODULE_DEVICE_TABLE(of, dt_ids); \ + \ + \ +static struct spi_driver fbtft_driver_spi_driver = { \ + .driver = { \ + .name = _name, \ + .owner = THIS_MODULE, \ + .of_match_table = of_match_ptr(dt_ids), \ + }, \ + .probe = fbtft_driver_probe_spi, \ + .remove = fbtft_driver_remove_spi, \ +}; \ + \ +static struct platform_driver fbtft_driver_platform_driver = { \ + .driver = { \ + .name = _name, \ + .owner = THIS_MODULE, \ + .of_match_table = of_match_ptr(dt_ids), \ + }, \ + .probe = fbtft_driver_probe_pdev, \ + .remove = fbtft_driver_remove_pdev, \ +}; \ + \ +static int __init fbtft_driver_module_init(void) \ +{ \ + int ret; \ + \ + ret = spi_register_driver(&fbtft_driver_spi_driver); \ + if (ret < 0) \ + return ret; \ + return platform_driver_register(&fbtft_driver_platform_driver); \ +} \ + \ +static void __exit fbtft_driver_module_exit(void) \ +{ \ + spi_unregister_driver(&fbtft_driver_spi_driver); \ + platform_driver_unregister(&fbtft_driver_platform_driver); \ +} \ + \ +module_init(fbtft_driver_module_init); \ +module_exit(fbtft_driver_module_exit); + + +/* Debug macros */ + +/* shorthand debug levels */ +#define DEBUG_LEVEL_1 DEBUG_REQUEST_GPIOS +#define DEBUG_LEVEL_2 (DEBUG_LEVEL_1 | DEBUG_DRIVER_INIT_FUNCTIONS | DEBUG_TIME_FIRST_UPDATE) +#define DEBUG_LEVEL_3 (DEBUG_LEVEL_2 | DEBUG_RESET | DEBUG_INIT_DISPLAY | DEBUG_BLANK | DEBUG_REQUEST_GPIOS | DEBUG_FREE_GPIOS | DEBUG_VERIFY_GPIOS | DEBUG_BACKLIGHT | DEBUG_SYSFS) +#define DEBUG_LEVEL_4 (DEBUG_LEVEL_2 | DEBUG_FB_READ | DEBUG_FB_WRITE | DEBUG_FB_FILLRECT | DEBUG_FB_COPYAREA | DEBUG_FB_IMAGEBLIT | DEBUG_FB_BLANK) +#define DEBUG_LEVEL_5 (DEBUG_LEVEL_3 | DEBUG_UPDATE_DISPLAY) +#define DEBUG_LEVEL_6 (DEBUG_LEVEL_4 | DEBUG_LEVEL_5) +#define DEBUG_LEVEL_7 0xFFFFFFFF + +#define DEBUG_DRIVER_INIT_FUNCTIONS (1<<3) +#define DEBUG_TIME_FIRST_UPDATE (1<<4) +#define DEBUG_TIME_EACH_UPDATE (1<<5) +#define DEBUG_DEFERRED_IO (1<<6) +#define DEBUG_FBTFT_INIT_FUNCTIONS (1<<7) + +/* fbops */ +#define DEBUG_FB_READ (1<<8) +#define DEBUG_FB_WRITE (1<<9) +#define DEBUG_FB_FILLRECT (1<<10) +#define DEBUG_FB_COPYAREA (1<<11) +#define DEBUG_FB_IMAGEBLIT (1<<12) +#define DEBUG_FB_SETCOLREG (1<<13) +#define DEBUG_FB_BLANK (1<<14) + +#define DEBUG_SYSFS (1<<16) + +/* fbtftops */ +#define DEBUG_BACKLIGHT (1<<17) +#define DEBUG_READ (1<<18) +#define DEBUG_WRITE (1<<19) +#define DEBUG_WRITE_VMEM (1<<20) +#define DEBUG_WRITE_REGISTER (1<<21) +#define DEBUG_SET_ADDR_WIN (1<<22) +#define DEBUG_RESET (1<<23) +#define DEBUG_MKDIRTY (1<<24) +#define DEBUG_UPDATE_DISPLAY (1<<25) +#define DEBUG_INIT_DISPLAY (1<<26) +#define DEBUG_BLANK (1<<27) +#define DEBUG_REQUEST_GPIOS (1<<28) +#define DEBUG_FREE_GPIOS (1<<29) +#define DEBUG_REQUEST_GPIOS_MATCH (1<<30) +#define DEBUG_VERIFY_GPIOS (1<<31) + + +#define fbtft_init_dbg(dev, format, arg...) \ +do { \ + if (unlikely((dev)->platform_data && \ + (((struct fbtft_platform_data *)(dev)->platform_data)->display.debug & DEBUG_DRIVER_INIT_FUNCTIONS))) \ + dev_info(dev, format, ##arg); \ +} while (0) + +#define fbtft_par_dbg(level, par, format, arg...) \ +do { \ + if (unlikely(par->debug & level)) \ + dev_info(par->info->device, format, ##arg); \ +} while (0) + +#define fbtft_dev_dbg(level, par, dev, format, arg...) \ +do { \ + if (unlikely(par->debug & level)) \ + dev_info(dev, format, ##arg); \ +} while (0) + +#define fbtft_par_dbg_hex(level, par, dev, type, buf, num, format, arg...) \ +do { \ + if (unlikely(par->debug & level)) \ + fbtft_dbg_hex(dev, sizeof(type), buf, num * sizeof(type), format, ##arg); \ +} while (0) + +#endif /* __LINUX_FBTFT_H */