diff --git a/hw/display/Kconfig b/hw/display/Kconfig index 2250c74007..4078a2990d 100644 --- a/hw/display/Kconfig +++ b/hw/display/Kconfig @@ -140,3 +140,6 @@ config XLNX_DISPLAYPORT config DM163 bool + +config ESP_RGB + bool diff --git a/hw/display/esp_rgb.c b/hw/display/esp_rgb.c new file mode 100644 index 0000000000..68654e3dcb --- /dev/null +++ b/hw/display/esp_rgb.c @@ -0,0 +1,346 @@ +/* + * ESP Display RGB emulation + * + * Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or + * (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" +#include "hw/sysbus.h" +#include "hw/irq.h" +#include "hw/display/esp_rgb.h" +#include "ui/console.h" +#include "qemu/error-report.h" +#include "sysemu/dma.h" + +#define RGB_WARNING 1 +#define RGB_DEBUG 0 + +#define RGB_VERSION_MAJOR 0 +#define RGB_VERSION_MINOR 2 + +static void update_rgb_surface(ESPRgbState* s){ + DisplaySurface *surface; + switch (s->bpp){ + case BPP_32: + surface = qemu_create_displaysurface_from( + s->width, s->height, + PIXMAN_x8r8g8b8, + s->width * 4, NULL + ); + break; + case BPP_16: + surface= qemu_create_displaysurface_from( + s->width, s->height, + PIXMAN_r5g6b5, + s->width * 2, NULL + ); + break; + default: + warn_report("[ESP RGB] Invalid %d bpp value", s->bpp); + return; + } + surface->flags = QEMU_ALLOCATED_FLAG; + dpy_gfx_replace_surface(s->con, surface); +}; + +static uint64_t esp_rgb_read(void *opaque, hwaddr addr, unsigned int size) +{ + ESPRgbState *s = ESP_RGB(opaque); + uint32_t r = 0; + + /* Only accept 32-bit transactions */ + if (size != sizeof(uint32_t)) { + return r; + } + + switch(addr) { + case A_RGB_VERSION: + r = FIELD_DP32(r, RGB_VERSION, MAJOR, RGB_VERSION_MAJOR); + r = FIELD_DP32(r, RGB_VERSION, MINOR, RGB_VERSION_MINOR); + break; + + case A_RGB_WIN_SIZE: + r = FIELD_DP32(r, RGB_WIN_SIZE, WIDTH, s->width); + r = FIELD_DP32(r, RGB_WIN_SIZE, HEIGHT, s->height); + break; + + case A_RGB_UPDATE_FROM: + r = FIELD_DP32(r, RGB_UPDATE_FROM, X, s->from_x); + r = FIELD_DP32(r, RGB_UPDATE_FROM, Y, s->from_y); + break; + + case A_RGB_UPDATE_TO: + r = FIELD_DP32(r, RGB_UPDATE_TO, X, s->to_x); + r = FIELD_DP32(r, RGB_UPDATE_TO, Y, s->to_y); + break; + + case A_RGB_UPDATE_CONTENT: + r = s->color_content; + break; + + case A_RGB_UPDATE_STATUS: + r = s->update_area; + break; + + case A_RGB_BPP_VALUE: + r = s->bpp; + break; + + default: +#if RGB_WARNING + warn_report("[ESP RGB] Unsupported read to 0x%lx", (unsigned long) addr); +#endif + break; + } + +#if RGB_DEBUG + info_report("[ESP RGB] Reading 0x%lx (0x%x)", addr, r); +#endif + + return r; +} + + +static void esp_rgb_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + ESPRgbState *s = ESP_RGB(opaque); + uint32_t width = 0; + uint32_t height = 0; + + /* Only accept 32-bit transactions */ + if (size != sizeof(uint32_t)) { + return; + } + +#if RGB_DEBUG + info_report("[ESP RGB] Writing 0x%lx = %08lx", addr, value); +#endif + + switch(addr) { + case A_RGB_WIN_SIZE: + width = FIELD_EX32(value, RGB_WIN_SIZE, WIDTH); + height = FIELD_EX32(value, RGB_WIN_SIZE, HEIGHT); + + /* Check the bounds of the new window size */ + s->width = MIN(width, ESP_RGB_MAX_WIDTH); + s->height = MIN(height, ESP_RGB_MAX_HEIGHT); + /* Make sure it's bigger than 10x10 */ + s->width = MAX(s->width, 10); + s->height = MAX(s->height, 10); + + /* Update the window size */ + s->do_update_surface = true; + break; + + case A_RGB_UPDATE_STATUS: + s->update_area = FIELD_EX32(value, RGB_UPDATE_STATUS, ENA) != 0; + break; + + case A_RGB_UPDATE_FROM: + s->from_x = FIELD_EX32(value, RGB_UPDATE_FROM, X); + s->from_y = FIELD_EX32(value, RGB_UPDATE_FROM, Y); + break; + + case A_RGB_UPDATE_TO: + s->to_x = FIELD_EX32(value, RGB_UPDATE_TO, X); + s->to_y = FIELD_EX32(value, RGB_UPDATE_TO, Y); + break; + + case A_RGB_UPDATE_CONTENT: + s->color_content = (uint32_t) value; + break; + + case A_RGB_BPP_VALUE: + s->bpp = (BppEnum) value; + s->do_update_surface = true; + break; + + default: +#if RGB_WARNING + warn_report("[ESP RGB] Unsupported write to 0x%lx (%08lx)", (unsigned long) addr, (unsigned long) value); +#endif + break; + } + +} + + +static void rgb_update(void* opaque) +{ + ESPRgbState* s = (ESPRgbState*) opaque; + + if (s->con && s->do_update_surface) { + update_rgb_surface(s); + s->do_update_surface = false; + } + + if (s->con && s->update_area) { + uint32_t src = s->color_content; + AddressSpace* src_as = NULL; + + /* Since we are in a 32bpp configuration, it's enough to cast the framebuffer + * as a uint32_t pointer */ + const int bpp = s->bpp; + uint8_t* data = surface_data(qemu_console_surface(s->con)); + + /* Width and height of the area to update */ + const int width = s->to_x - s->from_x; + const int height = s->to_y - s->from_y; + const int bytes_per_pixel = bpp/8; + const int total_bytes = width * height * bytes_per_pixel; + + if (address_space_access_valid(&s->vram_as, src, total_bytes, false, MEMTXATTRS_UNSPECIFIED)) { + src_as = &s->vram_as; + } else if (address_space_access_valid(&s->intram_as, src, total_bytes, false, MEMTXATTRS_UNSPECIFIED)) { + src_as = &s->intram_as; + } else { + s->update_area = false; +#if RGB_WARNING + warn_report("[ESP RGB] Invalid color content address or length"); +#endif + return; + } + + /* Only perform the copy if the area is valid */ + if (width > 0 && height > 0 && + (s->from_x + width) <= s->width && (s->from_y + height) <= s->height) { + + uint8_t* dest = data + (s->from_y * s->width + s->from_x) * bytes_per_pixel; + + /* Copy the pixels to the framebuffer */ + for (int i = 0; i < height; i++) { + dma_memory_read(src_as, src, dest, width * bytes_per_pixel, MEMTXATTRS_UNSPECIFIED); + /* Go to the next line in the destination */ + dest += s->width * bytes_per_pixel; + /* Same goes for the source */ + src += width * bytes_per_pixel; + } + + dpy_gfx_update(s->con, s->from_x, s->from_y, width, height); + } +#if RGB_WARNING + else { + warn_report("[ESP RGB] Invalid drawing area"); + } +#endif + + /* Automatically clear the update flag, the guest can re-use the given color_content buffer. + * It must set it again to trigger another update. */ + s->update_area = false; + } +} + + +static void rgb_invalidate(void *opaque) +{ + ESPRgbState* s = (ESPRgbState*) opaque; + + if (s->con) { + uint32_t* data = surface_data(qemu_console_surface(s->con)); + + /* On invalidate, reset the display */ + memset(data, 0, s->width * s->height * 4); + } +} + + +static const GraphicHwOps fb_ops = { + .invalidate = rgb_invalidate, + .gfx_update = rgb_update +}; + + +static void esp_rgb_realize(DeviceState *dev, Error **errp) +{ + ESPRgbState* s = ESP_RGB(dev); + + assert(s->intram != NULL); + /* Create an address space for internal RAM so that we can read data from it on GUI update */ + address_space_init(&s->intram_as, s->intram, "esp.rgb.intram_as"); +} + + +static const MemoryRegionOps esp_rgb_ops = { + .read = esp_rgb_read, + .write = esp_rgb_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + + +static void esp_rgb_init(Object *obj) +{ + ESPRgbState* s = ESP_RGB(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &esp_rgb_ops, s, + TYPE_ESP_RGB, ESP_RGB_IO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + /* Default window size */ + s->width = ESP_RGB_MAX_WIDTH; + s->height = ESP_RGB_MAX_HEIGHT; + s->update_area = false; + s->bpp = DEFAULT_BPP; + + if (s->con == NULL) { + s->con = graphic_console_init(DEVICE(s), 0, &fb_ops, s); + /* Resize and use corrent color bpp*/ + update_rgb_surface(s); + void * data = surface_data(qemu_console_surface(s->con)); + /* Initialize the window to black */ + memset(data, 0, (s->width * s->height * s->bpp) / 8); + } + + /* Create a memory region that can be used as a framebuffer by the guest */ + memory_region_init_ram(&s->vram, OBJECT(s), "esp-rgb-vram", ESP_RGB_MAX_VRAM_SIZE, &error_abort); + + /* Create an AddressSpace out of the MemoryRegion to be able to perform DMA */ + address_space_init(&s->vram_as, &s->vram, "esp.rgb.vram_as"); +} + + +static void esp_rgb_reset(DeviceState *dev) +{ + ESPRgbState* s = ESP_RGB(dev); + + /* Default window size */ + s->width = ESP_RGB_MAX_WIDTH; + s->height = ESP_RGB_MAX_HEIGHT; + s->update_area = false; + s->from_x = 0; + s->from_y = 0; + s->to_x = 0; + s->to_y = 0; +} + + +static void esp_rgb_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->legacy_reset = esp_rgb_reset; + dc->realize = esp_rgb_realize; +} + +static const TypeInfo esp_rgb_info = { + .name = TYPE_ESP_RGB, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ESPRgbState), + .instance_init = esp_rgb_init, + .class_init = esp_rgb_class_init, +}; + +static void esp_rgb_register_types(void) +{ + type_register_static(&esp_rgb_info); +} + +type_init(esp_rgb_register_types) diff --git a/hw/display/meson.build b/hw/display/meson.build index 20a94973fa..36a9ace56e 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -31,6 +31,7 @@ system_ss.add(when: 'CONFIG_TCX', if_true: files('tcx.c')) system_ss.add(when: 'CONFIG_CG3', if_true: files('cg3.c')) system_ss.add(when: 'CONFIG_MACFB', if_true: files('macfb.c')) system_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-fb.c')) +system_ss.add(when: 'CONFIG_ESP_RGB', if_true: files('esp_rgb.c')) system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c')) system_ss.add(when: 'CONFIG_VIRTIO', if_true: files('virtio-dmabuf.c')) diff --git a/include/hw/display/esp_rgb.h b/include/hw/display/esp_rgb.h new file mode 100644 index 0000000000..8aec1c0a6f --- /dev/null +++ b/include/hw/display/esp_rgb.h @@ -0,0 +1,80 @@ +#pragma once + +#include "hw/hw.h" +#include "hw/registerfields.h" + + +#define TYPE_ESP_RGB "display.esp.rgb" +#define ESP_RGB(obj) OBJECT_CHECK(ESPRgbState, (obj), TYPE_ESP_RGB) +#define ESP_RGB_GET_CLASS(obj) OBJECT_GET_CLASS(ESPRgbState, obj, TYPE_ESP_RGB) +#define ESP_RGB_CLASS(klass) OBJECT_CLASS_CHECK(ESPRgbState, klass, TYPE_ESP_RGB) + + +#define ESP_RGB_MAX_WIDTH (800) +#define ESP_RGB_MAX_HEIGHT (600) +#define ESP_RGB_MAX_VRAM_SIZE (ESP_RGB_MAX_WIDTH * ESP_RGB_MAX_HEIGHT * 4) + +#define DEFAULT_BPP (BPP_32) + +typedef enum { + BPP_16 = 16, + BPP_32 = 32 +} BppEnum; + +typedef struct ESPRgbState { + SysBusDevice parent_obj; + + QemuConsole *con; + MemoryRegion iomem; + MemoryRegion vram; + AddressSpace vram_as; + /* Internal RAM memory region */ + MemoryRegion* intram; + AddressSpace intram_as; + + /* Window size */ + uint32_t width; + uint32_t height; + bool do_update_surface; + + /* Update area */ + bool update_area; + uint32_t from_x; + uint32_t from_y; + uint32_t to_x; + uint32_t to_y; + uint32_t color_content; + + /* BPP */ + BppEnum bpp; +} ESPRgbState; + +#define ESP_RGB_IO_SIZE (A_RGB_BPP_VALUE + 4) + +REG32(RGB_VERSION, 0x00) + FIELD(RGB_VERSION, MAJOR, 16, 16) + FIELD(RGB_VERSION, MINOR, 0, 16) + +/* Used to resize the window */ +REG32(RGB_WIN_SIZE, 0x04) + FIELD(RGB_WIN_SIZE, WIDTH, 16, 16) + FIELD(RGB_WIN_SIZE, HEIGHT, 0, 16) + +/* Used to define the area to update on next window update */ +REG32(RGB_UPDATE_FROM, 0x08) + FIELD(RGB_UPDATE_FROM, X, 16, 16) + FIELD(RGB_UPDATE_FROM, Y, 0, 16) + +REG32(RGB_UPDATE_TO, 0x0c) + FIELD(RGB_UPDATE_TO, X, 16, 16) + FIELD(RGB_UPDATE_TO, Y, 0, 16) + +REG32(RGB_UPDATE_CONTENT, 0x10) + +REG32(RGB_UPDATE_STATUS, 0x14) + /* When set, only the area described above will be rendered + * on next window update. When not set, the window is not updated. + * Automatically cleared by the hardware after window update. */ + FIELD(RGB_UPDATE_STATUS, ENA, 0, 1) + +REG32(RGB_BPP_VALUE, 0x18) \ No newline at end of file