373 lines
8.7 KiB
C
373 lines
8.7 KiB
C
#include <linux/init.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/fb.h>
|
|
|
|
#define BPP 16
|
|
|
|
#define LMGE_GPIO 100
|
|
#define LMGE_OTHER 99
|
|
|
|
#define PALETTE_SIZE 256
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Markus Koch");
|
|
MODULE_DESCRIPTION("lmg6202ulyt display driver");
|
|
MODULE_VERSION("0.1");
|
|
|
|
static int pix_threshold = 0;
|
|
module_param(pix_threshold, int, 0);
|
|
MODULE_PARM_DESC(pix_threshold, "Black / White threshold. 0-255");
|
|
|
|
static struct of_device_id lmg6202ulyt_dt_ids[] = {
|
|
{ .compatible = "hitachi,lmg6202ulyt", },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, lmg6202ulyt_dt_ids);
|
|
|
|
static const struct fb_videomode default_mode = {
|
|
NULL, 30, 480, 128, /* name, refresh, xres, yres */
|
|
39300, /* pixclk */
|
|
0, 0, 0, 0, /* margin l, r, t, b */
|
|
0, 0, /* hsync_len, vsync_len */
|
|
0, FB_VMODE_NONINTERLACED /* vmode, flag */
|
|
};
|
|
|
|
struct lmg6202ulyt_priv {
|
|
struct spi_device *spi;
|
|
struct fb_info info;
|
|
|
|
dma_addr_t fb_phys;
|
|
void __iomem *fb_virt;
|
|
u32 pseudo_palette[PALETTE_SIZE];
|
|
};
|
|
|
|
static int lmg6202ulyt_init_var(struct lmg6202ulyt_priv *priv)
|
|
{
|
|
struct fb_var_screeninfo *var = &priv->info.var;
|
|
|
|
var->accel_flags = FB_ACCEL_NONE;
|
|
var->activate = FB_ACTIVATE_NOW;
|
|
var->xres_virtual = var->xres;
|
|
var->yres_virtual = var->yres;
|
|
|
|
switch (var->bits_per_pixel) {
|
|
case 8:
|
|
var->transp.offset = 0;
|
|
var->transp.length = 0;
|
|
var->red.offset = 0;
|
|
var->red.length = 8;
|
|
var->green.offset = 0;
|
|
var->green.length = 8;
|
|
var->blue.offset = 0;
|
|
var->blue.length = 8;
|
|
break;
|
|
|
|
case 16:
|
|
var->transp.offset = 0;
|
|
var->transp.length = 0;
|
|
var->red.offset = 11;
|
|
var->red.length = 5;
|
|
var->green.offset = 5;
|
|
var->green.length = 6;
|
|
var->blue.offset = 0;
|
|
var->blue.length = 5;
|
|
break;
|
|
|
|
case 24:
|
|
var->transp.offset = 0;
|
|
var->transp.length = 0;
|
|
var->red.offset = 16;
|
|
var->red.length = 8;
|
|
var->green.offset = 8;
|
|
var->green.length = 8;
|
|
var->blue.offset = 0;
|
|
var->blue.length = 8;
|
|
break;
|
|
|
|
case 32:
|
|
var->transp.offset = 24;
|
|
var->transp.length = 8;
|
|
var->red.offset = 16;
|
|
var->red.length = 8;
|
|
var->green.offset = 8;
|
|
var->green.length = 8;
|
|
var->blue.offset = 0;
|
|
var->blue.length = 8;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lmg6202ulyt_init_fix(struct lmg6202ulyt_priv *priv)
|
|
{
|
|
struct fb_var_screeninfo *var = &priv->info.var;
|
|
struct fb_fix_screeninfo *fix = &priv->info.fix;
|
|
|
|
strcpy(fix->id, "LW-35 LCD");
|
|
|
|
fix->line_length = var->xres * var->bits_per_pixel/8;
|
|
fix->smem_len = fix->line_length * var->yres;
|
|
fix->type = FB_TYPE_PACKED_PIXELS;
|
|
|
|
if (var->bits_per_pixel == 8 && !var->grayscale)
|
|
fix->visual = FB_VISUAL_PSEUDOCOLOR;
|
|
else
|
|
fix->visual = FB_VISUAL_TRUECOLOR;
|
|
|
|
printk(KERN_INFO "lmg6202ulyt: Using %d bpp (%s).\n",
|
|
var->bits_per_pixel,
|
|
(var->grayscale ? "grayscale" : "truecolor"));
|
|
|
|
return 0;
|
|
}
|
|
|
|
void lfb_mkdirty(struct fb_info *info, int y, int height)
|
|
{
|
|
//struct lmg6202ulyt_priv *priv = info->par;
|
|
struct fb_deferred_io *fbdefio = info->fbdefio;
|
|
|
|
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
|
|
}
|
|
|
|
static int lfb_setcolreg(unsigned regno, unsigned red, unsigned green,
|
|
unsigned blue, unsigned transp,
|
|
struct fb_info *info)
|
|
{
|
|
//struct lmg6202ulyt_priv *fbdev = (struct lmg6202ulyt_priv *)info->par;
|
|
u32 color;
|
|
|
|
if (regno >= info->cmap.len) {
|
|
dev_err(info->device, "regno >= cmap.len\n");
|
|
return 1;
|
|
}
|
|
|
|
if (info->var.grayscale) {
|
|
/* grayscale = 0.30*R + 0.59*G + 0.11*B */
|
|
red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8;
|
|
}
|
|
|
|
red >>= (16 - info->var.red.length);
|
|
green >>= (16 - info->var.green.length);
|
|
blue >>= (16 - info->var.blue.length);
|
|
transp >>= (16 - info->var.transp.length);
|
|
|
|
if (info->var.bits_per_pixel == 8 && !info->var.grayscale) {
|
|
regno <<= 2;
|
|
color = (red << 16) | (green << 8) | blue;
|
|
//ocfb_writereg(fbdev, OCFB_PALETTE + regno, color);
|
|
} else {
|
|
((u32 *)(info->pseudo_palette))[regno] =
|
|
(red << info->var.red.offset) |
|
|
(green << info->var.green.offset) |
|
|
(blue << info->var.blue.offset) |
|
|
(transp << info->var.transp.offset);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void lfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
|
|
{
|
|
sys_fillrect(info, rect);
|
|
lfb_mkdirty(info, rect->dy, rect->height);
|
|
}
|
|
|
|
void lfb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
|
|
{
|
|
sys_copyarea(info, area);
|
|
lfb_mkdirty(info, area->dy, area->height);
|
|
}
|
|
|
|
void lfb_imageblit(struct fb_info *info, const struct fb_image *image)
|
|
{
|
|
sys_imageblit(info, image);
|
|
lfb_mkdirty(info, image->dy, image->height);
|
|
}
|
|
|
|
static struct fb_ops ocfb_ops = {
|
|
.owner = THIS_MODULE,
|
|
.fb_setcolreg = lfb_setcolreg,
|
|
.fb_fillrect = lfb_fillrect,
|
|
.fb_copyarea = lfb_copyarea,
|
|
.fb_imageblit = lfb_imageblit,
|
|
};
|
|
|
|
#define BLOCKMUL 4
|
|
#define BLOCKSIZE 180 * BLOCKMUL /* 180 = 3 rows */
|
|
#define ROWSPERBLOCK (3 * BLOCKMUL)
|
|
#define BLOCKCNT (128 / ROWSPERBLOCK) + 1
|
|
void lmg6202ulyt_deferred_io(struct fb_info *info, struct list_head *pagelist)
|
|
{
|
|
struct lmg6202ulyt_priv *priv = info->par;
|
|
char buf[BLOCKSIZE + 3] = {0x00,0x00,0x00,0x00,0x00};
|
|
int i;
|
|
int blk;
|
|
char __iomem *pixel;
|
|
char pixval;
|
|
int txsz;
|
|
|
|
spi_write(priv->spi, buf, 5);
|
|
|
|
pixel = info->screen_base;
|
|
for (blk = 0; blk < BLOCKCNT; ++blk) {
|
|
memset(buf, 0, sizeof(buf));
|
|
buf[0] = 1;
|
|
|
|
for (i = 0; i < BLOCKSIZE * 8; i++) {
|
|
#if BPP == 8
|
|
pixval = *(pixel);
|
|
#else
|
|
pixval = *(pixel + 1);
|
|
// TODO: We should have more cases here for higher BPPs
|
|
#endif
|
|
if (pixval > pix_threshold) {
|
|
buf[(i / 8) + 1] |= (1 << (7 - (i % 8)));
|
|
}
|
|
if (pixel >= info->screen_base + info->screen_size - ((info->var.bits_per_pixel) / 8))
|
|
break;
|
|
pixel+= (info->var.bits_per_pixel) / 8;
|
|
}
|
|
|
|
if (blk == BLOCKCNT - 1)
|
|
txsz = sizeof(buf)/sizeof(buf[0]);
|
|
else
|
|
txsz = sizeof(buf)/sizeof(buf[0]);
|
|
|
|
spi_write(priv->spi, buf, txsz);
|
|
}
|
|
}
|
|
|
|
static int lmg6202ulyt_probe(struct spi_device *spi)
|
|
{
|
|
struct lmg6202ulyt_priv *priv = NULL;
|
|
struct fb_deferred_io *fbdefio = NULL;
|
|
int fbsize;
|
|
int ret;
|
|
|
|
printk(KERN_INFO "lmg6202ulyt: probe\n");
|
|
|
|
priv = devm_kzalloc(&spi->dev,
|
|
sizeof(struct lmg6202ulyt_priv),
|
|
GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
spi_set_drvdata(spi, priv);
|
|
|
|
priv->spi = spi;
|
|
|
|
priv->info.fbops = &ocfb_ops;
|
|
priv->info.device = &spi->dev;
|
|
priv->info.par = priv;
|
|
|
|
|
|
printk(KERN_INFO "lmg6202ulyt: vmode\n");
|
|
|
|
if (!fb_find_mode(&priv->info.var, &priv->info, "",
|
|
NULL, 0, &default_mode, BPP)) {
|
|
dev_err(&spi->dev, "No valid video modes found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
printk(KERN_INFO "lmg6202ulyt: Step 1\n");
|
|
lmg6202ulyt_init_var(priv);
|
|
printk(KERN_INFO "lmg6202ulyt: Step 1.5\n");
|
|
lmg6202ulyt_init_fix(priv);
|
|
|
|
printk(KERN_INFO "lmg6202ulyt: Step 2\n");
|
|
/* Allocate framebuffer memory */
|
|
fbsize = priv->info.fix.smem_len;
|
|
dma_set_coherent_mask(&spi->dev, 0xFFFFFFFF);
|
|
priv->fb_virt = dma_alloc_coherent(&spi->dev, PAGE_ALIGN(fbsize),
|
|
&priv->fb_phys, GFP_KERNEL);
|
|
if (!priv->fb_virt) {
|
|
dev_err(&spi->dev,
|
|
"Frame buffer memory allocation failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
printk(KERN_INFO, "lmg6202ulyt: fbdefio init\n");
|
|
fbdefio = devm_kzalloc(&spi->dev, sizeof(struct fb_deferred_io),
|
|
GFP_KERNEL);
|
|
if (!fbdefio) {
|
|
dev_err(&spi->dev,
|
|
"FB defio memory allocation failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->info.fbdefio = fbdefio;
|
|
|
|
fbdefio->delay = HZ/30;
|
|
fbdefio->deferred_io = lmg6202ulyt_deferred_io;
|
|
fb_deferred_io_init(&priv->info);
|
|
|
|
printk(KERN_INFO "lmg6202ulyt: Step 3\n");
|
|
priv->info.fix.smem_start = priv->fb_phys;
|
|
priv->info.screen_base = priv->fb_virt;
|
|
priv->info.screen_size = fbsize;
|
|
priv->info.pseudo_palette = priv->pseudo_palette;
|
|
|
|
// HW specific init goes here
|
|
|
|
/* Allocate color map */
|
|
ret = fb_alloc_cmap(&priv->info.cmap, PALETTE_SIZE, 0);
|
|
if (ret) {
|
|
dev_err(&spi->dev, "Color map allocation failed\n");
|
|
goto err_dma_free;
|
|
}
|
|
printk(KERN_INFO "lmg6202ulyt: Step 4\n");
|
|
|
|
/* Register framebuffer */
|
|
ret = register_framebuffer(&priv->info);
|
|
if (ret) {
|
|
dev_err(&spi->dev, "Framebuffer registration failed\n");
|
|
goto err_dealloc_cmap;
|
|
|
|
}
|
|
printk(KERN_INFO "lmg6202ulyt: Loaded driver\n");
|
|
|
|
priv->info.var.grayscale = 1;
|
|
|
|
lfb_mkdirty(&priv->info, 0, 0);
|
|
|
|
return 0;
|
|
|
|
err_dealloc_cmap:
|
|
fb_dealloc_cmap(&priv->info.cmap);
|
|
|
|
err_dma_free:
|
|
// kzfree(priv->fb_mem);
|
|
dma_free_coherent(&spi->dev, PAGE_ALIGN(fbsize), priv->fb_virt,
|
|
priv->fb_phys);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int lmg6202ulyt_remove(struct spi_device *spi)
|
|
{
|
|
struct lmg6202ulyt_priv *priv = spi_get_drvdata(spi);
|
|
|
|
//unregister_framebuffer(&priv->info);
|
|
fb_dealloc_cmap(&priv->info.cmap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct spi_driver lmg6202ulyt_driver = {
|
|
.probe = lmg6202ulyt_probe,
|
|
.remove = lmg6202ulyt_remove,
|
|
.driver = {
|
|
.name = "lmg6202ulyt",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = lmg6202ulyt_dt_ids,
|
|
},
|
|
};
|
|
module_spi_driver(lmg6202ulyt_driver);
|
|
|