The driver has Kconfig-urable width, height and color format.
Tested with:
        qemu-system-riscv32 -M virt -vga none -device ramfb -kernel 
images/barebox-dt-2nd.img

Signed-off-by: Adrian Negreanu <[email protected]>
---
 drivers/video/Kconfig  |  39 ++++++
 drivers/video/Makefile |   1 +
 drivers/video/ramfb.c  | 308 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 348 insertions(+)
 create mode 100644 drivers/video/ramfb.c

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index a20b7bbee9..ccea930362 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -123,6 +123,45 @@ config DRIVER_VIDEO_SIMPLEFB
          Add support for setting up the kernel's simple framebuffer driver
          based on the active barebox framebuffer.
 
+config DRIVER_VIDEO_RAMFB
+       bool "QEMU RamFB support"
+       depends on OFTREE
+       help
+         Add support for setting up a QEMU RamFB driver.
+
+if DRIVER_VIDEO_RAMFB
+
+config DRIVER_VIDEO_RAMFB_WIDTH
+       int "Width"
+       default 800
+       help
+         Set the RamFB width in pixels.
+
+config DRIVER_VIDEO_RAMFB_HEIGHT
+       int "Height"
+       default 600
+       help
+         Set the RamFB height in pixels.
+
+choice
+       prompt "RamFB color format"
+       default DRIVER_VIDEO_RAMFB_FORMAT_XRGB8888
+       help
+         Set the RamFB color format.
+
+config DRIVER_VIDEO_RAMFB_FORMAT_XRGB8888
+       bool "XRGB8888 (32bit)"
+
+config DRIVER_VIDEO_RAMFB_FORMAT_RGB888
+       bool "RGB8888 (24bit)"
+
+config DRIVER_VIDEO_RAMFB_FORMAT_RGB565
+       bool "RGB565 (16bit)"
+
+endchoice
+
+endif
+
 config DRIVER_VIDEO_EDID
        bool "Add EDID support"
        help
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 9ec0420cca..d50d2d3ba5 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_DRIVER_VIDEO_OMAP) += omap.o
 obj-$(CONFIG_DRIVER_VIDEO_BCM283X) += bcm2835.o
 obj-$(CONFIG_DRIVER_VIDEO_SIMPLEFB_CLIENT) += simplefb-client.o
 obj-$(CONFIG_DRIVER_VIDEO_SIMPLEFB) += simplefb-fixup.o
+obj-$(CONFIG_DRIVER_VIDEO_RAMFB) += ramfb.o
 obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += imx-ipu-v3/
 obj-$(CONFIG_DRIVER_VIDEO_EFI_GOP) += efi_gop.o
 obj-$(CONFIG_DRIVER_VIDEO_FB_SSD1307) += ssd1307fb.o
