#include #include #include #include #include #include #include #include #include #include #include #include #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);