/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program 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 General Public License for more details.
*/
#include "qemu_file.h"
#include "android/android.h"
#include "goldfish_device.h"
#include "framebuffer.h"
enum {
FB_GET_WIDTH = 0x00,
FB_GET_HEIGHT = 0x04,
FB_INT_STATUS = 0x08,
FB_INT_ENABLE = 0x0c,
FB_SET_BASE = 0x10,
FB_SET_ROTATION = 0x14,
FB_SET_BLANK = 0x18,
FB_GET_PHYS_WIDTH = 0x1c,
FB_GET_PHYS_HEIGHT = 0x20,
FB_INT_VSYNC = 1U << 0,
FB_INT_BASE_UPDATE_DONE = 1U << 1
};
struct goldfish_fb_state {
struct goldfish_device dev;
QFrameBuffer* qfbuff;
uint32_t fb_base;
uint32_t base_valid : 1;
uint32_t need_update : 1;
uint32_t need_int : 1;
uint32_t set_rotation : 2;
uint32_t blank : 1;
uint32_t int_status;
uint32_t int_enable;
int rotation; /* 0, 1, 2 or 3 */
};
#define GOLDFISH_FB_SAVE_VERSION 1
static void goldfish_fb_save(QEMUFile* f, void* opaque)
{
struct goldfish_fb_state* s = opaque;
QFrameBuffer* q = s->qfbuff;
qemu_put_be32(f, q->width);
qemu_put_be32(f, q->height);
qemu_put_be32(f, q->pitch);
qemu_put_byte(f, q->rotation);
qemu_put_be32(f, s->fb_base);
qemu_put_byte(f, s->base_valid);
qemu_put_byte(f, s->need_update);
qemu_put_byte(f, s->need_int);
qemu_put_byte(f, s->set_rotation);
qemu_put_byte(f, s->blank);
qemu_put_be32(f, s->int_status);
qemu_put_be32(f, s->int_enable);
qemu_put_be32(f, s->rotation);
}
static int goldfish_fb_load(QEMUFile* f, void* opaque, int version_id)
{
struct goldfish_fb_state* s = opaque;
QFrameBuffer* q = s->qfbuff;
int ret = -1;
int ds_w, ds_h, ds_pitch, ds_rot;
if (version_id != GOLDFISH_FB_SAVE_VERSION)
goto Exit;
ds_w = qemu_get_be32(f);
ds_h = qemu_get_be32(f);
ds_pitch = qemu_get_be32(f);
ds_rot = qemu_get_byte(f);
if (q->width != ds_w ||
q->height != ds_h ||
q->pitch != ds_pitch ||
q->rotation != ds_rot )
{
/* XXX: We should be able to force a resize/rotation from here ? */
fprintf(stderr, "%s: framebuffer dimensions mismatch\n", __FUNCTION__);
goto Exit;
}
s->fb_base = qemu_get_be32(f);
s->base_valid = qemu_get_byte(f);
s->need_update = qemu_get_byte(f);
s->need_int = qemu_get_byte(f);
s->set_rotation = qemu_get_byte(f);
s->blank = qemu_get_byte(f);
s->int_status = qemu_get_be32(f);
s->int_enable = qemu_get_be32(f);
s->rotation = qemu_get_be32(f);
/* force a refresh */
s->need_update = 1;
ret = 0;
Exit:
return ret;
}
#define STATS 0
#if STATS
static int stats_counter;
static long stats_total;
static int stats_full_updates;
static long stats_total_full_updates;
#endif
static void goldfish_fb_update_display(void *opaque)
{
struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
uint32_t addr;
uint32_t base;
uint8_t* dst_line;
uint8_t* src_line;
int y_first, y_last = 0;
int full_update = 0;
int width, height, pitch;
base = s->fb_base;
if(base == 0)
return;
if((s->int_enable & FB_INT_VSYNC) && !(s->int_status & FB_INT_VSYNC)) {
s->int_status |= FB_INT_VSYNC;
goldfish_device_set_irq(&s->dev, 0, 1);
}
y_first = -1;
addr = base;
if(s->need_update) {
full_update = 1;
if(s->need_int) {
s->int_status |= FB_INT_BASE_UPDATE_DONE;
if(s->int_enable & FB_INT_BASE_UPDATE_DONE)
goldfish_device_set_irq(&s->dev, 0, 1);
}
s->need_int = 0;
s->need_update = 0;
}
src_line = qemu_get_ram_ptr( base );
dst_line = s->qfbuff->pixels;
pitch = s->qfbuff->pitch;
width = s->qfbuff->width;
height = s->qfbuff->height;
#if STATS
if (full_update)
stats_full_updates += 1;
if (++stats_counter == 120) {
stats_total += stats_counter;
stats_total_full_updates += stats_full_updates;
printf( "full update stats: peak %.2f %% total %.2f %%\n",
stats_full_updates*100.0/stats_counter,
stats_total_full_updates*100.0/stats_total );
stats_counter = 0;
stats_full_updates = 0;
}
#endif /* STATS */
if (s->blank)
{
memset( dst_line, 0, height*pitch );
y_first = 0;
y_last = height-1;
}
else if (full_update)
{
int yy;
for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2)
{
uint16_t* src = (uint16_t*) src_line;
uint16_t* dst = (uint16_t*) dst_line;
int nn;
for (nn = 0; nn < width; nn++) {
unsigned spix = src[nn];
unsigned dpix = dst[nn];
#if WORDS_BIGENDIAN
spix = ((spix << 8) | (spix >> 8)) & 0xffff;
#else
if (spix != dpix)
break;
#endif
}
if (nn == width)
continue;
#if WORDS_BIGENDIAN
for ( ; nn < width; nn++ ) {
unsigned spix = src[nn];
dst[nn] = (uint16_t)((spix << 8) | (spix >> 8));
}
#else
memcpy( dst+nn, src+nn, (width-nn)*2 );
#endif
y_first = (y_first < 0) ? yy : y_first;
y_last = yy;
}
}
else /* not a full update, should not happen very often with Android */
{
int yy;
for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2)
{
uint16_t* src = (uint16_t*) src_line;
uint16_t* dst = (uint16_t*) dst_line;
int len = width*2;
#if WORDS_BIGENDIAN
int nn;
#endif
int dirty = 0;
while (len > 0) {
int len2 = TARGET_PAGE_SIZE - (addr & (TARGET_PAGE_SIZE-1));
if (len2 > len)
len2 = len;
dirty |= cpu_physical_memory_get_dirty(addr, VGA_DIRTY_FLAG);
addr += len2;
len -= len2;
}
if (!dirty)
continue;
#if WORDS_BIGENDIAN
for (nn = 0; nn < width; nn++ ) {
unsigned spix = src[nn];
dst[nn] = (uint16_t)((spix << 8) | (spix >> 8));
}
#else
memcpy( dst, src, width*2 );
#endif
y_first = (y_first < 0) ? yy : y_first;
y_last = yy;
}
}
if (y_first < 0)
return;
y_last += 1;
//printf("goldfish_fb_update_display %d %d, base %x\n", first, last, base);
cpu_physical_memory_reset_dirty(base + y_first * width * 2,
base + y_last * width * 2,
VGA_DIRTY_FLAG);
qframebuffer_update( s->qfbuff, 0, y_first, width, y_last-y_first );
}
static void goldfish_fb_invalidate_display(void * opaque)
{
// is this called?
struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
s->need_update = 1;
}
static void goldfish_fb_detach_display(void* opaque)
{
struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
s->qfbuff = NULL;
}
static uint32_t goldfish_fb_read(void *opaque, target_phys_addr_t offset)
{
uint32_t ret;
struct goldfish_fb_state *s = opaque;
switch(offset) {
case FB_GET_WIDTH:
ret = s->qfbuff->width;
//printf("FB_GET_WIDTH => %d\n", ret);
return ret;
case FB_GET_HEIGHT:
ret = s->qfbuff->height;
//printf( "FB_GET_HEIGHT = %d\n", ret);
return ret;
case FB_INT_STATUS:
ret = s->int_status & s->int_enable;
if(ret) {
s->int_status &= ~ret;
goldfish_device_set_irq(&s->dev, 0, 0);
}
return ret;
case FB_GET_PHYS_WIDTH:
ret = s->qfbuff->phys_width_mm;
//printf( "FB_GET_PHYS_WIDTH => %d\n", ret );
return ret;
case FB_GET_PHYS_HEIGHT:
ret = s->qfbuff->phys_height_mm;
//printf( "FB_GET_PHYS_HEIGHT => %d\n", ret );
return ret;
default:
cpu_abort (cpu_single_env, "goldfish_fb_read: Bad offset %x\n", offset);
return 0;
}
}
static void goldfish_fb_write(void *opaque, target_phys_addr_t offset,
uint32_t val)
{
struct goldfish_fb_state *s = opaque;
switch(offset) {
case FB_INT_ENABLE:
s->int_enable = val;
goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable));
break;
case FB_SET_BASE: {
int need_resize = !s->base_valid;
s->fb_base = val;
s->int_status &= ~FB_INT_BASE_UPDATE_DONE;
s->need_update = 1;
s->need_int = 1;
s->base_valid = 1;
if(s->set_rotation != s->rotation) {
//printf("FB_SET_BASE: rotation : %d => %d\n", s->rotation, s->set_rotation);
s->rotation = s->set_rotation;
need_resize = 1;
}
goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable));
if (need_resize) {
//printf("FB_SET_BASE: need resize (rotation=%d)\n", s->rotation );
qframebuffer_rotate( s->qfbuff, s->rotation );
}
} break;
case FB_SET_ROTATION:
//printf( "FB_SET_ROTATION %d\n", val);
s->set_rotation = val;
break;
case FB_SET_BLANK:
s->blank = val;
s->need_update = 1;
break;
default:
cpu_abort (cpu_single_env, "goldfish_fb_write: Bad offset %x\n", offset);
}
}
static CPUReadMemoryFunc *goldfish_fb_readfn[] = {
goldfish_fb_read,
goldfish_fb_read,
goldfish_fb_read
};
static CPUWriteMemoryFunc *goldfish_fb_writefn[] = {
goldfish_fb_write,
goldfish_fb_write,
goldfish_fb_write
};
void goldfish_fb_init(DisplayState *ds, int id)
{
struct goldfish_fb_state *s;
s = (struct goldfish_fb_state *)qemu_mallocz(sizeof(*s));
s->dev.name = "goldfish_fb";
s->dev.id = id;
s->dev.size = 0x1000;
s->dev.irq_count = 1;
s->qfbuff = qframebuffer_fifo_get();
qframebuffer_set_producer( s->qfbuff, s,
goldfish_fb_update_display,
goldfish_fb_invalidate_display,
goldfish_fb_detach_display );
goldfish_device_add(&s->dev, goldfish_fb_readfn, goldfish_fb_writefn, s);
register_savevm( "goldfish_fb", 0, GOLDFISH_FB_SAVE_VERSION,
goldfish_fb_save, goldfish_fb_load, s);
}