qemu/hw/syborg_fb.c
Alexander Graf 2507c12ab0 Add endianness as io mem parameter
As stated before, devices can be little, big or native endian. The
target endianness is not of their concern, so we need to push things
down a level.

This patch adds a parameter to cpu_register_io_memory that allows a
device to choose its endianness. For now, all devices simply choose
native endian, because that's the same behavior as before.

Signed-off-by: Alexander Graf <agraf@suse.de>
Signed-off-by: Blue Swirl <blauwirbel@gmail.com>
2010-12-11 15:24:25 +00:00

552 lines
13 KiB
C

/*
* Syborg Framebuffer
*
* Copyright (c) 2009 CodeSourcery
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "sysbus.h"
#include "console.h"
#include "syborg.h"
#include "framebuffer.h"
//#define DEBUG_SYBORG_FB
#ifdef DEBUG_SYBORG_FB
#define DPRINTF(fmt, ...) \
do { printf("syborg_fb: " fmt , ## __VA_ARGS__); } while (0)
#define BADF(fmt, ...) \
do { fprintf(stderr, "syborg_fb: error: " fmt , ## __VA_ARGS__); \
exit(1);} while (0)
#else
#define DPRINTF(fmt, ...) do {} while(0)
#define BADF(fmt, ...) \
do { fprintf(stderr, "syborg_fb: error: " fmt , ## __VA_ARGS__);} while (0)
#endif
enum {
FB_ID = 0,
FB_BASE = 1,
FB_HEIGHT = 2,
FB_WIDTH = 3,
FB_ORIENTATION = 4,
FB_BLANK = 5,
FB_INT_MASK = 6,
FB_INTERRUPT_CAUSE = 7,
FB_BPP = 8,
FB_COLOR_ORDER = 9,
FB_BYTE_ORDER = 10,
FB_PIXEL_ORDER = 11,
FB_ROW_PITCH = 12,
FB_ENABLED = 13,
FB_PALETTE_START = 0x400 >> 2,
FB_PALETTE_END = FB_PALETTE_START+256-1,
};
#define FB_INT_VSYNC (1U << 0)
#define FB_INT_BASE_UPDATE_DONE (1U << 1)
typedef struct {
SysBusDevice busdev;
DisplayState *ds;
/*QEMUConsole *console;*/
uint32_t need_update : 1;
uint32_t need_int : 1;
uint32_t enabled : 1;
uint32_t int_status;
uint32_t int_enable;
qemu_irq irq;
uint32_t base;
uint32_t pitch;
uint32_t rows;
uint32_t cols;
int blank;
int bpp;
int rgb; /* 0 = BGR, 1 = RGB */
int endian; /* 0 = Little, 1 = Big */
uint32_t raw_palette[256];
uint32_t palette[256];
} SyborgFBState;
enum {
BPP_SRC_1,
BPP_SRC_2,
BPP_SRC_4,
BPP_SRC_8,
BPP_SRC_16,
BPP_SRC_32,
/* TODO: Implement these. */
BPP_SRC_15 = -1,
BPP_SRC_24 = -2
};
#include "pixel_ops.h"
#define BITS 8
#include "pl110_template.h"
#define BITS 15
#include "pl110_template.h"
#define BITS 16
#include "pl110_template.h"
#define BITS 24
#include "pl110_template.h"
#define BITS 32
#include "pl110_template.h"
/* Update interrupts. */
static void syborg_fb_update(SyborgFBState *s)
{
if ((s->int_status & s->int_enable) != 0) {
DPRINTF("Raise IRQ\n");
qemu_irq_raise(s->irq);
} else {
DPRINTF("Lower IRQ\n");
qemu_irq_lower(s->irq);
}
}
static int syborg_fb_enabled(const SyborgFBState *s)
{
return s->enabled;
}
static void syborg_fb_update_palette(SyborgFBState *s)
{
int n, i;
uint32_t raw;
unsigned int r, g, b;
switch (s->bpp) {
case BPP_SRC_1: n = 2; break;
case BPP_SRC_2: n = 4; break;
case BPP_SRC_4: n = 16; break;
case BPP_SRC_8: n = 256; break;
default: return;
}
for (i = 0; i < n; i++) {
raw = s->raw_palette[i];
r = (raw >> 16) & 0xff;
g = (raw >> 8) & 0xff;
b = raw & 0xff;
switch (ds_get_bits_per_pixel(s->ds)) {
case 8:
s->palette[i] = rgb_to_pixel8(r, g, b);
break;
case 15:
s->palette[i] = rgb_to_pixel15(r, g, b);
break;
case 16:
s->palette[i] = rgb_to_pixel16(r, g, b);
break;
case 24:
case 32:
s->palette[i] = rgb_to_pixel32(r, g, b);
break;
default:
abort();
}
}
}
static void syborg_fb_update_display(void *opaque)
{
SyborgFBState *s = (SyborgFBState *)opaque;
drawfn* fntable;
drawfn fn;
int dest_width;
int src_width;
int bpp_offset;
int first;
int last;
if (!syborg_fb_enabled(s))
return;
switch (ds_get_bits_per_pixel(s->ds)) {
case 0:
return;
case 8:
fntable = pl110_draw_fn_8;
dest_width = 1;
break;
case 15:
fntable = pl110_draw_fn_15;
dest_width = 2;
break;
case 16:
fntable = pl110_draw_fn_16;
dest_width = 2;
break;
case 24:
fntable = pl110_draw_fn_24;
dest_width = 3;
break;
case 32:
fntable = pl110_draw_fn_32;
dest_width = 4;
break;
default:
fprintf(stderr, "syborg_fb: Bad color depth\n");
exit(1);
}
if (s->need_int) {
s->int_status |= FB_INT_BASE_UPDATE_DONE;
syborg_fb_update(s);
s->need_int = 0;
}
if (s->rgb) {
bpp_offset = 18;
} else {
bpp_offset = 0;
}
if (s->endian) {
bpp_offset += 6;
}
fn = fntable[s->bpp + bpp_offset];
if (s->pitch) {
src_width = s->pitch;
} else {
src_width = s->cols;
switch (s->bpp) {
case BPP_SRC_1:
src_width >>= 3;
break;
case BPP_SRC_2:
src_width >>= 2;
break;
case BPP_SRC_4:
src_width >>= 1;
break;
case BPP_SRC_8:
break;
case BPP_SRC_15:
case BPP_SRC_16:
src_width <<= 1;
break;
case BPP_SRC_24:
src_width *= 3;
break;
case BPP_SRC_32:
src_width <<= 2;
break;
}
}
dest_width *= s->cols;
first = 0;
/* TODO: Implement blanking. */
if (!s->blank) {
if (s->need_update && s->bpp <= BPP_SRC_8) {
syborg_fb_update_palette(s);
}
framebuffer_update_display(s->ds,
s->base, s->cols, s->rows,
src_width, dest_width, 0,
s->need_update,
fn, s->palette,
&first, &last);
if (first >= 0) {
dpy_update(s->ds, 0, first, s->cols, last - first + 1);
}
s->int_status |= FB_INT_VSYNC;
syborg_fb_update(s);
}
s->need_update = 0;
}
static void syborg_fb_invalidate_display(void * opaque)
{
SyborgFBState *s = (SyborgFBState *)opaque;
s->need_update = 1;
}
static uint32_t syborg_fb_read(void *opaque, target_phys_addr_t offset)
{
SyborgFBState *s = opaque;
DPRINTF("read reg %d\n", (int)offset);
offset &= 0xfff;
switch (offset >> 2) {
case FB_ID:
return SYBORG_ID_FRAMEBUFFER;
case FB_BASE:
return s->base;
case FB_HEIGHT:
return s->rows;
case FB_WIDTH:
return s->cols;
case FB_ORIENTATION:
return 0;
case FB_BLANK:
return s->blank;
case FB_INT_MASK:
return s->int_enable;
case FB_INTERRUPT_CAUSE:
return s->int_status;
case FB_BPP:
switch (s->bpp) {
case BPP_SRC_1: return 1;
case BPP_SRC_2: return 2;
case BPP_SRC_4: return 4;
case BPP_SRC_8: return 8;
case BPP_SRC_15: return 15;
case BPP_SRC_16: return 16;
case BPP_SRC_24: return 24;
case BPP_SRC_32: return 32;
default: return 0;
}
case FB_COLOR_ORDER:
return s->rgb;
case FB_BYTE_ORDER:
return s->endian;
case FB_PIXEL_ORDER:
return 0;
case FB_ROW_PITCH:
return s->pitch;
case FB_ENABLED:
return s->enabled;
default:
if ((offset >> 2) >= FB_PALETTE_START
&& (offset >> 2) <= FB_PALETTE_END) {
return s->raw_palette[(offset >> 2) - FB_PALETTE_START];
} else {
cpu_abort (cpu_single_env, "syborg_fb_read: Bad offset %x\n",
(int)offset);
}
return 0;
}
}
static void syborg_fb_write(void *opaque, target_phys_addr_t offset,
uint32_t val)
{
SyborgFBState *s = opaque;
DPRINTF("write reg %d = %d\n", (int)offset, val);
s->need_update = 1;
offset &= 0xfff;
switch (offset >> 2) {
case FB_BASE:
s->base = val;
s->need_int = 1;
s->need_update = 1;
syborg_fb_update(s);
break;
case FB_HEIGHT:
s->rows = val;
break;
case FB_WIDTH:
s->cols = val;
break;
case FB_ORIENTATION:
/* TODO: Implement rotation. */
break;
case FB_BLANK:
s->blank = val & 1;
break;
case FB_INT_MASK:
s->int_enable = val;
syborg_fb_update(s);
break;
case FB_INTERRUPT_CAUSE:
s->int_status &= ~val;
syborg_fb_update(s);
break;
case FB_BPP:
switch (val) {
case 1: val = BPP_SRC_1; break;
case 2: val = BPP_SRC_2; break;
case 4: val = BPP_SRC_4; break;
case 8: val = BPP_SRC_8; break;
/* case 15: val = BPP_SRC_15; break; */
case 16: val = BPP_SRC_16; break;
/* case 24: val = BPP_SRC_24; break; */
case 32: val = BPP_SRC_32; break;
default: val = s->bpp; break;
}
s->bpp = val;
break;
case FB_COLOR_ORDER:
s->rgb = (val != 0);
break;
case FB_BYTE_ORDER:
s->endian = (val != 0);
break;
case FB_PIXEL_ORDER:
/* TODO: Implement this. */
break;
case FB_ROW_PITCH:
s->pitch = val;
break;
case FB_ENABLED:
s->enabled = val;
break;
default:
if ((offset >> 2) >= FB_PALETTE_START
&& (offset >> 2) <= FB_PALETTE_END) {
s->raw_palette[(offset >> 2) - FB_PALETTE_START] = val;
} else {
cpu_abort (cpu_single_env, "syborg_fb_write: Bad offset %x\n",
(int)offset);
}
break;
}
}
static CPUReadMemoryFunc * const syborg_fb_readfn[] = {
syborg_fb_read,
syborg_fb_read,
syborg_fb_read
};
static CPUWriteMemoryFunc * const syborg_fb_writefn[] = {
syborg_fb_write,
syborg_fb_write,
syborg_fb_write
};
static void syborg_fb_save(QEMUFile *f, void *opaque)
{
SyborgFBState *s = opaque;
int i;
qemu_put_be32(f, s->need_int);
qemu_put_be32(f, s->int_status);
qemu_put_be32(f, s->int_enable);
qemu_put_be32(f, s->enabled);
qemu_put_be32(f, s->base);
qemu_put_be32(f, s->pitch);
qemu_put_be32(f, s->rows);
qemu_put_be32(f, s->cols);
qemu_put_be32(f, s->bpp);
qemu_put_be32(f, s->rgb);
for (i = 0; i < 256; i++) {
qemu_put_be32(f, s->raw_palette[i]);
}
}
static int syborg_fb_load(QEMUFile *f, void *opaque, int version_id)
{
SyborgFBState *s = opaque;
int i;
if (version_id != 1)
return -EINVAL;
s->need_int = qemu_get_be32(f);
s->int_status = qemu_get_be32(f);
s->int_enable = qemu_get_be32(f);
s->enabled = qemu_get_be32(f);
s->base = qemu_get_be32(f);
s->pitch = qemu_get_be32(f);
s->rows = qemu_get_be32(f);
s->cols = qemu_get_be32(f);
s->bpp = qemu_get_be32(f);
s->rgb = qemu_get_be32(f);
for (i = 0; i < 256; i++) {
s->raw_palette[i] = qemu_get_be32(f);
}
s->need_update = 1;
return 0;
}
static int syborg_fb_init(SysBusDevice *dev)
{
SyborgFBState *s = FROM_SYSBUS(SyborgFBState, dev);
int iomemtype;
sysbus_init_irq(dev, &s->irq);
iomemtype = cpu_register_io_memory(syborg_fb_readfn,
syborg_fb_writefn, s,
DEVICE_NATIVE_ENDIAN);
sysbus_init_mmio(dev, 0x1000, iomemtype);
s->ds = graphic_console_init(syborg_fb_update_display,
syborg_fb_invalidate_display,
NULL, NULL, s);
if (s->cols != 0 && s->rows != 0) {
qemu_console_resize(s->ds, s->cols, s->rows);
}
if (!s->cols)
s->cols = ds_get_width(s->ds);
if (!s->rows)
s->rows = ds_get_height(s->ds);
register_savevm(&dev->qdev, "syborg_framebuffer", -1, 1,
syborg_fb_save, syborg_fb_load, s);
return 0;
}
static SysBusDeviceInfo syborg_fb_info = {
.init = syborg_fb_init,
.qdev.name = "syborg,framebuffer",
.qdev.size = sizeof(SyborgFBState),
.qdev.props = (Property[]) {
DEFINE_PROP_UINT32("width", SyborgFBState, cols, 0),
DEFINE_PROP_UINT32("height", SyborgFBState, rows, 0),
DEFINE_PROP_END_OF_LIST(),
}
};
static void syborg_fb_register_devices(void)
{
sysbus_register_withprop(&syborg_fb_info);
}
device_init(syborg_fb_register_devices)