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@xxxxxxx> --- 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..87181e51e2 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_X8R8G8B8 + 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@xxxxxxx>"); +MODULE_DESCRIPTION("QEMU RamFB driver"); +MODULE_LICENSE("GPL v2"); -- 2.34.1