summaryrefslogtreecommitdiffstats
path: root/drivers/video/bochs/bochs_hw.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/bochs/bochs_hw.c')
-rw-r--r--drivers/video/bochs/bochs_hw.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/drivers/video/bochs/bochs_hw.c b/drivers/video/bochs/bochs_hw.c
new file mode 100644
index 0000000000..252350aebb
--- /dev/null
+++ b/drivers/video/bochs/bochs_hw.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2020 Ahmad Fatoum, Pengutronix
+/*
+ * Driver for VGA with the Bochs VBE / QEMU stdvga extensions.
+ *
+ * Based on the Linux v5.11-rc1 bochs-dispi DRM driver.
+ */
+
+#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 -EILSEQ;
+ }
+
+ 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);
+}