alistair23-linux/drivers/staging/fbtft/fbtft-core.c
Priit Laes a79cb3901e staging: fbtft: Use standard MIPI DCS command defines for fbtft driver
This patch makes use of the standard MIPI Display Command Set to remove
some of the magic constants found in source code.

Signed-off-by: Priit Laes <plaes@plaes.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-02-07 19:39:27 -08:00

1468 lines
38 KiB
C

/*
* 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.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/backlight.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/dma-mapping.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <video/mipi_display.h>
#include "fbtft.h"
#include "internal.h"
static unsigned long debug;
module_param(debug, ulong, 0);
MODULE_PARM_DESC(debug, "override device debug level");
#ifdef CONFIG_HAS_DMA
static bool dma = true;
module_param(dma, bool, 0);
MODULE_PARM_DESC(dma, "Use DMA buffer");
#endif
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);
static 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;
}
static 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))
return 0;
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
static 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;
}
static int fbtft_backlight_get_brightness(struct backlight_device *bd)
{
return bd->props.brightness;
}
void fbtft_unregister_backlight(struct fbtft_par *par)
{
if (par->info->bl_dev) {
par->info->bl_dev->props.power = FB_BLANK_POWERDOWN;
backlight_update_status(par->info->bl_dev);
backlight_device_unregister(par->info->bl_dev);
par->info->bl_dev = NULL;
}
}
static const struct backlight_ops fbtft_bl_ops = {
.get_brightness = fbtft_backlight_get_brightness,
.update_status = fbtft_backlight_update_status,
};
void fbtft_register_backlight(struct fbtft_par *par)
{
struct backlight_device *bd;
struct backlight_properties bl_props = { 0, };
if (par->gpio.led[0] == -1) {
fbtft_par_dbg(DEBUG_BACKLIGHT, par,
"%s(): led pin not set, exiting.\n", __func__);
return;
}
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, &fbtft_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);
static void fbtft_set_addr_win(struct fbtft_par *par, int xs, int ys, int xe,
int ye)
{
write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
(xs >> 8) & 0xFF, xs & 0xFF, (xe >> 8) & 0xFF, xe & 0xFF);
write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
(ys >> 8) & 0xFF, ys & 0xFF, (ye >> 8) & 0xFF, ye & 0xFF);
write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
}
static 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);
}
static void fbtft_update_display(struct fbtft_par *par, unsigned start_line,
unsigned end_line)
{
size_t offset, len;
ktime_t ts_start, ts_end;
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)) {
ts_start = ktime_get();
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)) {
ts_end = ktime_get();
if (ktime_to_ns(par->update_time))
par->update_time = ts_start;
par->update_time = ts_start;
fps = ktime_us_delta(ts_start, par->update_time);
fps = fps ? 1000000 / fps : 0;
throughput = ktime_us_delta(ts_end, ts_start);
throughput = throughput ? (len * 1000) / throughput : 0;
throughput = throughput * 1000 / 1024;
dev_info(par->info->device,
"Display update: %ld kB/s, fps=%ld\n",
throughput, fps);
par->first_update_done = true;
}
}
static 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);
}
static 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;
dev_dbg(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);
}
static void fbtft_fb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
struct fbtft_par *par = info->par;
dev_dbg(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);
}
static void fbtft_fb_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
struct fbtft_par *par = info->par;
dev_dbg(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);
}
static void fbtft_fb_imageblit(struct fb_info *info,
const struct fb_image *image)
{
struct fbtft_par *par = info->par;
dev_dbg(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);
}
static 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;
dev_dbg(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 */
static unsigned int chan_to_field(unsigned chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
static int fbtft_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp,
struct fb_info *info)
{
unsigned val;
int ret = 1;
dev_dbg(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;
}
static int fbtft_fb_blank(int blank, struct fb_info *info)
{
struct fbtft_par *par = info->par;
int ret = -EINVAL;
dev_dbg(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;
}
static 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 fbtft_platform_data *pdata)
{
struct fb_info *info;
struct fbtft_par *par;
struct fb_ops *fbops = NULL;
struct fb_deferred_io *fbdefio = NULL;
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, "FBTFT_GAMMA_MAX_VALUES_TOTAL=%d is exceeded\n",
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_buffer = 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 = pdata;
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) {
#ifdef CONFIG_HAS_DMA
if (dma) {
dev->coherent_dma_mask = ~0;
txbuf = dmam_alloc_coherent(dev, txbuflen, &par->txbuf.dma, GFP_DMA);
} else
#endif
{
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_buffer);
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, ", %zu 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);
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;
if (spi)
spi_set_drvdata(spi, NULL);
if (par->fbtftops.unregister_backlight)
par->fbtftops.unregister_backlight(par);
fbtft_sysfs_exit(par);
return unregister_framebuffer(fb_info);
}
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;
if (!node)
return -EINVAL;
prop = of_find_property(node, "init", NULL);
p = of_prop_next_u32(prop, NULL, &val);
if (!p)
return -EINVAL;
par->fbtftops.reset(par);
if (par->gpio.cs != -1)
gpio_set_value(par->gpio.cs, 0); /* Activate chip */
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 */
fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
"init: write_register:\n");
for (j = 0; j < i; j++)
fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
"buf[%d] = %02X\n", j, buf[j]);
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;
/* 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
*/
static int fbtft_verify_gpios(struct fbtft_par *par)
{
struct fbtft_platform_data *pdata = par->pdata;
int i;
fbtft_par_dbg(DEBUG_VERIFY_GPIOS, par, "%s()\n", __func__);
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);
}
info = fbtft_framebuffer_alloc(display, dev, pdata);
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) {
if (par->spi->master->bits_per_word_mask & SPI_BPW_MASK(9)) {
par->spi->bits_per_word = 9;
} else {
dev_warn(&par->spi->dev,
"9-bit SPI not available, emulating using 8-bit.\n");
/* 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");