// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Aeroflex Gaisler SVGACTRL framebuffer device.
*
* 2011 (c) Aeroflex Gaisler AB
*
* Full documentation of the core can be found here:
* https://www.gaisler.com/products/grlib/grip.pdf
*
* Contributors: Kristoffer Glembo <kristoffer@gaisler.com>
*/
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/of.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 const 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->va
r.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))+0 x7FFF-(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 &= ~3 UL;
/* Set framebuffer base address */
__raw_writel(base_addr,
&par->regs->fb_pos);
return 0 ;
}
static const struct fb_ops grvga_ops = {
.owner = THIS_MODULE,
FB_DEFAULT_IOMEM_OPS,
.fb_check_var = grvga_check_var,
.fb_set_par = grvga_set_par,
.fb_setcolreg = grvga_setcolreg,
.fb_pan_display = grvga_pan_display,
};
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)
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_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 void 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);
}
}
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" );
Messung V0.5 in Prozent C=95 H=93 G=93
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland