123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- /*
- * Driver for Aeroflex Gaisler SVGACTRL framebuffer device.
- *
- * 2011 (c) Aeroflex Gaisler AB
- *
- * Full documentation of the core can be found here:
- * http://www.gaisler.com/products/grlib/grip.pdf
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
- *
- * Contributors: Kristoffer Glembo <kristoffer@gaisler.com>
- *
- */
- #include <linux/platform_device.h>
- #include <linux/dma-mapping.h>
- #include <linux/of_platform.h>
- #include <linux/of_device.h>
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/string.h>
- #include <linux/delay.h>
- #include <linux/errno.h>
- #include <linux/init.h>
- #include <linux/slab.h>
- #include <linux/tty.h>
- #include <linux/mm.h>
- #include <linux/fb.h>
- #include <linux/io.h>
- struct grvga_regs {
- u32 status; /* 0x00 */
- u32 video_length; /* 0x04 */
- u32 front_porch; /* 0x08 */
- u32 sync_length; /* 0x0C */
- u32 line_length; /* 0x10 */
- u32 fb_pos; /* 0x14 */
- u32 clk_vector[4]; /* 0x18 */
- u32 clut; /* 0x20 */
- };
- struct grvga_par {
- struct grvga_regs *regs;
- u32 color_palette[16]; /* 16 entry pseudo palette used by fbcon in true color mode */
- int clk_sel;
- int fb_alloced; /* = 1 if framebuffer is allocated in main memory */
- };
- static const struct fb_videomode grvga_modedb[] = {
- {
- /* 640x480 @ 60 Hz */
- NULL, 60, 640, 480, 40000, 48, 16, 39, 11, 96, 2,
- 0, FB_VMODE_NONINTERLACED
- }, {
- /* 800x600 @ 60 Hz */
- NULL, 60, 800, 600, 25000, 88, 40, 23, 1, 128, 4,
- 0, FB_VMODE_NONINTERLACED
- }, {
- /* 800x600 @ 72 Hz */
- NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6,
- 0, FB_VMODE_NONINTERLACED
- }, {
- /* 1024x768 @ 60 Hz */
- NULL, 60, 1024, 768, 15385, 160, 24, 29, 3, 136, 6,
- 0, FB_VMODE_NONINTERLACED
- }
- };
- static struct fb_fix_screeninfo grvga_fix = {
- .id = "AG SVGACTRL",
- .type = FB_TYPE_PACKED_PIXELS,
- .visual = FB_VISUAL_PSEUDOCOLOR,
- .xpanstep = 0,
- .ypanstep = 1,
- .ywrapstep = 0,
- .accel = FB_ACCEL_NONE,
- };
- static int grvga_check_var(struct fb_var_screeninfo *var,
- struct fb_info *info)
- {
- struct grvga_par *par = info->par;
- int i;
- if (!var->xres)
- var->xres = 1;
- if (!var->yres)
- var->yres = 1;
- if (var->bits_per_pixel <= 8)
- var->bits_per_pixel = 8;
- else if (var->bits_per_pixel <= 16)
- var->bits_per_pixel = 16;
- else if (var->bits_per_pixel <= 24)
- var->bits_per_pixel = 24;
- else if (var->bits_per_pixel <= 32)
- var->bits_per_pixel = 32;
- else
- return -EINVAL;
- var->xres_virtual = var->xres;
- var->yres_virtual = 2*var->yres;
- if (info->fix.smem_len) {
- if ((var->yres_virtual*var->xres_virtual*var->bits_per_pixel/8) > info->fix.smem_len)
- return -ENOMEM;
- }
- /* Which clocks that are available can be read out in these registers */
- for (i = 0; i <= 3 ; i++) {
- if (var->pixclock == par->regs->clk_vector[i])
- break;
- }
- if (i <= 3)
- par->clk_sel = i;
- else
- return -EINVAL;
- switch (info->var.bits_per_pixel) {
- case 8:
- var->red = (struct fb_bitfield) {0, 8, 0}; /* offset, length, msb-right */
- var->green = (struct fb_bitfield) {0, 8, 0};
- var->blue = (struct fb_bitfield) {0, 8, 0};
- var->transp = (struct fb_bitfield) {0, 0, 0};
- break;
- case 16:
- var->red = (struct fb_bitfield) {11, 5, 0};
- var->green = (struct fb_bitfield) {5, 6, 0};
- var->blue = (struct fb_bitfield) {0, 5, 0};
- var->transp = (struct fb_bitfield) {0, 0, 0};
- break;
- case 24:
- case 32:
- var->red = (struct fb_bitfield) {16, 8, 0};
- var->green = (struct fb_bitfield) {8, 8, 0};
- var->blue = (struct fb_bitfield) {0, 8, 0};
- var->transp = (struct fb_bitfield) {24, 8, 0};
- break;
- default:
- return -EINVAL;
- }
- return 0;
- }
- static int grvga_set_par(struct fb_info *info)
- {
- u32 func = 0;
- struct grvga_par *par = info->par;
- __raw_writel(((info->var.yres - 1) << 16) | (info->var.xres - 1),
- &par->regs->video_length);
- __raw_writel((info->var.lower_margin << 16) | (info->var.right_margin),
- &par->regs->front_porch);
- __raw_writel((info->var.vsync_len << 16) | (info->var.hsync_len),
- &par->regs->sync_length);
- __raw_writel(((info->var.yres + info->var.lower_margin + info->var.upper_margin + info->var.vsync_len - 1) << 16) |
- (info->var.xres + info->var.right_margin + info->var.left_margin + info->var.hsync_len - 1),
- &par->regs->line_length);
- switch (info->var.bits_per_pixel) {
- case 8:
- info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
- func = 1;
- break;
- case 16:
- info->fix.visual = FB_VISUAL_TRUECOLOR;
- func = 2;
- break;
- case 24:
- case 32:
- info->fix.visual = FB_VISUAL_TRUECOLOR;
- func = 3;
- break;
- default:
- return -EINVAL;
- }
- __raw_writel((par->clk_sel << 6) | (func << 4) | 1,
- &par->regs->status);
- info->fix.line_length = (info->var.xres_virtual*info->var.bits_per_pixel)/8;
- return 0;
- }
- static int grvga_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info)
- {
- struct grvga_par *par;
- par = info->par;
- if (regno >= 256) /* Size of CLUT */
- return -EINVAL;
- if (info->var.grayscale) {
- /* grayscale = 0.30*R + 0.59*G + 0.11*B */
- red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8;
- }
- #define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16)
- red = CNVT_TOHW(red, info->var.red.length);
- green = CNVT_TOHW(green, info->var.green.length);
- blue = CNVT_TOHW(blue, info->var.blue.length);
- transp = CNVT_TOHW(transp, info->var.transp.length);
- #undef CNVT_TOHW
- /* In PSEUDOCOLOR we use the hardware CLUT */
- if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR)
- __raw_writel((regno << 24) | (red << 16) | (green << 8) | blue,
- &par->regs->clut);
- /* Truecolor uses the pseudo palette */
- else if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
- u32 v;
- if (regno >= 16)
- return -EINVAL;
- v = (red << info->var.red.offset) |
- (green << info->var.green.offset) |
- (blue << info->var.blue.offset) |
- (transp << info->var.transp.offset);
- ((u32 *) (info->pseudo_palette))[regno] = v;
- }
- return 0;
- }
- static int grvga_pan_display(struct fb_var_screeninfo *var,
- struct fb_info *info)
- {
- struct grvga_par *par = info->par;
- struct fb_fix_screeninfo *fix = &info->fix;
- u32 base_addr;
- if (var->xoffset != 0)
- return -EINVAL;
- base_addr = fix->smem_start + (var->yoffset * fix->line_length);
- base_addr &= ~3UL;
- /* Set framebuffer base address */
- __raw_writel(base_addr,
- &par->regs->fb_pos);
- return 0;
- }
- static struct fb_ops grvga_ops = {
- .owner = THIS_MODULE,
- .fb_check_var = grvga_check_var,
- .fb_set_par = grvga_set_par,
- .fb_setcolreg = grvga_setcolreg,
- .fb_pan_display = grvga_pan_display,
- .fb_fillrect = cfb_fillrect,
- .fb_copyarea = cfb_copyarea,
- .fb_imageblit = cfb_imageblit
- };
- static int grvga_parse_custom(char *options,
- struct fb_var_screeninfo *screendata)
- {
- char *this_opt;
- int count = 0;
- if (!options || !*options)
- return -1;
- while ((this_opt = strsep(&options, " ")) != NULL) {
- if (!*this_opt)
- continue;
- switch (count) {
- case 0:
- screendata->pixclock = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 1:
- screendata->xres = screendata->xres_virtual = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 2:
- screendata->right_margin = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 3:
- screendata->hsync_len = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 4:
- screendata->left_margin = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 5:
- screendata->yres = screendata->yres_virtual = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 6:
- screendata->lower_margin = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 7:
- screendata->vsync_len = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 8:
- screendata->upper_margin = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- case 9:
- screendata->bits_per_pixel = simple_strtoul(this_opt, NULL, 0);
- count++;
- break;
- default:
- return -1;
- }
- }
- screendata->activate = FB_ACTIVATE_NOW;
- screendata->vmode = FB_VMODE_NONINTERLACED;
- return 0;
- }
- static int grvga_probe(struct platform_device *dev)
- {
- struct fb_info *info;
- int retval = -ENOMEM;
- unsigned long virtual_start;
- unsigned long grvga_fix_addr = 0;
- unsigned long physical_start = 0;
- unsigned long grvga_mem_size = 0;
- struct grvga_par *par = NULL;
- char *options = NULL, *mode_opt = NULL;
- info = framebuffer_alloc(sizeof(struct grvga_par), &dev->dev);
- if (!info) {
- dev_err(&dev->dev, "framebuffer_alloc failed\n");
- return -ENOMEM;
- }
- /* Expecting: "grvga: modestring, [addr:<framebuffer physical address>], [size:<framebuffer size>]
- *
- * If modestring is custom:<custom mode string> we parse the string which then contains all videoparameters
- * If address is left out, we allocate memory,
- * if size is left out we only allocate enough to support the given mode.
- */
- if (fb_get_options("grvga", &options)) {
- retval = -ENODEV;
- goto free_fb;
- }
- if (!options || !*options)
- options = "640x480-8@60";
- while (1) {
- char *this_opt = strsep(&options, ",");
- if (!this_opt)
- break;
- if (!strncmp(this_opt, "custom", 6)) {
- if (grvga_parse_custom(this_opt, &info->var) < 0) {
- dev_err(&dev->dev, "Failed to parse custom mode (%s).\n", this_opt);
- retval = -EINVAL;
- goto free_fb;
- }
- } else if (!strncmp(this_opt, "addr", 4))
- grvga_fix_addr = simple_strtoul(this_opt + 5, NULL, 16);
- else if (!strncmp(this_opt, "size", 4))
- grvga_mem_size = simple_strtoul(this_opt + 5, NULL, 0);
- else
- mode_opt = this_opt;
- }
- par = info->par;
- info->fbops = &grvga_ops;
- info->fix = grvga_fix;
- info->pseudo_palette = par->color_palette;
- info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | FBINFO_HWACCEL_YPAN;
- info->fix.smem_len = grvga_mem_size;
- if (!devm_request_mem_region(&dev->dev, dev->resource[0].start,
- resource_size(&dev->resource[0]), "grlib-svgactrl regs")) {
- dev_err(&dev->dev, "registers already mapped\n");
- retval = -EBUSY;
- goto free_fb;
- }
- par->regs = of_ioremap(&dev->resource[0], 0,
- resource_size(&dev->resource[0]),
- "grlib-svgactrl regs");
- if (!par->regs) {
- dev_err(&dev->dev, "failed to map registers\n");
- retval = -ENOMEM;
- goto free_fb;
- }
- retval = fb_alloc_cmap(&info->cmap, 256, 0);
- if (retval < 0) {
- dev_err(&dev->dev, "failed to allocate mem with fb_alloc_cmap\n");
- retval = -ENOMEM;
- goto unmap_regs;
- }
- if (mode_opt) {
- retval = fb_find_mode(&info->var, info, mode_opt,
- grvga_modedb, sizeof(grvga_modedb), &grvga_modedb[0], 8);
- if (!retval || retval == 4) {
- retval = -EINVAL;
- goto dealloc_cmap;
- }
- }
- if (!grvga_mem_size)
- grvga_mem_size = info->var.xres_virtual * info->var.yres_virtual * info->var.bits_per_pixel/8;
- if (grvga_fix_addr) {
- /* Got framebuffer base address from argument list */
- physical_start = grvga_fix_addr;
- if (!devm_request_mem_region(&dev->dev, physical_start,
- grvga_mem_size, dev->name)) {
- dev_err(&dev->dev, "failed to request memory region\n");
- retval = -ENOMEM;
- goto dealloc_cmap;
- }
- virtual_start = (unsigned long) ioremap(physical_start, grvga_mem_size);
- if (!virtual_start) {
- dev_err(&dev->dev, "error mapping framebuffer memory\n");
- retval = -ENOMEM;
- goto dealloc_cmap;
- }
- } else { /* Allocate frambuffer memory */
- unsigned long page;
- virtual_start = (unsigned long) __get_free_pages(GFP_DMA,
- get_order(grvga_mem_size));
- if (!virtual_start) {
- dev_err(&dev->dev,
- "unable to allocate framebuffer memory (%lu bytes)\n",
- grvga_mem_size);
- retval = -ENOMEM;
- goto dealloc_cmap;
- }
- physical_start = dma_map_single(&dev->dev, (void *)virtual_start, grvga_mem_size, DMA_TO_DEVICE);
- /* Set page reserved so that mmap will work. This is necessary
- * since we'll be remapping normal memory.
- */
- for (page = virtual_start;
- page < PAGE_ALIGN(virtual_start + grvga_mem_size);
- page += PAGE_SIZE) {
- SetPageReserved(virt_to_page(page));
- }
- par->fb_alloced = 1;
- }
- memset((unsigned long *) virtual_start, 0, grvga_mem_size);
- info->screen_base = (char __iomem *) virtual_start;
- info->fix.smem_start = physical_start;
- info->fix.smem_len = grvga_mem_size;
- dev_set_drvdata(&dev->dev, info);
- dev_info(&dev->dev,
- "Aeroflex Gaisler framebuffer device (fb%d), %dx%d-%d, using %luK of video memory @ %p\n",
- info->node, info->var.xres, info->var.yres, info->var.bits_per_pixel,
- grvga_mem_size >> 10, info->screen_base);
- retval = register_framebuffer(info);
- if (retval < 0) {
- dev_err(&dev->dev, "failed to register framebuffer\n");
- goto free_mem;
- }
- __raw_writel(physical_start, &par->regs->fb_pos);
- __raw_writel(__raw_readl(&par->regs->status) | 1, /* Enable framebuffer */
- &par->regs->status);
- return 0;
- free_mem:
- if (grvga_fix_addr)
- iounmap((void *)virtual_start);
- else
- kfree((void *)virtual_start);
- dealloc_cmap:
- fb_dealloc_cmap(&info->cmap);
- unmap_regs:
- of_iounmap(&dev->resource[0], par->regs,
- resource_size(&dev->resource[0]));
- free_fb:
- framebuffer_release(info);
- return retval;
- }
- static int grvga_remove(struct platform_device *device)
- {
- struct fb_info *info = dev_get_drvdata(&device->dev);
- struct grvga_par *par;
- if (info) {
- par = info->par;
- unregister_framebuffer(info);
- fb_dealloc_cmap(&info->cmap);
- of_iounmap(&device->resource[0], par->regs,
- resource_size(&device->resource[0]));
- if (!par->fb_alloced)
- iounmap(info->screen_base);
- else
- kfree((void *)info->screen_base);
- framebuffer_release(info);
- }
- return 0;
- }
- static struct of_device_id svgactrl_of_match[] = {
- {
- .name = "GAISLER_SVGACTRL",
- },
- {
- .name = "01_063",
- },
- {},
- };
- MODULE_DEVICE_TABLE(of, svgactrl_of_match);
- static struct platform_driver grvga_driver = {
- .driver = {
- .name = "grlib-svgactrl",
- .of_match_table = svgactrl_of_match,
- },
- .probe = grvga_probe,
- .remove = grvga_remove,
- };
- module_platform_driver(grvga_driver);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Aeroflex Gaisler");
- MODULE_DESCRIPTION("Aeroflex Gaisler framebuffer device driver");
|