lw35-upgrade/display/kernel-driver/lmg6202ulyt.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);