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