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