1 | |
2 | /* |
3 | * QEMU model of the Milkymist VGA framebuffer. |
4 | * |
5 | * Copyright (c) 2010-2012 Michael Walle <michael@walle.cc> |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
19 | * |
20 | * |
21 | * Specification available at: |
22 | * http://milkymist.walle.cc/socdoc/vgafb.pdf |
23 | */ |
24 | |
25 | #include "qemu/osdep.h" |
26 | #include "hw/hw.h" |
27 | #include "hw/qdev-properties.h" |
28 | #include "hw/sysbus.h" |
29 | #include "trace.h" |
30 | #include "ui/console.h" |
31 | #include "framebuffer.h" |
32 | #include "ui/pixel_ops.h" |
33 | #include "qemu/error-report.h" |
34 | #include "qemu/module.h" |
35 | |
36 | #define BITS 8 |
37 | #include "migration/vmstate.h" |
38 | #include "milkymist-vgafb_template.h" |
39 | #define BITS 15 |
40 | #include "milkymist-vgafb_template.h" |
41 | #define BITS 16 |
42 | #include "milkymist-vgafb_template.h" |
43 | #define BITS 24 |
44 | #include "milkymist-vgafb_template.h" |
45 | #define BITS 32 |
46 | #include "milkymist-vgafb_template.h" |
47 | |
48 | enum { |
49 | R_CTRL = 0, |
50 | R_HRES, |
51 | R_HSYNC_START, |
52 | R_HSYNC_END, |
53 | R_HSCAN, |
54 | R_VRES, |
55 | R_VSYNC_START, |
56 | R_VSYNC_END, |
57 | R_VSCAN, |
58 | R_BASEADDRESS, |
59 | R_BASEADDRESS_ACT, |
60 | R_BURST_COUNT, |
61 | R_DDC, |
62 | R_SOURCE_CLOCK, |
63 | R_MAX |
64 | }; |
65 | |
66 | enum { |
67 | CTRL_RESET = (1<<0), |
68 | }; |
69 | |
70 | #define TYPE_MILKYMIST_VGAFB "milkymist-vgafb" |
71 | #define MILKYMIST_VGAFB(obj) \ |
72 | OBJECT_CHECK(MilkymistVgafbState, (obj), TYPE_MILKYMIST_VGAFB) |
73 | |
74 | struct MilkymistVgafbState { |
75 | SysBusDevice parent_obj; |
76 | |
77 | MemoryRegion regs_region; |
78 | MemoryRegionSection fbsection; |
79 | QemuConsole *con; |
80 | |
81 | int invalidate; |
82 | uint32_t fb_offset; |
83 | uint32_t fb_mask; |
84 | |
85 | uint32_t regs[R_MAX]; |
86 | }; |
87 | typedef struct MilkymistVgafbState MilkymistVgafbState; |
88 | |
89 | static int vgafb_enabled(MilkymistVgafbState *s) |
90 | { |
91 | return !(s->regs[R_CTRL] & CTRL_RESET); |
92 | } |
93 | |
94 | static void vgafb_update_display(void *opaque) |
95 | { |
96 | MilkymistVgafbState *s = opaque; |
97 | SysBusDevice *sbd; |
98 | DisplaySurface *surface = qemu_console_surface(s->con); |
99 | int src_width; |
100 | int first = 0; |
101 | int last = 0; |
102 | drawfn fn; |
103 | |
104 | if (!vgafb_enabled(s)) { |
105 | return; |
106 | } |
107 | |
108 | sbd = SYS_BUS_DEVICE(s); |
109 | int dest_width = s->regs[R_HRES]; |
110 | |
111 | switch (surface_bits_per_pixel(surface)) { |
112 | case 0: |
113 | return; |
114 | case 8: |
115 | fn = draw_line_8; |
116 | break; |
117 | case 15: |
118 | fn = draw_line_15; |
119 | dest_width *= 2; |
120 | break; |
121 | case 16: |
122 | fn = draw_line_16; |
123 | dest_width *= 2; |
124 | break; |
125 | case 24: |
126 | fn = draw_line_24; |
127 | dest_width *= 3; |
128 | break; |
129 | case 32: |
130 | fn = draw_line_32; |
131 | dest_width *= 4; |
132 | break; |
133 | default: |
134 | hw_error("milkymist_vgafb: bad color depth\n" ); |
135 | break; |
136 | } |
137 | |
138 | src_width = s->regs[R_HRES] * 2; |
139 | if (s->invalidate) { |
140 | framebuffer_update_memory_section(&s->fbsection, |
141 | sysbus_address_space(sbd), |
142 | s->regs[R_BASEADDRESS] + s->fb_offset, |
143 | s->regs[R_VRES], src_width); |
144 | } |
145 | |
146 | framebuffer_update_display(surface, &s->fbsection, |
147 | s->regs[R_HRES], |
148 | s->regs[R_VRES], |
149 | src_width, |
150 | dest_width, |
151 | 0, |
152 | s->invalidate, |
153 | fn, |
154 | NULL, |
155 | &first, &last); |
156 | |
157 | if (first >= 0) { |
158 | dpy_gfx_update(s->con, 0, first, s->regs[R_HRES], last - first + 1); |
159 | } |
160 | s->invalidate = 0; |
161 | } |
162 | |
163 | static void vgafb_invalidate_display(void *opaque) |
164 | { |
165 | MilkymistVgafbState *s = opaque; |
166 | s->invalidate = 1; |
167 | } |
168 | |
169 | static void vgafb_resize(MilkymistVgafbState *s) |
170 | { |
171 | if (!vgafb_enabled(s)) { |
172 | return; |
173 | } |
174 | |
175 | qemu_console_resize(s->con, s->regs[R_HRES], s->regs[R_VRES]); |
176 | s->invalidate = 1; |
177 | } |
178 | |
179 | static uint64_t vgafb_read(void *opaque, hwaddr addr, |
180 | unsigned size) |
181 | { |
182 | MilkymistVgafbState *s = opaque; |
183 | uint32_t r = 0; |
184 | |
185 | addr >>= 2; |
186 | switch (addr) { |
187 | case R_CTRL: |
188 | case R_HRES: |
189 | case R_HSYNC_START: |
190 | case R_HSYNC_END: |
191 | case R_HSCAN: |
192 | case R_VRES: |
193 | case R_VSYNC_START: |
194 | case R_VSYNC_END: |
195 | case R_VSCAN: |
196 | case R_BASEADDRESS: |
197 | case R_BURST_COUNT: |
198 | case R_DDC: |
199 | case R_SOURCE_CLOCK: |
200 | r = s->regs[addr]; |
201 | break; |
202 | case R_BASEADDRESS_ACT: |
203 | r = s->regs[R_BASEADDRESS]; |
204 | break; |
205 | |
206 | default: |
207 | error_report("milkymist_vgafb: read access to unknown register 0x" |
208 | TARGET_FMT_plx, addr << 2); |
209 | break; |
210 | } |
211 | |
212 | trace_milkymist_vgafb_memory_read(addr << 2, r); |
213 | |
214 | return r; |
215 | } |
216 | |
217 | static void vgafb_write(void *opaque, hwaddr addr, uint64_t value, |
218 | unsigned size) |
219 | { |
220 | MilkymistVgafbState *s = opaque; |
221 | |
222 | trace_milkymist_vgafb_memory_write(addr, value); |
223 | |
224 | addr >>= 2; |
225 | switch (addr) { |
226 | case R_CTRL: |
227 | s->regs[addr] = value; |
228 | vgafb_resize(s); |
229 | break; |
230 | case R_HSYNC_START: |
231 | case R_HSYNC_END: |
232 | case R_HSCAN: |
233 | case R_VSYNC_START: |
234 | case R_VSYNC_END: |
235 | case R_VSCAN: |
236 | case R_BURST_COUNT: |
237 | case R_DDC: |
238 | case R_SOURCE_CLOCK: |
239 | s->regs[addr] = value; |
240 | break; |
241 | case R_BASEADDRESS: |
242 | if (value & 0x1f) { |
243 | error_report("milkymist_vgafb: framebuffer base address have to " |
244 | "be 32 byte aligned" ); |
245 | break; |
246 | } |
247 | s->regs[addr] = value & s->fb_mask; |
248 | s->invalidate = 1; |
249 | break; |
250 | case R_HRES: |
251 | case R_VRES: |
252 | s->regs[addr] = value; |
253 | vgafb_resize(s); |
254 | break; |
255 | case R_BASEADDRESS_ACT: |
256 | error_report("milkymist_vgafb: write to read-only register 0x" |
257 | TARGET_FMT_plx, addr << 2); |
258 | break; |
259 | |
260 | default: |
261 | error_report("milkymist_vgafb: write access to unknown register 0x" |
262 | TARGET_FMT_plx, addr << 2); |
263 | break; |
264 | } |
265 | } |
266 | |
267 | static const MemoryRegionOps vgafb_mmio_ops = { |
268 | .read = vgafb_read, |
269 | .write = vgafb_write, |
270 | .valid = { |
271 | .min_access_size = 4, |
272 | .max_access_size = 4, |
273 | }, |
274 | .endianness = DEVICE_NATIVE_ENDIAN, |
275 | }; |
276 | |
277 | static void milkymist_vgafb_reset(DeviceState *d) |
278 | { |
279 | MilkymistVgafbState *s = MILKYMIST_VGAFB(d); |
280 | int i; |
281 | |
282 | for (i = 0; i < R_MAX; i++) { |
283 | s->regs[i] = 0; |
284 | } |
285 | |
286 | /* defaults */ |
287 | s->regs[R_CTRL] = CTRL_RESET; |
288 | s->regs[R_HRES] = 640; |
289 | s->regs[R_VRES] = 480; |
290 | s->regs[R_BASEADDRESS] = 0; |
291 | } |
292 | |
293 | static const GraphicHwOps vgafb_ops = { |
294 | .invalidate = vgafb_invalidate_display, |
295 | .gfx_update = vgafb_update_display, |
296 | }; |
297 | |
298 | static void milkymist_vgafb_init(Object *obj) |
299 | { |
300 | MilkymistVgafbState *s = MILKYMIST_VGAFB(obj); |
301 | SysBusDevice *dev = SYS_BUS_DEVICE(obj); |
302 | |
303 | memory_region_init_io(&s->regs_region, OBJECT(s), &vgafb_mmio_ops, s, |
304 | "milkymist-vgafb" , R_MAX * 4); |
305 | sysbus_init_mmio(dev, &s->regs_region); |
306 | } |
307 | |
308 | static void milkymist_vgafb_realize(DeviceState *dev, Error **errp) |
309 | { |
310 | MilkymistVgafbState *s = MILKYMIST_VGAFB(dev); |
311 | |
312 | s->con = graphic_console_init(dev, 0, &vgafb_ops, s); |
313 | } |
314 | |
315 | static int vgafb_post_load(void *opaque, int version_id) |
316 | { |
317 | vgafb_invalidate_display(opaque); |
318 | return 0; |
319 | } |
320 | |
321 | static const VMStateDescription vmstate_milkymist_vgafb = { |
322 | .name = "milkymist-vgafb" , |
323 | .version_id = 1, |
324 | .minimum_version_id = 1, |
325 | .post_load = vgafb_post_load, |
326 | .fields = (VMStateField[]) { |
327 | VMSTATE_UINT32_ARRAY(regs, MilkymistVgafbState, R_MAX), |
328 | VMSTATE_END_OF_LIST() |
329 | } |
330 | }; |
331 | |
332 | static Property milkymist_vgafb_properties[] = { |
333 | DEFINE_PROP_UINT32("fb_offset" , MilkymistVgafbState, fb_offset, 0x0), |
334 | DEFINE_PROP_UINT32("fb_mask" , MilkymistVgafbState, fb_mask, 0xffffffff), |
335 | DEFINE_PROP_END_OF_LIST(), |
336 | }; |
337 | |
338 | static void milkymist_vgafb_class_init(ObjectClass *klass, void *data) |
339 | { |
340 | DeviceClass *dc = DEVICE_CLASS(klass); |
341 | |
342 | dc->reset = milkymist_vgafb_reset; |
343 | dc->vmsd = &vmstate_milkymist_vgafb; |
344 | dc->props = milkymist_vgafb_properties; |
345 | dc->realize = milkymist_vgafb_realize; |
346 | } |
347 | |
348 | static const TypeInfo milkymist_vgafb_info = { |
349 | .name = TYPE_MILKYMIST_VGAFB, |
350 | .parent = TYPE_SYS_BUS_DEVICE, |
351 | .instance_size = sizeof(MilkymistVgafbState), |
352 | .instance_init = milkymist_vgafb_init, |
353 | .class_init = milkymist_vgafb_class_init, |
354 | }; |
355 | |
356 | static void milkymist_vgafb_register_types(void) |
357 | { |
358 | type_register_static(&milkymist_vgafb_info); |
359 | } |
360 | |
361 | type_init(milkymist_vgafb_register_types) |
362 | |