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
207
|
// 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 *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);
}
|