lw35-upgrade/display/kernel-driver/lmg6202ulyt.c

590 lines
14 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/of_gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/fb.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#define BPP 8 // 8
#define PIX_THRESHOLD 0
#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 char *testparam = "param";
module_param(testparam, charp, S_IRUGO);
MODULE_PARM_DESC(testparam, "A test parameter");
static struct of_device_id lmg6202ulyt_dt_ids[] = {
{ .compatible = "hitachi,lmg6202ulyt", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, lmg6202ulyt_dt_ids);
/* IOC Declerations */
//static int device_open(struct inode *, struct file *);
//static int device_release(struct inode *, struct file *);
//static ssize_t device_read(struct file *, char *, size_t, loff_t *);
//static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
//static struct file_operations fops = {
// .read = device_read,
// .write = device_write,
// .open = device_open,
// .release = device_release
//};
/* VIDEO DRIVER Declarations */
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 {
/* SPI dev */
struct spi_device *spi;
/* FB dev */
struct fb_info info;
dma_addr_t fb_phys;
void __iomem *fb_virt;
u32 pseudo_palette[PALETTE_SIZE];
/* IOC */
struct gpio_desc *ioc_irq;
int ioc_irq_number;
struct work_struct ioc_work;
struct gpio_desc *gpio_ioc_cs;
u32 deferred_instructions;
};
/* IOC Code */
#define IOC_DEFERRED_INSTRUCTION_GETKEY (1 << 0)
static irqreturn_t ioc_irq_handler(int irq, void* user_data)
{
struct lmg6202ulyt_priv *priv = user_data;
priv->deferred_instructions |= IOC_DEFERRED_INSTRUCTION_GETKEY;
schedule_work(&priv->ioc_work);
return IRQ_HANDLED;
}
#define SPI_SELECT_IOC(PRIV) {PRIV->spi->chip_select = 1; PRIV->spi->cs_gpio = desc_to_gpio(PRIV->gpio_ioc_cs);}
#define SPI_SELECT_DISPLAY(PRIV) {PRIV->spi->chip_select = 0; PRIV->spi->cs_gpio = -2;}
#define SPI_COMMAND_KEYBOARD_GETKEY 0x20 /* ret: KEY, ... */
/* Bits of the IOC status byte */
#define IOC_STATUS_KEYPRESSES_AVAILABLE (1 << 0)
#define IOC_STATUS_PRINTER_PAUSED (1 << 1)
#define GETKEY_AMOUNT 2
#define SPI_SPEED_READWRITE 10000
#define SPI_SPEED_WRITEONLY 100000
void spi_test_transaction(struct lmg6202ulyt_priv *priv)
{
char tx_buf[256] = "\x01The quick brown fox jumps over the lazy dog.\n"
"abcdefghijklmnopqrstuvwxyz 0123456789\n"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"
"!\"#$%&'()*+,-./:;=?_`|\n\n"
;//{0x01, 0x10, 0x20, 0x30};
char rx_buf[256];
int i;
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = strlen(tx_buf),
.speed_hz = SPI_SPEED_WRITEONLY,
};
tx_buf[0] = 0x01;
// for (i =1; i < 120; ++i) {
// tx_buf[i] = (i % 12) + '0';
// if (tx_buf[i] == '9'+1)
// tx_buf[i] = '\r';
// if (tx_buf[i] == '9'+2)
// tx_buf[i] = '\n';
// }
spi_sync_transfer(priv->spi, &t, 1);
printk(KERN_INFO "test done.\n");
}
/* https://linux-kernel-labs.github.io/master/labs/deferred_work.html */
void ioc_defio(struct work_struct *work)
{
#define ioc_status rx_buf[0]
struct lmg6202ulyt_priv *priv;
int i;
char rx_buf[8] = {0, 0, 0, 0}; // Minimum size: GETKEY_AMOUNT + 2
char tx_buf[8] = {0, 0x10, 0x20, 0x30}; // Minimum size: GETKEY_AMOUNT + 2
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = 0,
.speed_hz = SPI_SPEED_READWRITE,
};
priv = container_of(work, struct lmg6202ulyt_priv, ioc_work);
//printk(KERN_INFO "lmg: workpriv=%p\n", priv);
//SPI_SELECT_DISPLAY(priv);
priv->spi->chip_select = 1;
priv->spi->cs_gpio = desc_to_gpio(priv->gpio_ioc_cs);
// if (priv->deferred_instructions & IOC_DEFERRED_INSTRUCTION_GETKEY) {
// // Get status + n keys
// tx_buf[0] = SPI_COMMAND_KEYBOARD_GETKEY;
// t.len = GETKEY_AMOUNT + 2;
// if (spi_sync_transfer(priv->spi, &t, 1))
// goto SPI_ERR;
// for (i = 2; (i < GETKEY_AMOUNT + 2) && rx_buf[i]; ++i) {
// printk(KERN_INFO "lmg: Report key: %x\n", rx_buf[i]);
// }
// if (i < GETKEY_AMOUNT + 2) {
// printk(KERN_INFO "lmg: getkey finished.\n");
// ioc_status &= ~IOC_STATUS_KEYPRESSES_AVAILABLE;
// } else {
// printk(KERN_INFO "lmg: Want more keys!!\n");
// }
// }
spi_test_transaction(priv);
if (ioc_status) {
// TODO: Handle non-zero IOC status
// schedule_work(&priv->ioc_work);
}
// struct spi_transfer t = {
// .tx_buf = "HELLO\r\n",
// .len = 7,
// .speed_hz = 1000000,
// };
// if (evil) {
// SPI_SELECT_IOC(priv);
// } else {
// SPI_SELECT_DISPLAY(priv);
// }
// evil = !evil;
// printk(KERN_INFO "cs %d/%d", priv->spi->chip_select, priv->spi->cs_gpio);
// spi_sync_transfer(priv->spi, &t, 1);
// printk(KERN_INFO "lmg: SPI transaction done: %d\n", ret);
return;
SPI_ERR:
printk(KERN_ERR "lmg: IOC SPI transaction failed!\n");
}
static int ioc_probe(struct spi_device *spi,
struct lmg6202ulyt_priv *priv)
{
priv->ioc_irq_number = -1;
printk(KERN_INFO "probe priv=%p", priv);
// priv->ioc_work = devm_kzalloc(&spi->dev, sizeof(struct work_struct),
// GFP_KERNEL);
// if (!priv->ioc_work) {
// dev_err(&spi->dev,
// "IOC defio memory allocation failed\n");
// return -ENOMEM;
// }
INIT_WORK(&priv->ioc_work, ioc_defio);
printk(KERN_INFO "lmg: ioc defio init complete");
priv->gpio_ioc_cs = gpiod_get_index(&spi->dev,
"cs",
1,
GPIOD_OUT_LOW);
priv->ioc_irq = gpiod_get_index(&spi->dev,
"irq",
0,
GPIOD_IN);
if (IS_ERR(priv->ioc_irq)) {
printk(KERN_ERR "lmg: Error obtaining IRQ GPIO");
return -EIO;
}
priv->ioc_irq_number = gpiod_to_irq(priv->ioc_irq);
if (priv->ioc_irq_number < 0) {
printk(KERN_ERR "lmg: Error retrieving IRQ number for GPIO. ret=%d\n", priv->ioc_irq_number);
return -EIO;
}
printk(KERN_INFO "Found IRQ number: %d\n", priv->ioc_irq_number);
if (request_irq(priv->ioc_irq_number, ioc_irq_handler, 0, "ioc", (void*) priv)) {
printk(KERN_ERR "lmg: Can't allocate irq %d\n", priv->ioc_irq_number);
priv->ioc_irq_number = -1;
return -EBUSY;
}
printk(KERN_INFO "IRQ configuration complete\n");
priv->deferred_instructions = 0;
return 0;
}
/* VIDEO DRIVER CODE */
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.\n", var->bits_per_pixel);
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;
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 (*pixel > 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;
ioc_probe(spi, priv); // TODO: Handle return value
printk(KERN_INFO "lmg6202ulyt: DEBUG: SKIP DISPLAY INIT\n");
return 0;
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);
printk(KERN_INFO "DEBUG: SKIPPING framebuffer dealloc");
//fb_dealloc_cmap(&priv->info.cmap); // TODO: reenable
if (priv->ioc_irq_number >= 0)
free_irq(priv->ioc_irq_number, priv);
gpiod_put(priv->ioc_irq);
gpiod_put(priv->gpio_ioc_cs);
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);