diff options
Diffstat (limited to 'hw/milkymist-vgafb.c')
-rw-r--r-- | hw/milkymist-vgafb.c | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/hw/milkymist-vgafb.c b/hw/milkymist-vgafb.c new file mode 100644 index 0000000000..8922731511 --- /dev/null +++ b/hw/milkymist-vgafb.c @@ -0,0 +1,318 @@ + +/* + * QEMU model of the Milkymist VGA framebuffer. + * + * Copyright (c) 2010 Michael Walle <michael@walle.cc> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * + * Specification available at: + * http://www.milkymist.org/socdoc/vgafb.pdf + */ + +#include "hw.h" +#include "sysbus.h" +#include "trace.h" +#include "console.h" +#include "framebuffer.h" +#include "pixel_ops.h" +#include "qemu-error.h" + +#define BITS 8 +#include "milkymist-vgafb_template.h" +#define BITS 15 +#include "milkymist-vgafb_template.h" +#define BITS 16 +#include "milkymist-vgafb_template.h" +#define BITS 24 +#include "milkymist-vgafb_template.h" +#define BITS 32 +#include "milkymist-vgafb_template.h" + +enum { + R_CTRL = 0, + R_HRES, + R_HSYNC_START, + R_HSYNC_END, + R_HSCAN, + R_VRES, + R_VSYNC_START, + R_VSYNC_END, + R_VSCAN, + R_BASEADDRESS, + R_BASEADDRESS_ACT, + R_BURST_COUNT, + R_SOURCE_CLOCK, + R_MAX +}; + +enum { + CTRL_RESET = (1<<0), +}; + +struct MilkymistVgafbState { + SysBusDevice busdev; + DisplayState *ds; + + int invalidate; + uint32_t fb_offset; + uint32_t fb_mask; + + uint32_t regs[R_MAX]; +}; +typedef struct MilkymistVgafbState MilkymistVgafbState; + +static int vgafb_enabled(MilkymistVgafbState *s) +{ + return !(s->regs[R_CTRL] & CTRL_RESET); +} + +static void vgafb_update_display(void *opaque) +{ + MilkymistVgafbState *s = opaque; + int first = 0; + int last = 0; + drawfn fn; + + if (!vgafb_enabled(s)) { + return; + } + + int dest_width = s->regs[R_HRES]; + + switch (ds_get_bits_per_pixel(s->ds)) { + case 0: + return; + case 8: + fn = draw_line_8; + break; + case 15: + fn = draw_line_15; + dest_width *= 2; + break; + case 16: + fn = draw_line_16; + dest_width *= 2; + break; + case 24: + fn = draw_line_24; + dest_width *= 3; + break; + case 32: + fn = draw_line_32; + dest_width *= 4; + break; + default: + hw_error("milkymist_vgafb: bad color depth\n"); + break; + } + + framebuffer_update_display(s->ds, + s->regs[R_BASEADDRESS] + s->fb_offset, + s->regs[R_HRES], + s->regs[R_VRES], + s->regs[R_HRES] * 2, + dest_width, + 0, + s->invalidate, + fn, + NULL, + &first, &last); + + if (first >= 0) { + dpy_update(s->ds, 0, first, s->regs[R_HRES], last - first + 1); + } + s->invalidate = 0; +} + +static void vgafb_invalidate_display(void *opaque) +{ + MilkymistVgafbState *s = opaque; + s->invalidate = 1; +} + +static void vgafb_resize(MilkymistVgafbState *s) +{ + if (!vgafb_enabled(s)) { + return; + } + + qemu_console_resize(s->ds, s->regs[R_HRES], s->regs[R_VRES]); + s->invalidate = 1; +} + +static uint32_t vgafb_read(void *opaque, target_phys_addr_t addr) +{ + MilkymistVgafbState *s = opaque; + uint32_t r = 0; + + addr >>= 2; + switch (addr) { + case R_CTRL: + case R_HRES: + case R_HSYNC_START: + case R_HSYNC_END: + case R_HSCAN: + case R_VRES: + case R_VSYNC_START: + case R_VSYNC_END: + case R_VSCAN: + case R_BASEADDRESS: + case R_BURST_COUNT: + case R_SOURCE_CLOCK: + r = s->regs[addr]; + break; + case R_BASEADDRESS_ACT: + r = s->regs[R_BASEADDRESS]; + break; + + default: + error_report("milkymist_vgafb: read access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + + trace_milkymist_vgafb_memory_read(addr << 2, r); + + return r; +} + +static void +vgafb_write(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + MilkymistVgafbState *s = opaque; + + trace_milkymist_vgafb_memory_write(addr, value); + + addr >>= 2; + switch (addr) { + case R_CTRL: + case R_HSYNC_START: + case R_HSYNC_END: + case R_HSCAN: + case R_VSYNC_START: + case R_VSYNC_END: + case R_VSCAN: + case R_BURST_COUNT: + case R_SOURCE_CLOCK: + s->regs[addr] = value; + break; + case R_BASEADDRESS: + if (value & 0x1f) { + error_report("milkymist_vgafb: framebuffer base address have to " + "be 32 byte aligned"); + break; + } + s->regs[addr] = value & s->fb_mask; + s->invalidate = 1; + break; + case R_HRES: + case R_VRES: + s->regs[addr] = value; + vgafb_resize(s); + break; + case R_BASEADDRESS_ACT: + error_report("milkymist_vgafb: write to read-only register 0x" + TARGET_FMT_plx, addr << 2); + break; + + default: + error_report("milkymist_vgafb: write access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } +} + +static CPUReadMemoryFunc * const vgafb_read_fn[] = { + NULL, + NULL, + &vgafb_read +}; + +static CPUWriteMemoryFunc * const vgafb_write_fn[] = { + NULL, + NULL, + &vgafb_write +}; + +static void milkymist_vgafb_reset(DeviceState *d) +{ + MilkymistVgafbState *s = container_of(d, MilkymistVgafbState, busdev.qdev); + int i; + + for (i = 0; i < R_MAX; i++) { + s->regs[i] = 0; + } + + /* defaults */ + s->regs[R_CTRL] = CTRL_RESET; + s->regs[R_HRES] = 640; + s->regs[R_VRES] = 480; + s->regs[R_BASEADDRESS] = 0; +} + +static int milkymist_vgafb_init(SysBusDevice *dev) +{ + MilkymistVgafbState *s = FROM_SYSBUS(typeof(*s), dev); + int vgafb_regs; + + vgafb_regs = cpu_register_io_memory(vgafb_read_fn, vgafb_write_fn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, R_MAX * 4, vgafb_regs); + + s->ds = graphic_console_init(vgafb_update_display, + vgafb_invalidate_display, + NULL, NULL, s); + + return 0; +} + +static int vgafb_post_load(void *opaque, int version_id) +{ + vgafb_invalidate_display(opaque); + return 0; +} + +static const VMStateDescription vmstate_milkymist_vgafb = { + .name = "milkymist-vgafb", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = vgafb_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, MilkymistVgafbState, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static SysBusDeviceInfo milkymist_vgafb_info = { + .init = milkymist_vgafb_init, + .qdev.name = "milkymist-vgafb", + .qdev.size = sizeof(MilkymistVgafbState), + .qdev.vmsd = &vmstate_milkymist_vgafb, + .qdev.reset = milkymist_vgafb_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("fb_offset", MilkymistVgafbState, fb_offset, 0x0), + DEFINE_PROP_UINT32("fb_mask", MilkymistVgafbState, fb_mask, 0xffffffff), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void milkymist_vgafb_register(void) +{ + sysbus_register_withprop(&milkymist_vgafb_info); +} + +device_init(milkymist_vgafb_register) |