diff --git a/drivers/video/ramfb.c b/drivers/video/ramfb.c
new file mode 100644
index 0000000000..350f6a3aed
--- /dev/null
+++ b/drivers/video/ramfb.c
@@ -0,0 +1,308 @@
+/*
+ * RAMFB driver
+ *
+ * Copyright (C) 2022 Adrian Negreanu
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#define pr_fmt(fmt) "ramfb: " fmt
+
+#include <common.h>
+#include <errno.h>
+#include <fb.h>
+#include <fcntl.h>
+#include <fs.h>
+#include <init.h>
+#include <xfuncs.h>
+
+
+#define PACKED                  __attribute__((packed))
+#define QFW_CFG_FILE_DIR        0x19
+#define QFW_CFG_INVALID         0xffff
+
+/* fw_cfg DMA commands */
+#define QFW_CFG_DMA_CTL_ERROR   0x01
+#define QFW_CFG_DMA_CTL_READ    0x02
+#define QFW_CFG_DMA_CTL_SKIP    0x04
+#define QFW_CFG_DMA_CTL_SELECT  0x08
+#define QFW_CFG_DMA_CTL_WRITE   0x10
+
+#define QFW_CFG_OFFSET_DATA8     0     /* Data Register address: Base + 0 (8 
bytes). */
+#define QFW_CFG_OFFSET_DATA16    0
+#define QFW_CFG_OFFSET_DATA32    0
+#define QFW_CFG_OFFSET_DATA64    0
+#define QFW_CFG_OFFSET_SELECTOR  8     /* Selector Register address: Base + 8. 
*/
+#define QFW_CFG_OFFSET_DMA64     16    /* DMA Address address: Base + 16 (8 
bytes). */
+#define QFW_CFG_OFFSET_DMA32     20    /* DMA Address address: Base + 16 (8 
bytes). */
+
+
+#define fourcc_code(a, b, c, d) ((u32)(a) | ((u32)(b) << 8) | \
+               ((u32)(c) << 16) | ((u32)(d) << 24))
+
+#define DRM_FORMAT_RGB565       fourcc_code('R', 'G', '1', '6') /* [15:0] 
R:G:B 5:6:5 little endian */
+#define DRM_FORMAT_RGB888       fourcc_code('R', 'G', '2', '4') /* [23:0] 
R:G:B little endian */
+#define DRM_FORMAT_XRGB8888     fourcc_code('X', 'R', '2', '4') /* [31:0] 
x:R:G:B 8:8:8:8 little endian */
+
+static struct fb_ops ramfb_ops;
+
+struct ramfb {
+       struct fb_info info;
+       struct fb_videomode mode;
+};
+
+
+struct ramfb_mode {
+       const char *format;
+       u32 drm_format;
+       u32 bpp;
+       struct fb_bitfield red;
+       struct fb_bitfield green;
+       struct fb_bitfield blue;
+       struct fb_bitfield transp;
+};
+
+
+struct qfw_cfg_etc_ramfb {
+       u64 addr;
+       u32 fourcc;
+       u32 flags;
+       u32 width;
+       u32 height;
+       u32 stride;
+} PACKED;
+
+
+struct qfw_cfg_file {
+       u32  size;
+       u16  select;
+       u16  reserved;
+       char name[56];
+} PACKED;
+
+
+typedef struct qfw_cfg_dma {
+       u32 control;
+       u32 length;
+       u64 address;
+} PACKED qfw_cfg_dma;
+
+
+static const struct ramfb_mode fb_mode = {
+#if defined(CONFIG_DRIVER_VIDEO_RAMFB_FORMAT_RGB565)
+       .format = "r5g6b5",
+       .drm_format = DRM_FORMAT_RGB565,
+       .bpp    = 16,
+       .red    = { .length = 5, .offset = 11 },
+       .green  = { .length = 6, .offset = 5 },
+       .blue   = { .length = 5, .offset = 0 },
+       .transp = { .length = 0, .offset = 0 },
+#elif defined(CONFIG_DRIVER_VIDEO_RAMFB_FORMAT_RGB888)
+       .format = "r8g8b8",
+       .drm_format = DRM_FORMAT_RGB888,
+       .bpp    = 24,
+       .red    = { .length = 8, .offset = 16 },
+       .green  = { .length = 8, .offset = 8 },
+       .blue   = { .length = 8, .offset = 0 },
+       .transp = { .length = 0, .offset = 0 },
+#elif defined(CONFIG_DRIVER_VIDEO_RAMFB_FORMAT_XRGB8888)
+       .format = "x8r8g8b8",
+       .drm_format = DRM_FORMAT_XRGB8888,
+       .bpp    = 32,
+       .red    = { .length = 8, .offset = 16 },
+       .green  = { .length = 8, .offset = 8 },
+       .blue   = { .length = 8, .offset = 0 },
+       .transp = { .length = 0, .offset = 24 },
+#endif
+};
+
+
+static void qfw_cfg_read_entry(void __iomem *fw_cfg_base, void*address, u16 
entry, u32 size)
+{
+       /*
+        * writing QFW_CFG_INVALID will cause read operation to resume at last
+        * offset, otherwise read will start at offset 0
+        *
+        * Note: on platform where the control register is MMIO, the register
+        * is big endian.
+        */
+       if (entry != QFW_CFG_INVALID)
+               __raw_writew(cpu_to_be16(entry), fw_cfg_base + 
QFW_CFG_OFFSET_SELECTOR);
+
+#ifdef CONFIG_64BIT
+       while (size >= 8) {
+               *(u64*)address = __raw_readq(fw_cfg_base + 
QFW_CFG_OFFSET_DATA64);
+               address += 8;
+               size -= 8;
+       }
+#endif
+       while (size >= 4) {
+               *(u32*)address = __raw_readl(fw_cfg_base + 
QFW_CFG_OFFSET_DATA32);
+               address += 4;
+               size -= 4;
+       }
+       while (size >= 2) {
+               *(u16*)address = __raw_readw(fw_cfg_base + 
QFW_CFG_OFFSET_DATA16);
+               address += 2;
+               size -= 2;
+       }
+       while (size >= 1) {
+               *(u8*)address = __raw_readb(fw_cfg_base + QFW_CFG_OFFSET_DATA8);
+               address += 1;
+               size -= 1;
+       }
+}
+
+
+static void qfw_cfg_write_entry(void __iomem *fw_cfg_base, void*address, u16 
entry, u32 size)
+{
+       qfw_cfg_dma acc;
+
+       acc.address = cpu_to_be64((uintptr_t)address);
+       acc.control = cpu_to_be32(QFW_CFG_DMA_CTL_WRITE);
+       acc.length = cpu_to_be32(size);
+       if (entry != QFW_CFG_INVALID)
+               acc.control = cpu_to_be32(QFW_CFG_DMA_CTL_WRITE | 
QFW_CFG_DMA_CTL_SELECT | (entry << 16));
+
+#ifdef CONFIG_64BIT
+       __raw_writeq(cpu_to_be64((uintptr_t)&acc), fw_cfg_base + 
QFW_CFG_OFFSET_DMA64);
+#else
+       __raw_writel(cpu_to_be32((uintptr_t)&acc), fw_cfg_base + 
QFW_CFG_OFFSET_DMA32);
+#endif
+
+       barrier();
+
+       while (be32_to_cpu(acc.control) & ~QFW_CFG_DMA_CTL_ERROR);
+}
+
+
+static int qfw_cfg_find_file(void __iomem *fw_cfg_base, const char *filename)
+{
+       u32 count, e, select;
+
+       qfw_cfg_read_entry(fw_cfg_base, &count, QFW_CFG_FILE_DIR, 
sizeof(count));
+       count = be32_to_cpu(count);
+       for (select = 0, e = 0; e < count; e++) {
+               struct qfw_cfg_file qfile;
+               qfw_cfg_read_entry(fw_cfg_base, &qfile, QFW_CFG_INVALID, 
sizeof(qfile));
+               if (memcmp(qfile.name, filename, 10) == 0)
+               {
+                       select = be16_to_cpu(qfile.select);
+                       break;
+               }
+       }
+       return select;
+}
+
+
+static int ramfb_alloc(void __iomem *fw_cfg_base, struct fb_info *fbi)
+{
+       u32 select;
+       struct qfw_cfg_etc_ramfb etc_ramfb;
+
+       select = qfw_cfg_find_file(fw_cfg_base, "etc/ramfb");
+       if (select == 0) {
+               dev_err(&fbi->dev, "ramfb: fw_cfg (etc/ramfb) file not 
found\n");
+               return -1;
+       }
+       dev_info(&fbi->dev, "ramfb: fw_cfg (etc/ramfb) file at slot 0x%x\n", 
select);
+
+       fbi->screen_size = CONFIG_DRIVER_VIDEO_RAMFB_WIDTH * 
CONFIG_DRIVER_VIDEO_RAMFB_HEIGHT * fbi->bits_per_pixel;
+       fbi->screen_base = (void *)xzalloc(fbi->screen_size);
+
+       if (!fbi->screen_base) {
+               dev_err(&fbi->dev, "Unable to use FB\n");
+               return -1;
+       }
+
+       etc_ramfb.addr = cpu_to_be64((uintptr_t)fbi->screen_base),
+       etc_ramfb.fourcc = cpu_to_be32(fb_mode.drm_format),
+       etc_ramfb.flags  = cpu_to_be32(0),
+       etc_ramfb.width  = cpu_to_be32(fbi->xres),
+       etc_ramfb.height = cpu_to_be32(fbi->yres),
+       etc_ramfb.stride = cpu_to_be32(fbi->line_length),
+       qfw_cfg_write_entry(fw_cfg_base, &etc_ramfb, select, sizeof(etc_ramfb));
+
+       return 0;
+}
+
+
+static int ramfb_probe(struct device_d *dev)
+{
+       int ret;
+       struct ramfb *ramfb;
+       struct fb_info *fbi;
+       struct resource *fw_cfg_res;
+       void __iomem *fw_cfg_base;              /* base address of the 
registers */
+
+       ret = -ENODEV;
+
+       fw_cfg_res = dev_request_mem_resource(dev, 0);
+       if (IS_ERR(fw_cfg_res)) {
+               dev_err(dev, "No memory resource\n");
+               return PTR_ERR(fw_cfg_res);
+       }
+
+       ramfb = xzalloc(sizeof(*ramfb));
+
+       fw_cfg_base = IOMEM(fw_cfg_res->start);
+       if (!fw_cfg_base)
+               return ret;
+
+       ramfb->mode.name = fb_mode.format;
+       ramfb->mode.xres = CONFIG_DRIVER_VIDEO_RAMFB_WIDTH;
+       ramfb->mode.yres = CONFIG_DRIVER_VIDEO_RAMFB_HEIGHT;
+
+       fbi = &ramfb->info;
+       fbi->mode = &ramfb->mode;
+
+       fbi->bits_per_pixel = fb_mode.bpp;
+       fbi->red = fb_mode.red;
+       fbi->green = fb_mode.green;
+       fbi->blue = fb_mode.blue;
+       fbi->transp = fb_mode.transp;
+       fbi->xres = ramfb->mode.xres;
+       fbi->yres = ramfb->mode.yres;
+       /* this field is calculated by register_framebuffer()
+        * but register_framebuffer() can't be called before ramfb_alloc()
+        * so set line_length to zero.
+        */
+       fbi->line_length = 0;
+       fbi->fbops = &ramfb_ops;
+
+       fbi->dev.parent = dev;
+
+       ret = ramfb_alloc(fw_cfg_base, fbi);
+       if (ret < 0) {
+               dev_err(dev, "Unable to allocate ramfb: %d\n", ret);
+               return ret;
+       }
+
+       ret = register_framebuffer(fbi);
+       if (ret < 0) {
+               dev_err(dev, "Unable to register ramfb: %d\n", ret);
+               return ret;
+       }
+
+       dev_info(dev, "size %s registered\n", 
size_human_readable(fbi->screen_size));
+
+       return 0;
+}
+
+
+static const struct of_device_id ramfb_of_match[] = {
+       { .compatible = "qemu,fw-cfg-mmio", },
+       { },
+};
+
+
+static struct driver_d ramfb_driver = {
+       .name = "ramfb-framebuffer",
+       .of_compatible = ramfb_of_match,
+       .probe = ramfb_probe,
+};
+device_platform_driver(ramfb_driver);
+
+MODULE_AUTHOR("Adrian Negreanu <[email protected]>");
+MODULE_DESCRIPTION("QEMU RamFB driver");
+MODULE_LICENSE("GPL v2");
-- 
2.34.1


Reply via email to