[PATCH] video: add support for qemu ramfb

Adrian Negreanu adrian.negreanu at nxp.com
Tue Oct 11 10:11:06 PDT 2022


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 <adrian.negreanu at nxp.com>
---
 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 <adrian.negreanu at nxp.com>");
+MODULE_DESCRIPTION("QEMU RamFB driver");
+MODULE_LICENSE("GPL v2");
-- 
2.34.1




More information about the barebox mailing list