summaryrefslogtreecommitdiffstats
path: root/drivers/video/bochs/bochs_hw.c
blob: debdd36941f4fc924882b328f04ab44e34144679 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// 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;

	fb->dev.parent = dev;
	return register_framebuffer(fb);
}