aboutsummaryrefslogtreecommitdiffstats
path: root/hw/goldfish_fb.c
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit55f4e4a5ec657a017e3bf75299ad71fd1c968dd3 (patch)
tree550ce922ea0e125ac6a9738210ce2939bf2fe901 /hw/goldfish_fb.c
parent413f05aaf54fa08c0ae7e997327a4f4a473c0a8d (diff)
downloadexternal_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.zip
external_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.tar.gz
external_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.tar.bz2
Initial Contribution
Diffstat (limited to 'hw/goldfish_fb.c')
-rw-r--r--hw/goldfish_fb.c405
1 files changed, 405 insertions, 0 deletions
diff --git a/hw/goldfish_fb.c b/hw/goldfish_fb.c
new file mode 100644
index 0000000..0924735
--- /dev/null
+++ b/hw/goldfish_fb.c
@@ -0,0 +1,405 @@
+/* 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 "vl.h"
+#include "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 = phys_ram_base + 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;
+ offset -= s->dev.base;
+ 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;
+ offset -= s->dev.base;
+ 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_add_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);
+}
+