[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