All SVGA devices in Qemu, except for Cirrus, support the bochs display interface, which is very easy to set up. Add a driver for it. This has been tested with: PCI: qemu-system-mips(el) -device VGA + barebox qemu-malta_defconfig ISA: qemu-system-x86_64 -device isa-vga + barebox efi_defconfig Signed-off-by: Ahmad Fatoum <ahmad@xxxxxx> --- drivers/video/Kconfig | 2 + drivers/video/Makefile | 2 +- drivers/video/bochs/Kconfig | 22 ++++ drivers/video/bochs/Makefile | 3 + drivers/video/bochs/bochs_hw.c | 203 ++++++++++++++++++++++++++++++++ drivers/video/bochs/bochs_hw.h | 13 ++ drivers/video/bochs/bochs_isa.c | 31 +++++ drivers/video/bochs/bochs_pci.c | 37 ++++++ 8 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 drivers/video/bochs/Kconfig create mode 100644 drivers/video/bochs/Makefile create mode 100644 drivers/video/bochs/bochs_hw.c create mode 100644 drivers/video/bochs/bochs_hw.h create mode 100644 drivers/video/bochs/bochs_isa.c create mode 100644 drivers/video/bochs/bochs_pci.c diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 56d009529ea4..9ec6ea4248c1 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -98,6 +98,8 @@ config DRIVER_VIDEO_BCM283X source "drivers/video/imx-ipu-v3/Kconfig" +source "drivers/video/bochs/Kconfig" + config DRIVER_VIDEO_SIMPLEFB bool "Simple framebuffer support" depends on OFTREE diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 01fabe880920..28d0fe205b83 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -24,4 +24,4 @@ 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 obj-$(CONFIG_BACKLIGHT_RAVE_SP) += rave-sp-backlight.o - +obj-$(CONFIG_DRIVER_VIDEO_BOCHS) += bochs/ diff --git a/drivers/video/bochs/Kconfig b/drivers/video/bochs/Kconfig new file mode 100644 index 000000000000..e23e68f1126c --- /dev/null +++ b/drivers/video/bochs/Kconfig @@ -0,0 +1,22 @@ +config DRIVER_VIDEO_BOCHS + select DRIVER_VIDEO_EDID + bool + +config DRIVER_VIDEO_BOCHS_PCI + bool "bochs dispi / QEMU standard VGA PCI driver" + select DRIVER_VIDEO_BOCHS + depends on PCI + help + Say yes here if you have a PCI VGA display controller that + implements the bochs dispi VBE extension. This is a very simple + interface to drive the graphical output of virtual machines + like bochs, VirtualBox and Qemu (-device VGA). + +config DRIVER_VIDEO_BOCHS_ISA + bool "bochs dispi / QEMU standard VGA ISA driver" + select DRIVER_VIDEO_BOCHS + help + Say yes here if you have a ISA (I/O ports) VGA display controller that + implements the bochs dispi VBE extension. This is a very simple + interface to drive the graphical output of virtual machines + like bochs, VirtualBox and Qemu (-device isa-vga). diff --git a/drivers/video/bochs/Makefile b/drivers/video/bochs/Makefile new file mode 100644 index 000000000000..78ec1fe0ca81 --- /dev/null +++ b/drivers/video/bochs/Makefile @@ -0,0 +1,3 @@ +obj-y += bochs_hw.o +obj-$(CONFIG_DRIVER_VIDEO_BOCHS_PCI) += bochs_pci.o +obj-$(CONFIG_DRIVER_VIDEO_BOCHS_ISA) += bochs_isa.o diff --git a/drivers/video/bochs/bochs_hw.c b/drivers/video/bochs/bochs_hw.c new file mode 100644 index 000000000000..4f908a60318d --- /dev/null +++ b/drivers/video/bochs/bochs_hw.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright (c) 2020 Ahmad Fatoum, Pengutronix +/* + * PCI Driver for VGA with the Bochs VBE / QEMU stdvga extensions. + */ + +#include <common.h> +#include <driver.h> +#include <linux/pci.h> +#include <fb.h> +#include "../edid.h" +#include "bochs_hw.h" + +#define VBE_DISPI_INDEX_ID 0x0 +#define VBE_DISPI_INDEX_XRES 0x1 +#define VBE_DISPI_INDEX_YRES 0x2 +#define VBE_DISPI_INDEX_BPP 0x3 +#define VBE_DISPI_INDEX_ENABLE 0x4 +#define VBE_DISPI_INDEX_BANK 0x5 +#define VBE_DISPI_INDEX_VIRT_WIDTH 0x6 +#define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7 +#define VBE_DISPI_INDEX_X_OFFSET 0x8 +#define VBE_DISPI_INDEX_Y_OFFSET 0x9 +#define VBE_DISPI_INDEX_VIDEO_MEMORY_64K 0xa + +#define VBE_DISPI_ENABLED 0x01 +#define VBE_DISPI_GETCAPS 0x02 +#define VBE_DISPI_8BIT_DAC 0x20 +#define VBE_DISPI_LFB_ENABLED 0x40 +#define VBE_DISPI_NOCLEARMEM 0x80 + +/* Offsets for accessing ioports via PCI BAR1 (MMIO) */ +#define VGA_MMIO_OFFSET (0x400 - 0x3c0) +#define VBE_MMIO_OFFSET 0x500 + +struct bochs { + struct fb_info fb; + void __iomem *fb_map; + void __iomem *mmio; +}; + +static void bochs_vga_writeb(struct bochs *bochs, u16 ioport, u8 val) +{ + if (WARN_ON(ioport < 0x3c0 || ioport > 0x3df)) + return; + + if (bochs->mmio) { + int offset = ioport + VGA_MMIO_OFFSET; + writeb(val, bochs->mmio + offset); + } else { + outb(val, ioport); + } +} + +static u16 bochs_dispi_read(struct bochs *bochs, u16 reg) +{ + u16 ret = 0; + + if (bochs->mmio) { + int offset = VBE_MMIO_OFFSET + (reg << 1); + ret = readw(bochs->mmio + offset); + } else { + outw(reg, VBE_DISPI_IOPORT_INDEX); + ret = inw(VBE_DISPI_IOPORT_DATA); + } + return ret; +} + +static void bochs_dispi_write(struct bochs *bochs, u16 reg, u16 val) +{ + if (bochs->mmio) { + int offset = VBE_MMIO_OFFSET + (reg << 1); + writew(val, bochs->mmio + offset); + } else { + outw(reg, VBE_DISPI_IOPORT_INDEX); + outw(val, VBE_DISPI_IOPORT_DATA); + } +} + +static void bochs_fb_enable(struct fb_info *fb) +{ + struct bochs *bochs = fb->priv; + + bochs_vga_writeb(bochs, 0x3c0, 0x20); /* unblank */ + + bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE, 0); + + bochs_dispi_write(bochs, VBE_DISPI_INDEX_BPP, fb->bits_per_pixel); + bochs_dispi_write(bochs, VBE_DISPI_INDEX_XRES, fb->xres); + bochs_dispi_write(bochs, VBE_DISPI_INDEX_YRES, fb->yres); + bochs_dispi_write(bochs, VBE_DISPI_INDEX_BANK, 0); + bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_WIDTH, fb->xres); + bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_HEIGHT, fb->yres); + bochs_dispi_write(bochs, VBE_DISPI_INDEX_X_OFFSET, 0); + bochs_dispi_write(bochs, VBE_DISPI_INDEX_Y_OFFSET, 0); + + bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE, + VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED ); +} + +static void bochs_fb_disable(struct fb_info *fb) +{ + struct bochs *bochs = fb->priv; + + bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE, + bochs_dispi_read(bochs, VBE_DISPI_INDEX_ENABLE) & + ~VBE_DISPI_ENABLED); +} + +static struct fb_ops bochs_fb_ops = { + .fb_enable = bochs_fb_enable, + .fb_disable = bochs_fb_disable, +}; + +static int bochs_hw_load_edid(struct bochs *bochs) +{ + u8 *edid; + int i; + + edid = xzalloc(EDID_LENGTH); + + for (i = 0; i <= EDID_HEADER_END; i++) + edid[i] = readb(bochs->mmio + i); + + /* check header to detect whenever edid support is enabled in qemu */ + if (!edid_check_header(edid)) { + free(edid); + return -1; + } + + for (i = EDID_HEADER_END + 1; i < EDID_LENGTH; i++) + edid[i] = readb(bochs->mmio + i); + + bochs->fb.edid_data = edid; + return 0; +} + +static int bochs_hw_read_version(struct bochs *bochs) +{ + u16 ver; + + ver = bochs_dispi_read(bochs, VBE_DISPI_INDEX_ID); + + if ((ver & 0xB0C0) != 0xB0C0) + return -ENODEV; + + return ver & 0xF; +} + +int bochs_hw_probe(struct device_d *dev, void __iomem *fb_map, void __iomem *mmio) +{ + struct bochs *bochs; + struct fb_info *fb; + int ret; + + bochs = xzalloc(sizeof(*bochs)); + + bochs->fb_map = IOMEM(fb_map); + bochs->mmio = IOMEM(mmio); + + ret = bochs_hw_read_version(bochs); + if (ret < 0) { + free(bochs); + return ret; + } + + dev_info(dev, "detected bochs dispi v%u\n", ret); + + fb = &bochs->fb; + fb->screen_base = bochs->fb_map; + + fb->bits_per_pixel = 16; + fb->red.length = 5; + fb->green.length = 6; + fb->blue.length = 5; + fb->red.offset = 11; + fb->green.offset = 5; + fb->blue.offset = 0; + + /* EDID is only exposed over PCI */ + ret = -ENODEV; + + if (mmio) { + ret = bochs_hw_load_edid(bochs); + if (ret) + dev_warn(dev, "couldn't read EDID information\n"); + } + + if (ret) { + fb->mode = xzalloc(sizeof(*fb->mode)); + fb->modes.modes = fb->mode; + fb->modes.num_modes = 1; + + fb->mode->name = "640x480"; + fb->mode->xres = 640; + fb->mode->yres = 480; + } + + fb->priv = bochs; + fb->fbops = &bochs_fb_ops; + + return register_framebuffer(fb); +} diff --git a/drivers/video/bochs/bochs_hw.h b/drivers/video/bochs/bochs_hw.h new file mode 100644 index 000000000000..36c2cc1cc376 --- /dev/null +++ b/drivers/video/bochs/bochs_hw.h @@ -0,0 +1,13 @@ +#ifndef BOCHS_HW_H +#define BOCHS_HW_H + +#include <linux/compiler.h> + +#define VBE_DISPI_IOPORT_INDEX 0x01CE +#define VBE_DISPI_IOPORT_DATA 0x01CF + +struct device_d; + +int bochs_hw_probe(struct device_d *dev, void *fb_map, void __iomem *mmio); + +#endif diff --git a/drivers/video/bochs/bochs_isa.c b/drivers/video/bochs/bochs_isa.c new file mode 100644 index 000000000000..7f75803baa0c --- /dev/null +++ b/drivers/video/bochs/bochs_isa.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright (c) 2020 Ahmad Fatoum, Pengutronix +/* + * ISA Driver for VGA with the Bochs VBE / QEMU stdvga extensions. + */ + +#include <common.h> +#include <driver.h> +#include <linux/ioport.h> +#include "bochs_hw.h" + +static int bochs_isa_detect(void) +{ + struct device_d *dev; + int ret; + + outw(0, VBE_DISPI_IOPORT_INDEX); + ret = inw(VBE_DISPI_IOPORT_DATA); + + if ((ret & 0xB0C0) != 0xB0C0) + return -ENODEV; + + dev = device_alloc("bochs-dispi", 0); + + ret = platform_device_register(dev); + if (ret) + return ret; + + return bochs_hw_probe(dev, (void *)0xe0000000, NULL); +} +device_initcall(bochs_isa_detect); diff --git a/drivers/video/bochs/bochs_pci.c b/drivers/video/bochs/bochs_pci.c new file mode 100644 index 000000000000..39f582029d35 --- /dev/null +++ b/drivers/video/bochs/bochs_pci.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright (c) 2020 Ahmad Fatoum, Pengutronix +/* + * PCI Driver for VGA with the Bochs VBE / QEMU stdvga extensions. + */ + +#include <common.h> +#include <driver.h> +#include <linux/pci.h> +#include "bochs_hw.h" + +static int bochs_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + void __iomem *fb_map, *mmio; + int ret; + + ret = pci_enable_device(pdev); + if (ret) + return ret; + + fb_map = pci_iomap(pdev, 0); + mmio = pci_iomap(pdev, 2); + + return bochs_hw_probe(&pdev->dev, fb_map, mmio); +} + +static DEFINE_PCI_DEVICE_TABLE(bochs_pci_tbl) = { + /* https://github.com/qemu/qemu/blob/master/docs/specs/standard-vga.txt */ + { PCI_DEVICE(0x1234, 0x1111) }, +}; + +static struct pci_driver bochs_pci_driver = { + .name = "bochs-dispi", + .probe = bochs_pci_probe, + .id_table = bochs_pci_tbl, +}; +device_pci_driver(bochs_pci_driver); -- 2.29.2 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox