Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
0a6659bd | 2 | /* |
0a6659bd GH |
3 | */ |
4 | ||
016f363e SR |
5 | #include <linux/pci.h> |
6 | ||
7 | #include <drm/drm_fourcc.h> | |
8 | ||
0a6659bd GH |
9 | #include "bochs.h" |
10 | ||
11 | /* ---------------------------------------------------------------------- */ | |
12 | ||
13 | static void bochs_vga_writeb(struct bochs_device *bochs, u16 ioport, u8 val) | |
14 | { | |
15 | if (WARN_ON(ioport < 0x3c0 || ioport > 0x3df)) | |
16 | return; | |
17 | ||
18 | if (bochs->mmio) { | |
19 | int offset = ioport - 0x3c0 + 0x400; | |
20 | writeb(val, bochs->mmio + offset); | |
21 | } else { | |
22 | outb(val, ioport); | |
23 | } | |
24 | } | |
25 | ||
26 | static u16 bochs_dispi_read(struct bochs_device *bochs, u16 reg) | |
27 | { | |
28 | u16 ret = 0; | |
29 | ||
30 | if (bochs->mmio) { | |
31 | int offset = 0x500 + (reg << 1); | |
32 | ret = readw(bochs->mmio + offset); | |
33 | } else { | |
34 | outw(reg, VBE_DISPI_IOPORT_INDEX); | |
35 | ret = inw(VBE_DISPI_IOPORT_DATA); | |
36 | } | |
37 | return ret; | |
38 | } | |
39 | ||
40 | static void bochs_dispi_write(struct bochs_device *bochs, u16 reg, u16 val) | |
41 | { | |
42 | if (bochs->mmio) { | |
43 | int offset = 0x500 + (reg << 1); | |
44 | writew(val, bochs->mmio + offset); | |
45 | } else { | |
46 | outw(reg, VBE_DISPI_IOPORT_INDEX); | |
47 | outw(val, VBE_DISPI_IOPORT_DATA); | |
48 | } | |
49 | } | |
50 | ||
86351de0 GH |
51 | static void bochs_hw_set_big_endian(struct bochs_device *bochs) |
52 | { | |
53 | if (bochs->qext_size < 8) | |
54 | return; | |
55 | ||
56 | writel(0xbebebebe, bochs->mmio + 0x604); | |
57 | } | |
58 | ||
59 | static void bochs_hw_set_little_endian(struct bochs_device *bochs) | |
60 | { | |
61 | if (bochs->qext_size < 8) | |
62 | return; | |
63 | ||
64 | writel(0x1e1e1e1e, bochs->mmio + 0x604); | |
65 | } | |
66 | ||
67 | #ifdef __BIG_ENDIAN | |
68 | #define bochs_hw_set_native_endian(_b) bochs_hw_set_big_endian(_b) | |
69 | #else | |
70 | #define bochs_hw_set_native_endian(_b) bochs_hw_set_little_endian(_b) | |
71 | #endif | |
72 | ||
01f23459 GH |
73 | static int bochs_get_edid_block(void *data, u8 *buf, |
74 | unsigned int block, size_t len) | |
75 | { | |
76 | struct bochs_device *bochs = data; | |
77 | size_t i, start = block * EDID_LENGTH; | |
78 | ||
79 | if (start + len > 0x400 /* vga register offset */) | |
80 | return -1; | |
81 | ||
82 | for (i = 0; i < len; i++) { | |
83 | buf[i] = readb(bochs->mmio + start + i); | |
84 | } | |
85 | return 0; | |
86 | } | |
87 | ||
88 | int bochs_hw_load_edid(struct bochs_device *bochs) | |
89 | { | |
70bce993 GH |
90 | u8 header[8]; |
91 | ||
01f23459 GH |
92 | if (!bochs->mmio) |
93 | return -1; | |
94 | ||
70bce993 GH |
95 | /* check header to detect whenever edid support is enabled in qemu */ |
96 | bochs_get_edid_block(bochs, header, 0, ARRAY_SIZE(header)); | |
97 | if (drm_edid_header_is_valid(header) != 8) | |
98 | return -1; | |
99 | ||
01f23459 GH |
100 | kfree(bochs->edid); |
101 | bochs->edid = drm_do_get_edid(&bochs->connector, | |
102 | bochs_get_edid_block, bochs); | |
103 | if (bochs->edid == NULL) | |
104 | return -1; | |
105 | ||
106 | return 0; | |
107 | } | |
108 | ||
7780eb9c | 109 | int bochs_hw_init(struct drm_device *dev) |
0a6659bd GH |
110 | { |
111 | struct bochs_device *bochs = dev->dev_private; | |
112 | struct pci_dev *pdev = dev->pdev; | |
86351de0 | 113 | unsigned long addr, size, mem, ioaddr, iosize; |
0a6659bd GH |
114 | u16 id; |
115 | ||
fbd2f9fe | 116 | if (pdev->resource[2].flags & IORESOURCE_MEM) { |
0a6659bd GH |
117 | /* mmio bar with vga and bochs registers present */ |
118 | if (pci_request_region(pdev, 2, "bochs-drm") != 0) { | |
119 | DRM_ERROR("Cannot request mmio region\n"); | |
120 | return -EBUSY; | |
121 | } | |
122 | ioaddr = pci_resource_start(pdev, 2); | |
123 | iosize = pci_resource_len(pdev, 2); | |
124 | bochs->mmio = ioremap(ioaddr, iosize); | |
125 | if (bochs->mmio == NULL) { | |
126 | DRM_ERROR("Cannot map mmio region\n"); | |
127 | return -ENOMEM; | |
128 | } | |
129 | } else { | |
130 | ioaddr = VBE_DISPI_IOPORT_INDEX; | |
131 | iosize = 2; | |
132 | if (!request_region(ioaddr, iosize, "bochs-drm")) { | |
133 | DRM_ERROR("Cannot request ioports\n"); | |
134 | return -EBUSY; | |
135 | } | |
136 | bochs->ioports = 1; | |
137 | } | |
138 | ||
139 | id = bochs_dispi_read(bochs, VBE_DISPI_INDEX_ID); | |
140 | mem = bochs_dispi_read(bochs, VBE_DISPI_INDEX_VIDEO_MEMORY_64K) | |
141 | * 64 * 1024; | |
142 | if ((id & 0xfff0) != VBE_DISPI_ID0) { | |
143 | DRM_ERROR("ID mismatch\n"); | |
144 | return -ENODEV; | |
145 | } | |
146 | ||
147 | if ((pdev->resource[0].flags & IORESOURCE_MEM) == 0) | |
148 | return -ENODEV; | |
149 | addr = pci_resource_start(pdev, 0); | |
150 | size = pci_resource_len(pdev, 0); | |
151 | if (addr == 0) | |
152 | return -ENODEV; | |
153 | if (size != mem) { | |
154 | DRM_ERROR("Size mismatch: pci=%ld, bochs=%ld\n", | |
155 | size, mem); | |
156 | size = min(size, mem); | |
157 | } | |
158 | ||
8c34cd1a GH |
159 | if (pci_request_region(pdev, 0, "bochs-drm") != 0) |
160 | DRM_WARN("Cannot request framebuffer, boot fb still active?\n"); | |
0a6659bd GH |
161 | |
162 | bochs->fb_map = ioremap(addr, size); | |
163 | if (bochs->fb_map == NULL) { | |
164 | DRM_ERROR("Cannot map framebuffer\n"); | |
165 | return -ENOMEM; | |
166 | } | |
167 | bochs->fb_base = addr; | |
168 | bochs->fb_size = size; | |
169 | ||
170 | DRM_INFO("Found bochs VGA, ID 0x%x.\n", id); | |
171 | DRM_INFO("Framebuffer size %ld kB @ 0x%lx, %s @ 0x%lx.\n", | |
172 | size / 1024, addr, | |
173 | bochs->ioports ? "ioports" : "mmio", | |
174 | ioaddr); | |
9ecdb039 GH |
175 | |
176 | if (bochs->mmio && pdev->revision >= 2) { | |
86351de0 GH |
177 | bochs->qext_size = readl(bochs->mmio + 0x600); |
178 | if (bochs->qext_size < 4 || bochs->qext_size > iosize) { | |
179 | bochs->qext_size = 0; | |
9ecdb039 | 180 | goto noext; |
9ecdb039 | 181 | } |
86351de0 GH |
182 | DRM_DEBUG("Found qemu ext regs, size %ld\n", |
183 | bochs->qext_size); | |
184 | bochs_hw_set_native_endian(bochs); | |
9ecdb039 GH |
185 | } |
186 | ||
187 | noext: | |
0a6659bd GH |
188 | return 0; |
189 | } | |
190 | ||
191 | void bochs_hw_fini(struct drm_device *dev) | |
192 | { | |
193 | struct bochs_device *bochs = dev->dev_private; | |
194 | ||
195 | if (bochs->mmio) | |
196 | iounmap(bochs->mmio); | |
197 | if (bochs->ioports) | |
198 | release_region(VBE_DISPI_IOPORT_INDEX, 2); | |
199 | if (bochs->fb_map) | |
200 | iounmap(bochs->fb_map); | |
201 | pci_release_regions(dev->pdev); | |
01f23459 | 202 | kfree(bochs->edid); |
0a6659bd GH |
203 | } |
204 | ||
205 | void bochs_hw_setmode(struct bochs_device *bochs, | |
472fde88 | 206 | struct drm_display_mode *mode) |
0a6659bd GH |
207 | { |
208 | bochs->xres = mode->hdisplay; | |
209 | bochs->yres = mode->vdisplay; | |
210 | bochs->bpp = 32; | |
211 | bochs->stride = mode->hdisplay * (bochs->bpp / 8); | |
212 | bochs->yres_virtual = bochs->fb_size / bochs->stride; | |
213 | ||
472fde88 | 214 | DRM_DEBUG_DRIVER("%dx%d @ %d bpp, vy %d\n", |
0a6659bd GH |
215 | bochs->xres, bochs->yres, bochs->bpp, |
216 | bochs->yres_virtual); | |
217 | ||
218 | bochs_vga_writeb(bochs, 0x3c0, 0x20); /* unblank */ | |
219 | ||
564b687b | 220 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE, 0); |
0a6659bd GH |
221 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_BPP, bochs->bpp); |
222 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_XRES, bochs->xres); | |
223 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_YRES, bochs->yres); | |
224 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_BANK, 0); | |
225 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_WIDTH, bochs->xres); | |
226 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_HEIGHT, | |
227 | bochs->yres_virtual); | |
228 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_X_OFFSET, 0); | |
229 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_Y_OFFSET, 0); | |
230 | ||
231 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE, | |
232 | VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED); | |
472fde88 GH |
233 | } |
234 | ||
235 | void bochs_hw_setformat(struct bochs_device *bochs, | |
236 | const struct drm_format_info *format) | |
237 | { | |
238 | DRM_DEBUG_DRIVER("format %c%c%c%c\n", | |
239 | (format->format >> 0) & 0xff, | |
240 | (format->format >> 8) & 0xff, | |
241 | (format->format >> 16) & 0xff, | |
242 | (format->format >> 24) & 0xff); | |
86351de0 GH |
243 | |
244 | switch (format->format) { | |
245 | case DRM_FORMAT_XRGB8888: | |
246 | bochs_hw_set_little_endian(bochs); | |
247 | break; | |
248 | case DRM_FORMAT_BGRX8888: | |
249 | bochs_hw_set_big_endian(bochs); | |
250 | break; | |
251 | default: | |
252 | /* should not happen */ | |
253 | DRM_ERROR("%s: Huh? Got framebuffer format 0x%x", | |
254 | __func__, format->format); | |
255 | break; | |
f2f7df4f | 256 | } |
0a6659bd GH |
257 | } |
258 | ||
259 | void bochs_hw_setbase(struct bochs_device *bochs, | |
dd2b5959 | 260 | int x, int y, int stride, u64 addr) |
0a6659bd | 261 | { |
dd2b5959 GH |
262 | unsigned long offset; |
263 | unsigned int vx, vy, vwidth; | |
264 | ||
265 | bochs->stride = stride; | |
266 | offset = (unsigned long)addr + | |
0a6659bd GH |
267 | y * bochs->stride + |
268 | x * (bochs->bpp / 8); | |
dd2b5959 GH |
269 | vy = offset / bochs->stride; |
270 | vx = (offset % bochs->stride) * 8 / bochs->bpp; | |
271 | vwidth = stride * 8 / bochs->bpp; | |
0a6659bd GH |
272 | |
273 | DRM_DEBUG_DRIVER("x %d, y %d, addr %llx -> offset %lx, vx %d, vy %d\n", | |
274 | x, y, addr, offset, vx, vy); | |
dd2b5959 | 275 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_WIDTH, vwidth); |
0a6659bd GH |
276 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_X_OFFSET, vx); |
277 | bochs_dispi_write(bochs, VBE_DISPI_INDEX_Y_OFFSET, vy); | |
278 | } |