2018-04-30 09:08:47 +02:00
|
|
|
#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>
|
2019-10-26 16:58:31 +02:00
|
|
|
#include <linux/of_gpio.h>
|
2018-04-30 09:08:47 +02:00
|
|
|
#include <linux/gpio/consumer.h>
|
|
|
|
#include <linux/fb.h>
|
2019-10-26 16:58:31 +02:00
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/workqueue.h>
|
2018-04-30 09:08:47 +02:00
|
|
|
|
|
|
|
#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);
|
|
|
|
|
2019-10-26 16:58:31 +02:00
|
|
|
|
|
|
|
/* 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 */
|
2018-04-30 09:08:47 +02:00
|
|
|
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 {
|
2019-10-26 16:58:31 +02:00
|
|
|
/* SPI dev */
|
2018-04-30 09:08:47 +02:00
|
|
|
struct spi_device *spi;
|
|
|
|
|
2019-10-26 16:58:31 +02:00
|
|
|
/* FB dev */
|
|
|
|
struct fb_info info;
|
2018-04-30 09:08:47 +02:00
|
|
|
dma_addr_t fb_phys;
|
|
|
|
void __iomem *fb_virt;
|
|
|
|
u32 pseudo_palette[PALETTE_SIZE];
|
2019-10-26 16:58:31 +02:00
|
|
|
|
|
|
|
/* IOC */
|
|
|
|
struct gpio_desc *ioc_irq;
|
|
|
|
int ioc_irq_number;
|
|
|
|
struct work_struct ioc_work;
|
|
|
|
|
|
|
|
struct gpio_desc *gpio_ioc_cs;
|
|
|
|
|
|
|
|
u32 deferred_instructions;
|
2018-04-30 09:08:47 +02:00
|
|
|
};
|
|
|
|
|
2019-10-26 16:58:31 +02:00
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
|
2018-04-30 09:08:47 +02:00
|
|
|
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;
|
2019-10-26 16:58:31 +02:00
|
|
|
|
2018-04-30 09:08:47 +02:00
|
|
|
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;
|
|
|
|
|
2019-10-26 16:58:31 +02:00
|
|
|
ioc_probe(spi, priv); // TODO: Handle return value
|
|
|
|
|
|
|
|
printk(KERN_INFO "lmg6202ulyt: DEBUG: SKIP DISPLAY INIT\n");
|
|
|
|
return 0;
|
2018-04-30 09:08:47 +02:00
|
|
|
|
|
|
|
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);
|
2019-10-26 16:58:31 +02:00
|
|
|
|
|
|
|
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);
|
2018-04-30 09:08:47 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
|