blob: be6be5a0aaea4548f1045bcc229891f0e256a7cd [file] [log] [blame]
/* Copyright (C) 2007-2013 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 "framebuffer.h"
#include "hw/hw.h"
#include "hw/sysbus.h"
#include "ui/console.h"
#include "ui/pixel_ops.h"
#include "trace.h"
#include "hw/display/goldfish_fb.h"
#include <inttypes.h>
static int s_use_host_gpu = 0;
static int s_display_bpp = 32;
void goldfish_fb_set_use_host_gpu(int enabled) {
s_use_host_gpu = enabled;
}
void goldfish_fb_set_display_depth(int depth) {
s_display_bpp = depth;
}
#define DEST_BITS 8
#define SOURCE_BITS 16
#include "goldfish_fb_template.h"
#define DEST_BITS 15
#define SOURCE_BITS 16
#include "goldfish_fb_template.h"
#define DEST_BITS 16
#define SOURCE_BITS 16
#include "goldfish_fb_template.h"
#define DEST_BITS 24
#define SOURCE_BITS 16
#include "goldfish_fb_template.h"
#define DEST_BITS 32
#define SOURCE_BITS 16
#include "goldfish_fb_template.h"
#define DEST_BITS 8
#define SOURCE_BITS 32
#include "goldfish_fb_template.h"
#define DEST_BITS 15
#define SOURCE_BITS 32
#include "goldfish_fb_template.h"
#define DEST_BITS 16
#define SOURCE_BITS 32
#include "goldfish_fb_template.h"
#define DEST_BITS 24
#define SOURCE_BITS 32
#include "goldfish_fb_template.h"
#define DEST_BITS 32
#define SOURCE_BITS 32
#include "goldfish_fb_template.h"
#define TYPE_GOLDFISH_FB "goldfish_fb"
#define GOLDFISH_FB(obj) OBJECT_CHECK(struct goldfish_fb_state, (obj), TYPE_GOLDFISH_FB)
/* These values *must* match the platform definitions found under
* <system/graphics.h>
*/
enum {
HAL_PIXEL_FORMAT_RGBA_8888 = 1,
HAL_PIXEL_FORMAT_RGBX_8888 = 2,
HAL_PIXEL_FORMAT_RGB_888 = 3,
HAL_PIXEL_FORMAT_RGB_565 = 4,
HAL_PIXEL_FORMAT_BGRA_8888 = 5,
};
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, /* DEPRECATED */
FB_SET_BLANK = 0x18,
FB_GET_PHYS_WIDTH = 0x1c,
FB_GET_PHYS_HEIGHT = 0x20,
FB_GET_FORMAT = 0x24,
FB_INT_VSYNC = 1U << 0,
FB_INT_BASE_UPDATE_DONE = 1U << 1
};
struct goldfish_fb_state {
SysBusDevice parent;
QemuConsole *con;
MemoryRegion iomem;
qemu_irq irq;
uint32_t fb_base;
uint32_t base_valid : 1;
uint32_t need_update : 1;
uint32_t need_int : 1;
uint32_t blank : 1;
uint32_t int_status;
uint32_t int_enable;
int rotation; /* 0, 1, 2 or 3 */
int dpi;
int format;
};
#define GOLDFISH_FB_SAVE_VERSION 3
/* Console hooks */
void goldfish_fb_set_rotation(int rotation)
{
DeviceState *dev = qdev_find_recursive(sysbus_get_default(), TYPE_GOLDFISH_FB);
if (dev) {
struct goldfish_fb_state *s = GOLDFISH_FB(dev);
DisplaySurface *ds = qemu_console_surface(s->con);
s->rotation = rotation;
s->need_update = 1;
qemu_console_resize(s->con, surface_height(ds), surface_width(ds));
} else {
fprintf(stderr,"%s: unable to find FB dev\n", __func__);
}
}
static void goldfish_fb_save(QEMUFile* f, void* opaque)
{
struct goldfish_fb_state* s = opaque;
DisplaySurface *ds = qemu_console_surface(s->con);
qemu_put_be32(f, surface_width(ds));
qemu_put_be32(f, surface_height(ds));
qemu_put_be32(f, surface_stride(ds));
qemu_put_byte(f, 0);
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->blank);
qemu_put_be32(f, s->int_status);
qemu_put_be32(f, s->int_enable);
qemu_put_be32(f, s->rotation);
qemu_put_be32(f, s->dpi);
qemu_put_be32(f, s->format);
}
static int goldfish_fb_load(QEMUFile* f, void* opaque, int version_id)
{
struct goldfish_fb_state* s = opaque;
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);
DisplaySurface *ds = qemu_console_surface(s->con);
if (surface_width(ds) != ds_w ||
surface_height(ds) != ds_h ||
surface_stride(ds) != ds_pitch ||
ds_rot != 0)
{
/* 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->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);
s->dpi = qemu_get_be32(f);
s->format = qemu_get_be32(f);
/* force a refresh */
s->need_update = 1;
ret = 0;
Exit:
return ret;
}
static int
pixels_to_mm(int pixels, int dpi)
{
/* dpi = dots / inch
** inch = dots / dpi
** mm / 25.4 = dots / dpi
** mm = (dots * 25.4)/dpi
*/
return (int)(0.5 + 25.4 * pixels / dpi);
}
#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;
DisplaySurface *ds = qemu_console_surface(s->con);
int full_update = 0;
if (!s || !s->con || surface_bits_per_pixel(ds) == 0 || !s->fb_base)
return;
if((s->int_enable & FB_INT_VSYNC) && !(s->int_status & FB_INT_VSYNC)) {
s->int_status |= FB_INT_VSYNC;
qemu_irq_raise(s->irq);
}
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)
qemu_irq_raise(s->irq);
}
s->need_int = 0;
s->need_update = 0;
}
int dest_width = surface_width(ds);
int dest_height = surface_height(ds);
int dest_pitch = surface_stride(ds);
int ymin, ymax;
#if STATS
if (full_update)
stats_full_updates += 1;
if (++stats_counter == 120) {
stats_total += stats_counter;
stats_total_full_updates += stats_full_updates;
trace_goldfish_fb_update_stats(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)
{
void *dst_line = surface_data(ds);
memset( dst_line, 0, dest_height*dest_pitch );
ymin = 0;
ymax = dest_height-1;
}
else
{
SysBusDevice *dev = SYS_BUS_DEVICE(opaque);
MemoryRegion *address_space = sysbus_address_space(dev);
int src_width, src_height;
int dest_row_pitch, dest_col_pitch;
drawfn fn;
/* The source framebuffer is always read in a linear fashion,
* we achieve rotation by altering the destination
* step-per-pixel.
*/
switch (s->rotation) {
case 0: /* Normal, native landscape view */
src_width = dest_width;
src_height = dest_height;
dest_row_pitch = surface_stride(ds);
dest_col_pitch = surface_bytes_per_pixel(ds);
break;
case 1: /* 90 degree, portrait view */
src_width = dest_height;
src_height = dest_width;
dest_row_pitch = -surface_bytes_per_pixel(ds);
dest_col_pitch = surface_stride(ds);
break;
case 2: /* 180 degree, inverted landscape view */
src_width = dest_width;
src_height = dest_height;
dest_row_pitch = -surface_stride(ds);
dest_col_pitch = -surface_bytes_per_pixel(ds);
break;
case 3: /* 270 degree, mirror portrait view */
src_width = dest_height;
src_height = dest_width;
dest_row_pitch = surface_bytes_per_pixel(ds);
dest_col_pitch = -surface_stride(ds);
break;
default:
g_assert_not_reached();
}
int source_bytes_per_pixel = 2;
switch (s->format) { /* source format */
case HAL_PIXEL_FORMAT_RGB_565:
source_bytes_per_pixel = 2;
switch (surface_bits_per_pixel(ds)) { /* dest format */
case 8: fn = draw_line_16_8; break;
case 15: fn = draw_line_16_15; break;
case 16: fn = draw_line_16_16; break;
case 24: fn = draw_line_16_24; break;
case 32: fn = draw_line_16_32; break;
default:
hw_error("goldfish_fb: bad dest color depth\n");
return;
}
break;
case HAL_PIXEL_FORMAT_RGBX_8888:
source_bytes_per_pixel = 4;
switch (surface_bits_per_pixel(ds)) { /* dest format */
case 8: fn = draw_line_32_8; break;
case 15: fn = draw_line_32_15; break;
case 16: fn = draw_line_32_16; break;
case 24: fn = draw_line_32_24; break;
case 32: fn = draw_line_32_32; break;
default:
hw_error("goldfish_fb: bad dest color depth\n");
return;
}
break;
default:
hw_error("goldfish_fb: bad source color format\n");
return;
}
ymin = 0;
// with -gpu on, the following check and return will save 2%
// CPU time on OSX; saving on other platforms may differ.
if (s_use_host_gpu) return;
framebuffer_update_display(ds, address_space, s->fb_base,
src_width, src_height,
src_width * source_bytes_per_pixel,
dest_row_pitch, dest_col_pitch,
full_update,
fn, ds, &ymin, &ymax);
}
ymax += 1;
if (ymin >= 0) {
if (s->rotation % 2) {
/* In portrait mode we are drawing "sideways" so always
* need to update the whole screen */
trace_goldfish_fb_update_display(0, dest_height, 0, dest_width);
dpy_gfx_update(s->con, 0, 0, dest_width, dest_height);
} else {
trace_goldfish_fb_update_display(ymin, ymax-ymin, 0, dest_width);
dpy_gfx_update(s->con, 0, ymin, dest_width, ymax-ymin);
}
}
}
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 uint64_t goldfish_fb_read(void *opaque, hwaddr offset, unsigned size)
{
uint64_t ret = 0;
struct goldfish_fb_state *s = opaque;
DisplaySurface *ds = qemu_console_surface(s->con);
if (!s_display_bpp) {
s_display_bpp = surface_bits_per_pixel(ds);
}
switch(offset) {
case FB_GET_WIDTH:
ret = surface_width(ds);
break;
case FB_GET_HEIGHT:
ret = surface_height(ds);
break;
case FB_INT_STATUS:
ret = s->int_status & s->int_enable;
if(ret) {
s->int_status &= ~ret;
qemu_irq_lower(s->irq);
}
break;
case FB_GET_PHYS_WIDTH:
ret = pixels_to_mm( surface_width(ds), s->dpi );
break;
case FB_GET_PHYS_HEIGHT:
ret = pixels_to_mm( surface_height(ds), s->dpi );
break;
case FB_GET_FORMAT:
/* A kernel making this query supports high color and true color */
switch (s_display_bpp) { /* hw.lcd.depth */
case 32:
case 24:
ret = HAL_PIXEL_FORMAT_RGBX_8888;
break;
case 16:
ret = HAL_PIXEL_FORMAT_RGB_565;
break;
default:
error_report("goldfish_fb_read: Bad display bit depth %d",
s_display_bpp);
break;
}
s->format = ret;
break;
default:
error_report("goldfish_fb_read: Bad offset 0x" TARGET_FMT_plx,
offset);
break;
}
trace_goldfish_fb_memory_read(offset, ret);
return ret;
}
static void goldfish_fb_write(void *opaque, hwaddr offset, uint64_t val,
unsigned size)
{
struct goldfish_fb_state *s = opaque;
trace_goldfish_fb_memory_write(offset, val);
switch(offset) {
case FB_INT_ENABLE:
s->int_enable = val;
qemu_set_irq(s->irq, s->int_status & s->int_enable);
break;
case FB_SET_BASE:
s->fb_base = val;
s->int_status &= ~FB_INT_BASE_UPDATE_DONE;
s->need_update = 1;
s->need_int = 1;
s->base_valid = 1;
/* The guest is waiting for us to complete an update cycle
* and notify it, so make sure we do a redraw immediately.
*/
if (s_use_host_gpu) return;
graphic_hw_update(s->con);
qemu_set_irq(s->irq, s->int_status & s->int_enable);
break;
case FB_SET_ROTATION:
error_report("%s: use of deprecated FB_SET_ROTATION %" PRIu64,
__func__, val);
break;
case FB_SET_BLANK:
s->blank = val;
s->need_update = 1;
break;
default:
error_report("goldfish_fb_write: Bad offset 0x" TARGET_FMT_plx,
offset);
}
}
static const MemoryRegionOps goldfish_fb_iomem_ops = {
.read = goldfish_fb_read,
.write = goldfish_fb_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl.min_access_size = 4,
.impl.max_access_size = 4,
};
static const GraphicHwOps goldfish_fb_ops = {
.invalidate = goldfish_fb_invalidate_display,
.gfx_update = goldfish_fb_update_display,
};
static int goldfish_fb_init(SysBusDevice *sbdev)
{
DeviceState *dev = DEVICE(sbdev);
struct goldfish_fb_state *s = GOLDFISH_FB(dev);
dev->id = g_strdup(TYPE_GOLDFISH_FB);
sysbus_init_irq(sbdev, &s->irq);
s->con = graphic_console_init(dev, 0, &goldfish_fb_ops, s);
s->dpi = 165; /* TODO: Find better way to get actual value ! */
s->format = HAL_PIXEL_FORMAT_RGB_565;
memory_region_init_io(&s->iomem, OBJECT(s), &goldfish_fb_iomem_ops, s,
"goldfish_fb", 0x100);
sysbus_init_mmio(sbdev, &s->iomem);
register_savevm(dev, "goldfish_fb", 0, GOLDFISH_FB_SAVE_VERSION,
goldfish_fb_save, goldfish_fb_load, s);
return 0;
}
static void goldfish_fb_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
k->init = goldfish_fb_init;
dc->desc = "goldfish framebuffer";
}
static const TypeInfo goldfish_fb_info = {
.name = TYPE_GOLDFISH_FB,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(struct goldfish_fb_state),
.class_init = goldfish_fb_class_init,
};
static void goldfish_fb_register(void)
{
type_register_static(&goldfish_fb_info);
}
type_init(goldfish_fb_register);