fb: add Efinix framebuffer driver
This commit is contained in:
@@ -2206,6 +2206,15 @@ config FB_SIMPLE
|
||||
Configuration re: surface address, size, and format must be provided
|
||||
through device tree, or plain old platform data.
|
||||
|
||||
config FB_EFX
|
||||
bool "Efinix framebuffer support"
|
||||
depends on FB
|
||||
select FB_CFB_FILLRECT
|
||||
select FB_CFB_COPYAREA
|
||||
select FB_CFB_IMAGEBLIT
|
||||
help
|
||||
Efinix framebuffer support
|
||||
|
||||
config FB_SSD1307
|
||||
tristate "Solomon SSD1307 framebuffer support"
|
||||
depends on FB && I2C
|
||||
|
||||
@@ -118,6 +118,7 @@ obj-$(CONFIG_FB_MB862XX) += mb862xx/
|
||||
obj-$(CONFIG_FB_HYPERV) += hyperv_fb.o
|
||||
obj-$(CONFIG_FB_OPENCORES) += ocfb.o
|
||||
obj-$(CONFIG_FB_SM712) += sm712fb.o
|
||||
obj-$(CONFIG_FB_EFX) += fb_efx.o
|
||||
|
||||
# Platform or fallback drivers go here
|
||||
obj-$(CONFIG_FB_UVESA) += uvesafb.o
|
||||
|
||||
555
drivers/video/fbdev/fb_efx.c
Normal file
555
drivers/video/fbdev/fb_efx.c
Normal file
@@ -0,0 +1,555 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define DRIVER_NAME "efx-framebuffer"
|
||||
#define EFX_MAX_FB_SIZE (4 * 1024 * 1024)
|
||||
|
||||
struct efxfb_format {
|
||||
const char *name;
|
||||
u32 bits_per_pixel;
|
||||
struct fb_bitfield red;
|
||||
struct fb_bitfield green;
|
||||
struct fb_bitfield blue;
|
||||
struct fb_bitfield transp;
|
||||
};
|
||||
|
||||
struct efxfb_params {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 stride;
|
||||
struct efxfb_format *format;
|
||||
};
|
||||
|
||||
#define PSEUDO_PALETTE_SIZE 16
|
||||
|
||||
struct efxfb_par {
|
||||
u32 palette[PSEUDO_PALETTE_SIZE];
|
||||
void __iomem *display_buff_io;
|
||||
void __iomem *base;
|
||||
char *display_buf;
|
||||
u32 fb_size;
|
||||
int fb_transfer_mode;
|
||||
|
||||
/* DMA */
|
||||
struct dma_chan *chan;
|
||||
dma_addr_t dma_display_buff_io;
|
||||
struct completion transfer_ok;
|
||||
size_t nents;
|
||||
struct scatterlist *sg;
|
||||
struct page **pages;
|
||||
size_t segments;
|
||||
|
||||
struct efxfb_params *params;
|
||||
struct platform_device *pdev;
|
||||
};
|
||||
|
||||
static const struct fb_fix_screeninfo efxfb_fix = {
|
||||
.id = "efxfb",
|
||||
.type = FB_TYPE_PACKED_PIXELS,
|
||||
.visual = FB_VISUAL_TRUECOLOR,
|
||||
.accel = FB_ACCEL_NONE,
|
||||
};
|
||||
|
||||
static const struct fb_var_screeninfo efxfb_var = {
|
||||
.height = -1,
|
||||
.width = -1,
|
||||
.activate = FB_ACTIVATE_NOW,
|
||||
.vmode = FB_VMODE_NONINTERLACED,
|
||||
};
|
||||
|
||||
static int efxfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
|
||||
u_int transp, struct fb_info *info)
|
||||
{
|
||||
u32 *pal = info->pseudo_palette;
|
||||
u32 cr = red >> (16 - info->var.red.length);
|
||||
u32 cg = green >> (16 - info->var.green.length);
|
||||
u32 cb = blue >> (16 - info->var.blue.length);
|
||||
u32 value;
|
||||
|
||||
if (regno >= PSEUDO_PALETTE_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
value = (cr << info->var.red.offset) |
|
||||
(cg << info->var.green.offset) |
|
||||
(cb << info->var.blue.offset);
|
||||
|
||||
if (info->var.transp.length > 0) {
|
||||
u32 mask = (1 << info->var.transp.length) - 1;
|
||||
mask <<= info->var.transp.offset;
|
||||
value |= mask;
|
||||
}
|
||||
|
||||
pal[regno] = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efxfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
||||
{
|
||||
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
|
||||
unsigned long size = vma->vm_end - vma->vm_start;
|
||||
struct efxfb_par *par = info->par;
|
||||
unsigned long page_start = (unsigned long)par->display_buf + offset;
|
||||
unsigned long page_count = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
||||
unsigned long start = vma->vm_start;
|
||||
unsigned long pfn;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < page_count; i++) {
|
||||
pfn = vmalloc_to_pfn((void *)(page_start + (i << PAGE_SHIFT)));
|
||||
if(remap_pfn_range(vma, start + (i << PAGE_SHIFT), pfn, PAGE_SIZE, vma->vm_page_prot)) {
|
||||
return -EAGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void efxfb_destroy(struct fb_info *info)
|
||||
{
|
||||
if (info->screen_base)
|
||||
iounmap(info->screen_base);
|
||||
}
|
||||
|
||||
static struct fb_ops efxfb_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.fb_mmap = efxfb_mmap,
|
||||
.fb_destroy = efxfb_destroy,
|
||||
.fb_setcolreg = efxfb_setcolreg,
|
||||
.fb_fillrect = cfb_fillrect,
|
||||
.fb_copyarea = cfb_copyarea,
|
||||
.fb_imageblit = cfb_imageblit,
|
||||
};
|
||||
|
||||
static int efxfb_parse_dt(struct platform_device *pdev,
|
||||
struct efxfb_params *params)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(np, "width", ¶ms->width);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Can't parse width property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "height", ¶ms->height);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Can't parse height property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "stride", ¶ms->stride);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Can't parse stride property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void efxfb_set_fix(struct platform_device *pdev,
|
||||
struct efxfb_params *params)
|
||||
{
|
||||
struct fb_info *info = platform_get_drvdata(pdev);
|
||||
struct efxfb_par *par = info->par;
|
||||
|
||||
info->fix = efxfb_fix;
|
||||
if (par->fb_transfer_mode == 1)
|
||||
info->fix.smem_start = par->dma_display_buff_io;
|
||||
else if (par->fb_transfer_mode == 2)
|
||||
info->fix.smem_start = (uintptr_t)page_to_phys(vmalloc_to_page(par->display_buf));
|
||||
|
||||
info->fix.smem_len = par->fb_size;
|
||||
info->fix.line_length = params->stride;
|
||||
}
|
||||
|
||||
static void efxfb_set_var(struct platform_device *pdev,
|
||||
struct efxfb_params *params)
|
||||
{
|
||||
struct efxfb_format format;
|
||||
struct fb_info *info = platform_get_drvdata(pdev);
|
||||
char format_name[] = "a8b8g8r8";
|
||||
|
||||
format.bits_per_pixel = 32;
|
||||
|
||||
format.red.offset = 0;
|
||||
format.red.length = 8;
|
||||
|
||||
format.green.offset = 8;
|
||||
format.green.length = 8;
|
||||
|
||||
format.blue.offset = 16;
|
||||
format.blue.length = 8;
|
||||
|
||||
format.transp.offset = 24;
|
||||
format.transp.length = 8;
|
||||
|
||||
info->var = efxfb_var;
|
||||
|
||||
info->var.xres = params->width;
|
||||
info->var.yres = params->height;
|
||||
info->var.xres_virtual = params->width;
|
||||
info->var.yres_virtual = params->height;
|
||||
|
||||
info->var.bits_per_pixel = format.bits_per_pixel;
|
||||
info->var.red = format.red;
|
||||
info->var.green = format.green;
|
||||
info->var.blue = format.blue;
|
||||
info->var.transp = format.transp;
|
||||
|
||||
dev_info(&pdev->dev, "framebuffer at 0x%lx, 0x%x bytes, mapped to 0x%pK\n",
|
||||
info->fix.smem_start, info->fix.smem_len,
|
||||
info->screen_base);
|
||||
dev_info(&pdev->dev, "format=%s, mode=%dx%dx%d, linelength=%d\n",
|
||||
format_name,
|
||||
info->var.xres, info->var.yres,
|
||||
info->var.bits_per_pixel, info->fix.line_length);
|
||||
}
|
||||
|
||||
static int efxfb_init_scatterlist_vmalloc(struct efxfb_par *par, void *buf_addr, enum dma_transfer_direction direction)
|
||||
{
|
||||
struct device *dev = &par->pdev->dev;
|
||||
struct scatterlist *sg;
|
||||
struct page **pages;
|
||||
size_t segments = par->segments;
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
if (!is_vmalloc_addr(buf_addr) || (uintptr_t)buf_addr & (PAGE_SIZE - 1)) {
|
||||
dev_err(dev, "Buffer address is not vmalloc or not page-aligned\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// Allocate page array
|
||||
pages = kcalloc(segments, sizeof(*pages), GFP_KERNEL);
|
||||
if (!pages) {
|
||||
dev_err(dev, "Failed to allocate page array\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
par->pages = pages;
|
||||
|
||||
// Allocate scatterlist
|
||||
sg = kcalloc(segments, sizeof(*sg), GFP_KERNEL);
|
||||
if (!sg) {
|
||||
dev_err(dev, "Failed to allocate scatterlist\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_free_pages;
|
||||
}
|
||||
sg_init_table(sg, segments);
|
||||
par->sg = sg;
|
||||
|
||||
// Get pages for vmalloc buffer
|
||||
for (i = 0; i < segments; i++) {
|
||||
pages[i] = vmalloc_to_page(buf_addr + i * PAGE_SIZE);
|
||||
if (!pages[i]) {
|
||||
dev_err(dev, "Failed to get page for vmalloc buffer\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_free_sg;
|
||||
}
|
||||
// Initialize and populate scatterlist
|
||||
sg_set_page(&sg[i], pages[i], PAGE_SIZE, 0);
|
||||
}
|
||||
|
||||
par->nents = dma_map_sg(dev, sg, segments, direction);
|
||||
if (par->nents <= 0) {
|
||||
dev_err(dev, "Failed to map scatterlist with vmalloc buffer\n");
|
||||
ret = -EIO;
|
||||
if (par->nents > 0) {
|
||||
dma_unmap_sg(dev, sg, segments, direction);
|
||||
}
|
||||
goto err_free_sg;
|
||||
}
|
||||
|
||||
dma_sync_sg_for_device(dev, sg, par->nents, direction);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_sg:
|
||||
kfree(par->sg);
|
||||
par->sg = NULL;
|
||||
err_free_pages:
|
||||
kfree(par->pages);
|
||||
par->pages = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int efxfb_dma_init_vmalloc_sg_cyclic(struct efxfb_par *par)
|
||||
{
|
||||
struct device *dev = &par->pdev->dev;
|
||||
struct dma_async_tx_descriptor *tx = NULL;
|
||||
dma_cookie_t cookie;
|
||||
int ret;
|
||||
unsigned int flags = DMA_CTRL_REUSE;
|
||||
|
||||
par->display_buf = vmalloc(par->fb_size);
|
||||
if (!par->display_buf) {
|
||||
dev_err(dev, "Failed to allocate memory using vmalloc\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
// Calculate number of pages
|
||||
par->segments = DIV_ROUND_UP(par->fb_size, PAGE_SIZE);
|
||||
ret = efxfb_init_scatterlist_vmalloc(par, par->display_buf, DMA_MEM_TO_DEV);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
par->chan = dma_request_chan(dev, "display");
|
||||
if (!par->chan) {
|
||||
dev_err(dev, "Failed to request DMA channel for display\n");
|
||||
ret = PTR_ERR(par->chan);
|
||||
goto err_unmap_sg;
|
||||
}
|
||||
|
||||
tx = dmaengine_prep_slave_sg(par->chan, par->sg, par->nents, DMA_MEM_TO_DEV, flags);
|
||||
if (!tx) {
|
||||
dev_err(dev, "Failed to prepare DMA descriptor\n");
|
||||
ret = -EIO;
|
||||
goto err_release_chan;
|
||||
}
|
||||
|
||||
cookie = dmaengine_submit(tx);
|
||||
if (dma_submit_error(cookie)) {
|
||||
dev_err(dev, "Failed to submit DMA transfer\n");
|
||||
ret = -EIO;
|
||||
goto err_release_chan;
|
||||
}
|
||||
dma_async_issue_pending(par->chan);
|
||||
|
||||
return 0;
|
||||
|
||||
err_release_chan:
|
||||
dma_release_channel(par->chan);
|
||||
par->chan = NULL;
|
||||
err_unmap_sg:
|
||||
dma_unmap_sg(dev, par->sg, par->nents, DMA_MEM_TO_DEV);
|
||||
kfree(par->sg);
|
||||
par->sg = NULL;
|
||||
kfree(par->pages);
|
||||
par->pages = NULL;
|
||||
vfree(par->display_buf);
|
||||
par->display_buf = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int efxfb_dma_init_kmalloc_sg_cyclic(struct efxfb_par *par)
|
||||
{
|
||||
struct device *dev = &par->pdev->dev;
|
||||
struct dma_chan *chan;
|
||||
struct dma_async_tx_descriptor *tx = NULL;
|
||||
dma_cookie_t cookie;
|
||||
int ret;
|
||||
size_t period_len = par->params->stride;
|
||||
unsigned long flags = 0;
|
||||
|
||||
par->display_buf = dma_alloc_coherent(dev, par->fb_size,
|
||||
&par->dma_display_buff_io,
|
||||
GFP_KERNEL);
|
||||
if (!par->display_buf) {
|
||||
dev_err(dev, "Failed to request memory region\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
chan = dma_request_chan(dev, "display");
|
||||
if (IS_ERR(chan)) {
|
||||
ret = PTR_ERR(chan);
|
||||
dev_err(dev, "Failed to request DMA channel: %d\n", ret);
|
||||
chan = NULL;
|
||||
goto err_free_buf;
|
||||
}
|
||||
|
||||
par->chan = chan;
|
||||
|
||||
tx = dmaengine_prep_dma_cyclic(chan, par->dma_display_buff_io, par->fb_size,
|
||||
period_len, DMA_MEM_TO_DEV, flags);
|
||||
if (!tx) {
|
||||
dev_err(dev, "Failed to prepare DMA transfer\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_prep;
|
||||
}
|
||||
|
||||
cookie = dmaengine_submit(tx);
|
||||
if (dma_submit_error(cookie)) {
|
||||
dev_err(dev, "Failed to submit DMA transfer\n");
|
||||
ret = -EIO;
|
||||
goto err_prep;
|
||||
}
|
||||
|
||||
dev_info(dev, "Start DMA transfer for framebuffer\n");
|
||||
dma_async_issue_pending(chan);
|
||||
|
||||
return 0;
|
||||
|
||||
err_prep:
|
||||
dma_release_channel(par->chan);
|
||||
par->chan = NULL;
|
||||
err_free_buf:
|
||||
dma_free_coherent(dev, par->fb_size, par->display_buf, par->dma_display_buff_io);
|
||||
par->display_buf = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int efxfb_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct efxfb_params params;
|
||||
struct fb_info *info;
|
||||
struct efxfb_par *par;
|
||||
struct resource *res;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node)
|
||||
ret = efxfb_parse_dt(pdev, ¶ms);
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
par = devm_kzalloc(&pdev->dev, sizeof(struct efxfb_par), GFP_KERNEL);
|
||||
if (!par)
|
||||
return -ENOMEM;
|
||||
|
||||
par->pdev = pdev;
|
||||
par->params = ¶ms;
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "No memory resource\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
par->base = devm_ioremap_resource(dev, res);
|
||||
|
||||
info = framebuffer_alloc(sizeof(struct efxfb_par), &pdev->dev);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
par->fb_size = params.stride * params.height;
|
||||
if (!par->fb_size) {
|
||||
dev_err(&pdev->dev, "Framebuffer size should not be 0\n");
|
||||
ret = -EINVAL;
|
||||
goto error_fb_release;
|
||||
}
|
||||
|
||||
// Reset the display
|
||||
writel(1, par->base + 0x4);
|
||||
udelay(10);
|
||||
writel(0, par->base + 0x4);
|
||||
|
||||
if (par->fb_size < EFX_MAX_FB_SIZE) {
|
||||
par->fb_transfer_mode = 1;
|
||||
ret = efxfb_dma_init_kmalloc_sg_cyclic(par);
|
||||
info->screen_buffer = par->display_buf;
|
||||
efxfb_ops.fb_mmap = NULL;
|
||||
|
||||
} else {
|
||||
par->fb_transfer_mode = 2;
|
||||
ret = efxfb_dma_init_vmalloc_sg_cyclic(par);
|
||||
info->screen_base = (char __iomem *)par->display_buf;
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
goto error_fb_release;
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
info->par = par;
|
||||
info->pseudo_palette = par->palette;
|
||||
|
||||
efxfb_set_fix(pdev, ¶ms);
|
||||
efxfb_set_var(pdev, ¶ms);
|
||||
|
||||
info->fbops = &efxfb_ops;
|
||||
info->flags = FBINFO_DEFAULT | FBINFO_MISC_FIRMWARE;
|
||||
|
||||
ret = register_framebuffer(info);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Unable to register efxfb: %d\n", ret);
|
||||
goto error_fb_release;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "fb%d: efxfb registerd!\n", info->node);
|
||||
|
||||
return 0;
|
||||
|
||||
error_fb_release:
|
||||
framebuffer_release(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int efxfb_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct fb_info *info = platform_get_drvdata(pdev);
|
||||
struct efxfb_par *par = info->par;
|
||||
struct dma_chan *chan = par->chan;
|
||||
|
||||
if (chan) {
|
||||
dma_release_channel(chan);
|
||||
chan = NULL;
|
||||
}
|
||||
|
||||
if (par->fb_transfer_mode == 1) {
|
||||
dma_free_coherent(&pdev->dev, par->fb_size, par->display_buf, par->dma_display_buff_io);
|
||||
} else if (par->fb_transfer_mode == 2) {
|
||||
dma_unmap_sg(&pdev->dev, par->sg, par->nents, DMA_MEM_TO_DEV);
|
||||
kfree(par->sg);
|
||||
par->sg = NULL;
|
||||
kfree(par->pages);
|
||||
par->pages = NULL;
|
||||
vfree(par->display_buf);
|
||||
par->display_buf = NULL;
|
||||
}
|
||||
|
||||
unregister_framebuffer(info);
|
||||
framebuffer_release(info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id efxfb_of_match[] = {
|
||||
{ .compatible = "efx,efx-fb"},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, efxfb_of_match);
|
||||
|
||||
static struct platform_driver efxfb_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = efxfb_of_match,
|
||||
},
|
||||
.probe = efxfb_probe,
|
||||
.remove = efxfb_remove,
|
||||
};
|
||||
|
||||
static int __init efxfb_init(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
platform_driver_register(&efxfb_driver);
|
||||
|
||||
if (IS_ENABLED(CONFIG_OF_ADDRESS) && of_chosen) {
|
||||
for_each_child_of_node(of_chosen, np) {
|
||||
if (of_device_is_compatible(np, DRIVER_NAME))
|
||||
of_platform_device_create(np, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit efxfb_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&efxfb_driver);
|
||||
}
|
||||
|
||||
late_initcall(efxfb_init);
|
||||
|
||||
MODULE_AUTHOR("Alim Hussin <mnalim@efinixinc.com>");
|
||||
MODULE_DESCRIPTION("Efinix framebuffer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Reference in New Issue
Block a user