From b6b404e6d18625c82d7bd3aab9aeec871f0205fe Mon Sep 17 00:00:00 2001 From: Mohamad Noor Alim Hussin Date: Wed, 19 Feb 2025 13:27:43 +0800 Subject: [PATCH] fb: add Efinix framebuffer driver --- drivers/video/fbdev/Kconfig | 9 + drivers/video/fbdev/Makefile | 1 + drivers/video/fbdev/fb_efx.c | 555 +++++++++++++++++++++++++++++++++++ 3 files changed, 565 insertions(+) create mode 100644 drivers/video/fbdev/fb_efx.c diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig index 4f02db65d..eda5045b4 100644 --- a/drivers/video/fbdev/Kconfig +++ b/drivers/video/fbdev/Kconfig @@ -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 diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile index 477b9624b..feebff39e 100644 --- a/drivers/video/fbdev/Makefile +++ b/drivers/video/fbdev/Makefile @@ -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 diff --git a/drivers/video/fbdev/fb_efx.c b/drivers/video/fbdev/fb_efx.c new file mode 100644 index 000000000..b703cec8f --- /dev/null +++ b/drivers/video/fbdev/fb_efx.c @@ -0,0 +1,555 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Efinix framebuffer driver"); +MODULE_LICENSE("GPL v2");