From dd530dbe4541a93eb87e411ee3ed9bf4b32ac016 Mon Sep 17 00:00:00 2001
From: Visual Ehrmanntraut
<30368284+VisualEhrmanntraut@users.noreply.github.com>
Date: Fri, 14 Feb 2025 16:13:18 +0200
Subject: [PATCH] feat: ADP Blend Unit
---
hw/arm/apple-silicon/t8030.c | 10 +-
hw/display/apple_displaypipe_v2.c | 544 -------------
hw/display/apple_displaypipe_v4.c | 743 ++++++++++++++++++
hw/display/meson.build | 2 +-
...isplaypipe_v2.h => apple_displaypipe_v4.h} | 36 +-
5 files changed, 771 insertions(+), 564 deletions(-)
delete mode 100644 hw/display/apple_displaypipe_v2.c
create mode 100644 hw/display/apple_displaypipe_v4.c
rename include/hw/display/{apple_displaypipe_v2.h => apple_displaypipe_v4.h} (68%)
diff --git a/hw/arm/apple-silicon/t8030.c b/hw/arm/apple-silicon/t8030.c
index f466afd869..569fb36cda 100644
--- a/hw/arm/apple-silicon/t8030.c
+++ b/hw/arm/apple-silicon/t8030.c
@@ -36,7 +36,7 @@
#include "hw/arm/apple-silicon/xnu_pf.h"
#include "hw/block/apple_ans.h"
#include "hw/char/apple_uart.h"
-#include "hw/display/apple_displaypipe_v2.h"
+#include "hw/display/apple_displaypipe_v4.h"
#include "hw/dma/apple_sio.h"
#include "hw/gpio/apple_gpio.h"
#include "hw/i2c/apple_i2c.h"
@@ -801,8 +801,8 @@ static void t8030_memory_setup(T8030MachineState *t8030_machine)
vram_reg[1] = T8030_DISPLAY_SIZE;
dtb_set_prop(vram, "reg", sizeof(vram_reg), &vram_reg);
t8030_machine->video_args.base_addr = vram_reg[0];
- AppleDisplayPipeV2State *display =
- APPLE_DISPLAYPIPE_V2(object_property_get_link(
+ AppleDisplayPipeV4State *display =
+ APPLE_DISPLAY_PIPE_V4(object_property_get_link(
OBJECT(t8030_machine), "disp0", &error_abort));
if (memory_region_is_mapped(&display->vram)) {
memory_region_del_subregion(t8030_machine->sysmem, &display->vram);
@@ -2074,7 +2074,7 @@ static void t8030_create_misc(T8030MachineState *t8030_machine)
static void t8030_create_display(T8030MachineState *t8030_machine)
{
MachineState *machine;
- AppleDisplayPipeV2State *s;
+ AppleDisplayPipeV4State *s;
SysBusDevice *sbd;
DTBNode *child;
uint64_t *reg;
@@ -2084,7 +2084,7 @@ static void t8030_create_display(T8030MachineState *t8030_machine)
child = dtb_get_node(t8030_machine->device_tree, "arm-io/disp0");
- s = apple_displaypipe_v2_create(child);
+ s = adp_v4_create(child);
sbd = SYS_BUS_DEVICE(s);
t8030_machine->video_args.row_bytes = s->width * 4;
diff --git a/hw/display/apple_displaypipe_v2.c b/hw/display/apple_displaypipe_v2.c
deleted file mode 100644
index 05b4b02e44..0000000000
--- a/hw/display/apple_displaypipe_v2.c
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * Apple Display Pipe V2 Controller.
- *
- * Copyright (c) 2023-2025 Visual Ehrmanntraut (VisualEhrmanntraut).
- * Copyright (c) 2023-2025 Christian Inci (chris-pcguy).
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see .
- */
-
-#include "qemu/osdep.h"
-#include "block/aio.h"
-#include "exec/memory.h"
-#include "hw/display/apple_displaypipe_v2.h"
-#include "hw/irq.h"
-#include "hw/qdev-properties.h"
-#include "hw/resettable.h"
-#include "qemu/error-report.h"
-#include "qom/object.h"
-#include "sysemu/dma.h"
-#include "ui/console.h"
-#include "ui/pixel_ops.h"
-#include "framebuffer.h"
-
-// #define DEBUG_DISP
-
-#ifdef DEBUG_DISP
-#define DISP_DBGLOG(fmt, ...) info_report(fmt, __VA_ARGS__)
-#else
-#define DISP_DBGLOG(fmt, ...) \
- do { \
- } while (0);
-#endif
-
-/**
- * Block Bases (DisplayTarget5)
- * 0x08000 | M3 Control Mailbox
- * 0x0A000 | M3 Video Mode Mailbox
- * 0x40000 | Control
- * 0x48000 | Vertical Frame Timing Generator
- * 0x50000 | Generic Pipe 0
- * 0x58000 | Generic Pipe 1
- * 0x60000 | Blend
- * 0x70000 | White Point Correction
- * 0x7C000 | Panel Response Correction
- * 0x80000 | Dither
- * 0x82000 | Dither: Enchanced ST Dither 0
- * 0x83000 | Dither: Enchanced ST Dither 1
- * 0x84000 | Content Dependent Frame Duration
- * 0x88000 | SPLR (Sub-Pixel Layout R?)
- * 0x90000 | Burn-In Compensation Sampler
- * 0x98000 | SPUC
- * 0xA0000 | PDC (Panel D? Correction?)
- * 0xB0000 | PCC (Pixel Color Correction?)
- * 0xD0000 | PCC Mailbox
- * 0xF0000 | DBM (Dynamic Backlight Modulation?)
- */
-
-#define REG_CONTROL_INT_STATUS (0x45818)
-#define CONTROL_INT_STATUS_MODE_CHANGED BIT(1)
-#define CONTROL_INT_STATUS_DISP_UNDERRUN BIT(3)
-#define CONTROL_INT_STATUS_VBLANK BIT(10) // "Swap Done"
-#define CONTROL_INT_STATUS_SUB_FRAME_OVERFLOW BIT(11)
-#define CONTROL_INT_STATUS_M3 BIT(13)
-#define CONTROL_INT_STATUS_PCC BIT(17)
-// #define CONTROL_INT_STATUS_?? BIT(19) // "Start accumulating"
-// #define CONTROL_INT_STATUS_?? BIT(20) // "Frame Processed"
-#define CONTROL_INT_STATUS_AXI_READ_ERR BIT(30)
-#define CONTROL_INT_STATUS_AXI_WRITE_ERR BIT(31)
-#define REG_CONTROL_VERSION (0x46020)
-#define CONTROL_VERSION_A0 (0x70044)
-#define CONTROL_VERSION_A1 (0x70045)
-#define REG_CONTROL_FRAME_SIZE (0x4603C)
-#define REG_CONTROL_CONFIG (0x46040)
-#define REG_CONTROL_OUT_FIFO_CLK_GATE (0x46074)
-#define REG_CONTROL_OUT_FIFO_DEPTH (0x46084)
-#define REG_CONTROL_COMPRESSION_CFG (0x460E0)
-#define REG_CONTROL_BACKPRESSURE (0x46120)
-#define REG_CONTROL_POWER_GATE_CTRL (0x46158)
-#define REG_CONTROL_BIS_UPDATE_INTERVAL (0x46198)
-#define REG_CONTROL_MIN_BANDWIDTH_RATE (0x461C0)
-#define REG_CONTROL_BANDWIDTH_RATE_SCALE_FACTOR (0x461C4)
-#define REG_CONTROL_PIO_DMA_BANDWIDTH_RATE (0x461C8)
-#define REG_CONTROL_REPLAY_DMA_BANDWIDTH_RATE (0x461CC)
-#define REG_CONTROL_GATE_CONTROL (0x461D0)
-#define REG_CONTROL_READ_LINK_GATE_METRIC (0x461D4)
-#define REG_CONTROL_READ_LTR_CONFIG (0x461D8)
-#define REG_CONTROL_LTR_TIMER (0x461DC)
-#define REG_CONTROL_WRITE_LTR_CONFIG (0x461E0)
-
-#define GP_BLOCK_BASE (0x50000)
-#define REG_GP_REG_SIZE (0x8000)
-#define REG_GP_CONFIG_CONTROL (0x4)
-#define GP_CONFIG_CONTROL_RUN BIT(0)
-#define GP_CONFIG_CONTROL_USE_DMA BIT(18)
-#define GP_CONFIG_CONTROL_HDR BIT(24)
-#define GP_CONFIG_CONTROL_ENABLED BIT(31)
-#define REG_GP_PIXEL_FORMAT (0x0001C)
-#define GP_PIXEL_FORMAT_BGRA ((BIT(4) << 22) | BIT(24) | (3 << 13))
-#define GP_PIXEL_FORMAT_ARGB ((BIT(4) << 22) | BIT(24))
-#define GP_PIXEL_FORMAT_COMPRESSED BIT(30)
-#define REG_GP_BASE (0x30)
-#define REG_GP_END (0x40)
-#define REG_GP_STRIDE (0x60)
-#define REG_GP_SIZE (0x70)
-#define REG_GP_FRAME_SIZE (0x80)
-#define REG_GP_CRC_DATA (0x160)
-#define REG_GP_BANDWIDTH_RATE (0x170)
-#define REG_GP_STATUS (0x184)
-#define GP_STATUS_DECOMPRESSION_FAIL BIT(0)
-
-#define GP_BLOCK_BASE_FOR(i) (GP_BLOCK_BASE + i * REG_GP_REG_SIZE)
-#define GP_BLOCK_END_FOR(i) (GP_BLOCK_BASE_FOR(i) + (REG_GP_REG_SIZE - 1))
-
-static void apple_disp_update_irqs(AppleDisplayPipeV2State *s)
-{
- qemu_set_irq(s->irqs[0], (s->int_status & CONTROL_INT_STATUS_VBLANK) != 0);
-}
-
-static void apple_disp_gp_reg_write(GenPipeState *s, hwaddr addr, uint64_t data)
-{
- switch (addr - GP_BLOCK_BASE_FOR(s->index)) {
- case REG_GP_CONFIG_CONTROL: {
- DISP_DBGLOG("[GP%zu] Control <- 0x" HWADDR_FMT_plx, s->index, data);
- s->config_control = (uint32_t)data;
- if (data & GP_CONFIG_CONTROL_RUN) {
- qemu_bh_schedule(s->bh);
- }
- break;
- }
- case REG_GP_PIXEL_FORMAT: {
- DISP_DBGLOG("[GP%zu] Pixel format <- 0x" HWADDR_FMT_plx, s->index,
- data);
- s->pixel_format = (uint32_t)data;
- break;
- }
- case REG_GP_BASE: {
- DISP_DBGLOG("[GP%zu] Base <- 0x" HWADDR_FMT_plx, s->index, data);
- s->base = (uint32_t)data;
- break;
- }
- case REG_GP_END: {
- DISP_DBGLOG("[GP%zu] End <- 0x" HWADDR_FMT_plx, s->index, data);
- s->end = (uint32_t)data;
- break;
- }
- case REG_GP_STRIDE: {
- DISP_DBGLOG("[GP%zu] Stride <- 0x" HWADDR_FMT_plx, s->index, data);
- s->stride = (uint32_t)data;
- break;
- }
- case REG_GP_SIZE: {
- DISP_DBGLOG("[GP%zu] Size <- 0x" HWADDR_FMT_plx, s->index, data);
- s->size = (uint32_t)data;
- break;
- }
- case REG_GP_FRAME_SIZE: {
- DISP_DBGLOG("[GP%zu] Frame Size <- 0x" HWADDR_FMT_plx, s->index, data);
- s->height = data & 0xFFFF;
- s->width = (data >> 16) & 0xFFFF;
- break;
- }
- default: {
- DISP_DBGLOG("[GP%zu] Unknown @ 0x" HWADDR_FMT_plx
- " <- 0x" HWADDR_FMT_plx,
- s->index, addr, data);
- break;
- }
- }
-}
-
-static uint32_t apple_disp_gp_reg_read(GenPipeState *s, hwaddr addr)
-{
- switch (addr - GP_BLOCK_BASE_FOR(s->index)) {
- case REG_GP_CONFIG_CONTROL: {
- DISP_DBGLOG("[GP%zu] Control -> 0x%x", s->index, s->config_control);
- return s->config_control;
- }
- case REG_GP_PIXEL_FORMAT: {
- DISP_DBGLOG("[GP%zu] Pixel format -> 0x%x", s->index, s->pixel_format);
- return s->pixel_format;
- }
- case REG_GP_BASE: {
- DISP_DBGLOG("[GP%zu] Base -> 0x%x", s->index, s->base);
- return s->base;
- }
- case REG_GP_END: {
- DISP_DBGLOG("[GP%zu] End -> 0x%x", s->index, s->end);
- return s->end;
- }
- case REG_GP_STRIDE: {
- DISP_DBGLOG("[GP%zu] Stride -> 0x%x", s->index, s->stride);
- return s->stride;
- }
- case REG_GP_SIZE: {
- DISP_DBGLOG("[GP%zu] Size -> 0x%x", s->index, s->size);
- return s->size;
- }
- case REG_GP_FRAME_SIZE: {
- DISP_DBGLOG("[GP%zu] Frame Size -> 0x%x (width: %d height: %d)",
- s->index, (s->width << 16) | s->height, s->width,
- s->height);
- return (s->width << 16) | s->height;
- }
- default: {
- DISP_DBGLOG("[GP%zu] Unknown @ 0x" HWADDR_FMT_plx
- " -> 0x" HWADDR_FMT_plx,
- s->index, addr, (hwaddr)0);
- return 0;
- }
- }
-}
-
-static void apple_gp_draw_bh(void *opaque)
-{
- GenPipeState *s;
- uint16_t height;
- uint16_t width;
- pixman_format_code_t src_fmt;
- uint8_t *buf;
-
- s = (GenPipeState *)opaque;
-
- // TODO: Decompress the data and display it properly.
- if (s->pixel_format & GP_PIXEL_FORMAT_COMPRESSED) {
- error_report("[GP%zu] Dropping frame as it's compressed.", s->index);
- return;
- }
-
- height = s->size & 0xFFFF;
- width = (s->size >> 16) & 0xFFFF;
-
- DISP_DBGLOG("[GP%zu] Width and height is %dx%d.", s->index, width, height);
- DISP_DBGLOG("[GP%zu] Stride is %d.", s->index, s->stride);
-
- if (height == 0 || width == 0 || s->stride == 0) {
- error_report(
- "[GP%zu] Dropping frame as width, height or stride is zero.",
- s->index);
- return;
- }
-
- if (width > s->disp_width || height > s->disp_height) {
- error_report("[GP%zu] Dropping frame as it's larger than the screen.",
- s->index);
- return;
- }
-
- if ((s->pixel_format & GP_PIXEL_FORMAT_BGRA) == GP_PIXEL_FORMAT_BGRA) {
- DISP_DBGLOG("[GP%zu] Pixel Format is BGRA (0x%X).", s->index,
- s->pixel_format);
- src_fmt = PIXMAN_b8g8r8a8;
- } else if ((s->pixel_format & GP_PIXEL_FORMAT_ARGB) ==
- GP_PIXEL_FORMAT_ARGB) {
- DISP_DBGLOG("[GP%zu] Pixel Format is ARGB (0x%X).", s->index,
- s->pixel_format);
- src_fmt = PIXMAN_a8r8g8b8;
- } else {
- error_report("[GP%zu] Pixel Format is unknown (0x%X).", s->index,
- s->pixel_format);
- return;
- }
-
- buf = g_malloc(height * width * s->stride);
- if (dma_memory_read(s->dma_as, s->base, buf, s->end - s->base,
- MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
- error_report("[GP%zu] Failed to read from DMA.", s->index);
- g_free(buf);
- return;
- }
-
- // TODO: Where is the destination X and Y?
- pixman_image_t *image = pixman_image_create_bits(
- src_fmt, width, height, (uint32_t *)buf, s->stride);
- pixman_image_composite32(PIXMAN_OP_OVER, image, NULL, s->disp_image, 0, 0,
- 0, 0, 0, 0, width, height);
- pixman_image_unref(image);
- g_free(buf);
- memory_region_set_dirty(s->vram, 0, height * s->width * sizeof(uint32_t));
-}
-
-static void apple_genpipev2_init(GenPipeState *s, size_t index,
- MemoryRegion *vram, AddressSpace *dma_as,
- pixman_image_t *disp_image,
- uint16_t disp_width, uint16_t disp_height)
-{
- if (s->bh != NULL) {
- qemu_bh_delete(s->bh);
- }
-
- memset(s, 0, sizeof(*s));
- s->index = index;
- s->vram = vram;
- s->dma_as = dma_as;
- s->bh = qemu_bh_new(apple_gp_draw_bh, s);
- s->disp_image = disp_image;
- s->disp_width = disp_width;
- s->disp_height = disp_height;
-}
-
-static void apple_disp_reg_write(void *opaque, hwaddr addr, uint64_t data,
- unsigned size)
-{
- AppleDisplayPipeV2State *s;
-
- s = APPLE_DISPLAYPIPE_V2(opaque);
-
- if (addr >= 0x200000) {
- addr -= 0x200000;
- }
-
- switch (addr) {
- case GP_BLOCK_BASE_FOR(0)... GP_BLOCK_END_FOR(0): {
- apple_disp_gp_reg_write(&s->genpipes[0], addr, data);
- break;
- }
- case GP_BLOCK_BASE_FOR(1)... GP_BLOCK_END_FOR(1): {
- apple_disp_gp_reg_write(&s->genpipes[1], addr, data);
- break;
- }
- case REG_CONTROL_INT_STATUS: {
- s->int_status &= ~(uint32_t)data;
- apple_disp_update_irqs(s);
- break;
- }
- default: {
- DISP_DBGLOG("[disp] Unknown @ 0x" HWADDR_FMT_plx
- " <- 0x" HWADDR_FMT_plx,
- addr, data);
- break;
- }
- }
-}
-
-static uint64_t apple_disp_reg_read(void *opaque, hwaddr addr,
- const unsigned size)
-{
- AppleDisplayPipeV2State *s;
-
- s = APPLE_DISPLAYPIPE_V2(opaque);
-
- if (addr >= 0x200000) {
- addr -= 0x200000;
- }
-
- switch (addr) {
- case GP_BLOCK_BASE_FOR(0)... GP_BLOCK_END_FOR(0): {
- return apple_disp_gp_reg_read(&s->genpipes[0], addr);
- }
- case GP_BLOCK_BASE_FOR(1)... GP_BLOCK_END_FOR(1): {
- return apple_disp_gp_reg_read(&s->genpipes[1], addr);
- }
- case REG_CONTROL_VERSION: {
- DISP_DBGLOG("[disp] Version -> 0x%x", CONTROL_VERSION_A0);
- return CONTROL_VERSION_A0;
- }
- case REG_CONTROL_FRAME_SIZE: {
- DISP_DBGLOG("[disp] Frame Size -> 0x%x", (s->width << 16) | s->height);
- return (s->width << 16) | s->height;
- }
- case REG_CONTROL_INT_STATUS: {
- DISP_DBGLOG("[disp] Int Status -> 0x%x", s->int_status);
- return s->int_status;
- }
- default: {
- DISP_DBGLOG("[disp] Unknown @ 0x" HWADDR_FMT_plx
- " -> 0x" HWADDR_FMT_plx,
- addr, (hwaddr)0);
- return 0;
- }
- }
-}
-
-static const MemoryRegionOps apple_disp_v2_reg_ops = {
- .write = apple_disp_reg_write,
- .read = apple_disp_reg_read,
- .endianness = DEVICE_NATIVE_ENDIAN,
- .impl.min_access_size = 4,
- .impl.max_access_size = 4,
- .valid.min_access_size = 4,
- .valid.max_access_size = 4,
- .valid.unaligned = false,
-};
-
-static uint32_t disp_timing_info[] = { 0x33C, 0x90, 0x1, 0x1,
- 0x700, 0x1, 0x1, 0x1 };
-
-AppleDisplayPipeV2State *apple_displaypipe_v2_create(DTBNode *node)
-{
- DeviceState *dev;
- SysBusDevice *sbd;
- AppleDisplayPipeV2State *s;
- DTBProp *prop;
- uint64_t *reg;
- int i;
-
- dev = qdev_new(TYPE_APPLE_DISPLAYPIPE_V2);
- sbd = SYS_BUS_DEVICE(dev);
- s = APPLE_DISPLAYPIPE_V2(sbd);
-
- dtb_set_prop(node, "display-target", 15, "DisplayTarget5");
- dtb_set_prop(node, "display-timing-info", sizeof(disp_timing_info),
- disp_timing_info);
- dtb_set_prop_u32(node, "bics-param-set", 0xD);
- dtb_set_prop_u32(node, "dot-pitch", 326);
- dtb_set_prop_null(node, "function-brightness_update");
-
- prop = dtb_find_prop(node, "reg");
- g_assert_nonnull(prop);
- reg = (uint64_t *)prop->data;
- memory_region_init_io(&s->up_regs, OBJECT(sbd), &apple_disp_v2_reg_ops, sbd,
- "up.regs", reg[1]);
- sysbus_init_mmio(sbd, &s->up_regs);
- object_property_add_const_link(OBJECT(sbd), "up.regs", OBJECT(&s->up_regs));
-
- for (i = 0; i < 9; i++) {
- sysbus_init_irq(sbd, &s->irqs[i]);
- }
-
- return s;
-}
-
-static void apple_displaypipe_v2_draw_row(void *opaque, uint8_t *dest,
- const uint8_t *src, int width,
- int dest_pitch)
-{
- while (width--) {
- uint32_t colour = ldl_le_p(src);
- memcpy(dest, &colour, sizeof(colour));
- src += sizeof(colour);
- dest += sizeof(colour);
- }
-}
-
-static void apple_displaypipe_v2_invalidate(void *opaque)
-{
- AppleDisplayPipeV2State *s = APPLE_DISPLAYPIPE_V2(opaque);
- s->invalidated = true;
-}
-
-static void apple_displaypipe_v2_gfx_update(void *opaque)
-{
- AppleDisplayPipeV2State *s = APPLE_DISPLAYPIPE_V2(opaque);
- DisplaySurface *surface = qemu_console_surface(s->console);
-
- int stride = s->width * sizeof(uint32_t);
- int first = 0, last = 0;
-
- if (s->invalidated) {
- framebuffer_update_memory_section(&s->vram_section, &s->vram, 0,
- s->height, stride);
- s->invalidated = false;
- }
-
- framebuffer_update_display(surface, &s->vram_section, s->width, s->height,
- stride, stride, 0, 0,
- apple_displaypipe_v2_draw_row, s, &first, &last);
- if (first >= 0) {
- dpy_gfx_update(s->console, 0, first, s->width, last - first + 1);
- }
-
- s->int_status |= CONTROL_INT_STATUS_VBLANK;
- apple_disp_update_irqs(s);
-}
-
-static const GraphicHwOps apple_displaypipe_v2_ops = {
- .invalidate = apple_displaypipe_v2_invalidate,
- .gfx_update = apple_displaypipe_v2_gfx_update,
-};
-
-static void apple_displaypipe_v2_reset_hold(Object *obj, ResetType type)
-{
- AppleDisplayPipeV2State *s = APPLE_DISPLAYPIPE_V2(obj);
-
- s->invalidated = true;
-
- s->int_status = 0;
- apple_disp_update_irqs(s);
-
- qemu_pixman_image_unref(s->disp_image);
- s->disp_image = pixman_image_create_bits(
- PIXMAN_a8r8g8b8, s->width, s->height,
- (uint32_t *)memory_region_get_ram_ptr(&s->vram),
- s->width * sizeof(uint32_t));
-
- apple_genpipev2_init(&s->genpipes[0], 0, &s->vram, &s->dma_as,
- s->disp_image, s->width, s->height);
- apple_genpipev2_init(&s->genpipes[1], 1, &s->vram, &s->dma_as,
- s->disp_image, s->width, s->height);
-
- memset(memory_region_get_ram_ptr(&s->vram), 0,
- memory_region_size(&s->vram));
- memory_region_set_dirty(&s->vram, 0, memory_region_size(&s->vram));
-}
-
-static void apple_displaypipe_v2_realize(DeviceState *dev, Error **errp)
-{
- AppleDisplayPipeV2State *s = APPLE_DISPLAYPIPE_V2(dev);
-
- s->console = graphic_console_init(dev, 0, &apple_displaypipe_v2_ops, s);
- qemu_console_resize(s->console, s->width, s->height);
-}
-
-static Property apple_displaypipe_v2_props[] = {
- DEFINE_PROP_UINT32("width", AppleDisplayPipeV2State, width, 828),
- DEFINE_PROP_UINT32("height", AppleDisplayPipeV2State, height, 1792),
- DEFINE_PROP_END_OF_LIST(),
-};
-
-static void apple_displaypipe_v2_class_init(ObjectClass *klass, void *data)
-{
- ResettableClass *rc = RESETTABLE_CLASS(klass);
- DeviceClass *dc = DEVICE_CLASS(klass);
-
- rc->phases.hold = apple_displaypipe_v2_reset_hold;
-
- device_class_set_props(dc, apple_displaypipe_v2_props);
- dc->realize = apple_displaypipe_v2_realize;
- set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
-}
-
-static const TypeInfo apple_displaypipe_v2_type_info = {
- .name = TYPE_APPLE_DISPLAYPIPE_V2,
- .parent = TYPE_SYS_BUS_DEVICE,
- .instance_size = sizeof(AppleDisplayPipeV2State),
- .class_init = apple_displaypipe_v2_class_init,
-};
-
-static void apple_displaypipe_v2_register_types(void)
-{
- type_register_static(&apple_displaypipe_v2_type_info);
-}
-
-type_init(apple_displaypipe_v2_register_types);
diff --git a/hw/display/apple_displaypipe_v4.c b/hw/display/apple_displaypipe_v4.c
new file mode 100644
index 0000000000..6ce99cac51
--- /dev/null
+++ b/hw/display/apple_displaypipe_v4.c
@@ -0,0 +1,743 @@
+/*
+ * Apple Display Pipe V4 Controller.
+ *
+ * Copyright (c) 2023-2025 Visual Ehrmanntraut (VisualEhrmanntraut).
+ * Copyright (c) 2023-2025 Christian Inci (chris-pcguy).
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#include "qemu/osdep.h"
+#include "exec/memory.h"
+#include "hw/display/apple_displaypipe_v4.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/resettable.h"
+#include "qemu/error-report.h"
+#include "qom/object.h"
+#include "sysemu/dma.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "framebuffer.h"
+#include "pixman.h"
+
+#define DEBUG_DISP
+
+#ifdef DEBUG_DISP
+#define ADP_INFO(fmt, ...) info_report(fmt, __VA_ARGS__)
+#else
+#define ADP_INFO(fmt, ...) \
+ do { \
+ } while (0);
+#endif
+
+/**
+ * Block Bases (DisplayTarget5)
+ * 0x08000 | M3 Control Mailbox
+ * 0x0A000 | M3 Video Mode Mailbox
+ * 0x40000 | Control
+ * 0x48000 | Vertical Frame Timing Generator
+ * 0x50000 | Generic Pixel Pipe 0
+ * 0x58000 | Generic Pixel Pipe 1
+ * 0x60000 | Blend Unit
+ * 0x70000 | White Point Correction
+ * 0x7C000 | Panel Response Correction
+ * 0x80000 | Dither
+ * 0x82000 | Dither: Enchanced ST Dither 0
+ * 0x83000 | Dither: Enchanced ST Dither 1
+ * 0x84000 | Content-Dependent Frame Duration
+ * 0x88000 | SPLR (Sub-Pixel Layout R?)
+ * 0x90000 | Burn-In Compensation Sampler
+ * 0x98000 | SPUC
+ * 0xA0000 | PDC (Panel D? Correction?)
+ * 0xB0000 | PCC (Pixel Color Correction?)
+ * 0xD0000 | PCC Mailbox
+ * 0xF0000 | DBM (Dynamic Backlight Modulation?)
+ */
+
+#define REG_CONTROL_INT_STATUS (0x45818)
+#define CONTROL_INT_STATUS_MODE_CHANGED BIT(1)
+#define CONTROL_INT_STATUS_DISP_UNDERRUN BIT(3)
+#define CONTROL_INT_STATUS_VBLANK BIT(10) // "Swap Done"
+#define CONTROL_INT_STATUS_SUB_FRAME_OVERFLOW BIT(11)
+#define CONTROL_INT_STATUS_M3 BIT(13)
+#define CONTROL_INT_STATUS_PCC BIT(17)
+// #define CONTROL_INT_STATUS_?? BIT(19) // "Start accumulating"
+// #define CONTROL_INT_STATUS_?? BIT(20) // "Frame Processed"
+#define CONTROL_INT_STATUS_AXI_READ_ERR BIT(30)
+#define CONTROL_INT_STATUS_AXI_WRITE_ERR BIT(31)
+#define REG_CONTROL_VERSION (0x46020)
+#define CONTROL_VERSION_A0 (0x70044)
+#define CONTROL_VERSION_A1 (0x70045)
+#define REG_CONTROL_FRAME_SIZE (0x4603C)
+#define REG_CONTROL_CONFIG (0x46040)
+#define REG_CONTROL_OUT_FIFO_CLK_GATE (0x46074)
+#define REG_CONTROL_OUT_FIFO_DEPTH (0x46084)
+#define REG_CONTROL_COMPRESSION_CFG (0x460E0)
+#define REG_CONTROL_BACKPRESSURE (0x46120)
+#define REG_CONTROL_POWER_GATE_CTRL (0x46158)
+#define REG_CONTROL_BIS_UPDATE_INTERVAL (0x46198)
+#define REG_CONTROL_MIN_BANDWIDTH_RATE (0x461C0)
+#define REG_CONTROL_BANDWIDTH_RATE_SCALE_FACTOR (0x461C4)
+#define REG_CONTROL_PIO_DMA_BANDWIDTH_RATE (0x461C8)
+#define REG_CONTROL_REPLAY_DMA_BANDWIDTH_RATE (0x461CC)
+#define REG_CONTROL_GATE_CONTROL (0x461D0)
+#define REG_CONTROL_READ_LINK_GATE_METRIC (0x461D4)
+#define REG_CONTROL_READ_LTR_CONFIG (0x461D8)
+#define REG_CONTROL_LTR_TIMER (0x461DC)
+#define REG_CONTROL_WRITE_LTR_CONFIG (0x461E0)
+
+#define GP_BLOCK_BASE (0x50000)
+#define GP_BLOCK_SIZE (0x8000)
+#define REG_GP_CONFIG_CONTROL (0x4)
+#define GP_CONFIG_CONTROL_RUN BIT(0)
+#define GP_CONFIG_CONTROL_USE_DMA BIT(18)
+#define GP_CONFIG_CONTROL_HDR BIT(24)
+#define GP_CONFIG_CONTROL_ENABLED BIT(31)
+#define REG_GP_PIXEL_FORMAT (0x0001C)
+#define GP_PIXEL_FORMAT_BGRA ((BIT(4) << 22) | BIT(24) | (3 << 13))
+#define GP_PIXEL_FORMAT_ARGB ((BIT(4) << 22) | BIT(24))
+#define GP_PIXEL_FORMAT_COMPRESSED BIT(30)
+#define REG_GP_BASE (0x30)
+#define REG_GP_END (0x40)
+#define REG_GP_STRIDE (0x60)
+#define REG_GP_SIZE (0x70)
+#define REG_GP_FRAME_SIZE (0x80)
+#define REG_GP_CRC_DATA (0x160)
+#define REG_GP_BANDWIDTH_RATE (0x170)
+#define REG_GP_STATUS (0x184)
+#define GP_STATUS_DECOMPRESSION_FAIL BIT(0)
+
+#define GP_BLOCK_BASE_FOR(i) (GP_BLOCK_BASE + i * GP_BLOCK_SIZE)
+#define GP_BLOCK_END_FOR(i) (GP_BLOCK_BASE_FOR(i) + (GP_BLOCK_SIZE - 1))
+
+#define BLEND_BLOCK_BASE (0x60000)
+#define BLEND_BLOCK_SIZE (0x8000)
+#define REG_BLEND_CONFIG (0x4)
+#define REG_BLEND_BG (0x8)
+#define REG_BLEND_LAYER_0_BG (0xC)
+#define REG_BLEND_LAYER_1_BG (0x10)
+#define REG_BLEND_LAYER_0_CONFIG (0x14)
+#define REG_BLEND_LAYER_1_CONFIG (0x18)
+#define BLEND_LAYER_CONFIG_PIPE(v) ((v) & 0xF)
+#define BLEND_LAYER_CONFIG_MODE(v) ((v >> 4) & 0xF)
+#define BLEND_MODE_NONE 0
+#define BLEND_MODE_ALPHA 1
+#define BLEND_MODE_PREMULT 2
+#define BLEND_MODE_BYPASS 3
+#define REG_BLEND_GAMMA_TABLE_R (0x1C)
+#define REG_BLEND_GAMMA_TABLE_G (0x1024)
+#define REG_BLEND_GAMMA_TABLE_B (0x202C)
+// #define REG_BLEND_?? (0x3034)
+
+static void adp_update_irqs(AppleDisplayPipeV4State *s)
+{
+ qemu_set_irq(s->irqs[0], (s->int_status & CONTROL_INT_STATUS_VBLANK) != 0);
+}
+
+static void adp_gp_reg_write(ADPGenPipeState *s, hwaddr addr, uint64_t data)
+{
+ switch (addr) {
+ case REG_GP_CONFIG_CONTROL: {
+ ADP_INFO("[gp%zu] Control <- 0x" HWADDR_FMT_plx, s->index, data);
+ s->config_control = (uint32_t)data;
+ if (s->config_control & GP_CONFIG_CONTROL_RUN) {
+ s->dirty = true;
+ }
+ break;
+ }
+ case REG_GP_PIXEL_FORMAT: {
+ ADP_INFO("[gp%zu] Pixel format <- 0x" HWADDR_FMT_plx, s->index, data);
+ s->pixel_format = (uint32_t)data;
+ break;
+ }
+ case REG_GP_BASE: {
+ ADP_INFO("[gp%zu] Base <- 0x" HWADDR_FMT_plx, s->index, data);
+ s->base = (uint32_t)data;
+ break;
+ }
+ case REG_GP_END: {
+ ADP_INFO("[gp%zu] End <- 0x" HWADDR_FMT_plx, s->index, data);
+ s->end = (uint32_t)data;
+ break;
+ }
+ case REG_GP_STRIDE: {
+ ADP_INFO("[gp%zu] Stride <- 0x" HWADDR_FMT_plx, s->index, data);
+ s->stride = (uint32_t)data;
+ break;
+ }
+ case REG_GP_SIZE: {
+ ADP_INFO("[gp%zu] Size <- 0x" HWADDR_FMT_plx, s->index, data);
+ s->buf_height = data & 0xFFFF;
+ s->buf_width = (data >> 16) & 0xFFFF;
+ break;
+ }
+ case REG_GP_FRAME_SIZE: {
+ ADP_INFO("[gp%zu] Frame Size <- 0x" HWADDR_FMT_plx, s->index, data);
+ s->height = data & 0xFFFF;
+ s->width = (data >> 16) & 0xFFFF;
+ break;
+ }
+ default: {
+ ADP_INFO("[gp%zu] Unknown @ 0x" HWADDR_FMT_plx " <- 0x" HWADDR_FMT_plx,
+ s->index, addr, data);
+ break;
+ }
+ }
+}
+
+static uint32_t adp_gp_reg_read(ADPGenPipeState *s, hwaddr addr)
+{
+ switch (addr) {
+ case REG_GP_CONFIG_CONTROL: {
+ ADP_INFO("[gp%zu] Control -> 0x%x", s->index, s->config_control);
+ return s->config_control;
+ }
+ case REG_GP_PIXEL_FORMAT: {
+ ADP_INFO("[gp%zu] Pixel format -> 0x%x", s->index, s->pixel_format);
+ return s->pixel_format;
+ }
+ case REG_GP_BASE: {
+ ADP_INFO("[gp%zu] Base -> 0x%x", s->index, s->base);
+ return s->base;
+ }
+ case REG_GP_END: {
+ ADP_INFO("[gp%zu] End -> 0x%x", s->index, s->end);
+ return s->end;
+ }
+ case REG_GP_STRIDE: {
+ ADP_INFO("[gp%zu] Stride -> 0x%x", s->index, s->stride);
+ return s->stride;
+ }
+ case REG_GP_SIZE: {
+ ADP_INFO("[gp%zu] Size -> 0x%x", s->index,
+ (s->buf_width << 16) | s->buf_height);
+ return (s->buf_width << 16) | s->buf_height;
+ }
+ case REG_GP_FRAME_SIZE: {
+ ADP_INFO("[gp%zu] Frame Size -> 0x%x (width: %d height: %d)", s->index,
+ (s->width << 16) | s->height, s->width, s->height);
+ return (s->width << 16) | s->height;
+ }
+ default: {
+ ADP_INFO("[gp%zu] Unknown @ 0x" HWADDR_FMT_plx " -> 0x" HWADDR_FMT_plx,
+ s->index, addr, (hwaddr)0);
+ return 0;
+ }
+ }
+}
+
+static pixman_format_code_t adp_gp_fmt_to_pixman(ADPGenPipeState *s)
+{
+ if ((s->pixel_format & GP_PIXEL_FORMAT_BGRA) == GP_PIXEL_FORMAT_BGRA) {
+ ADP_INFO("[gp%zu] Pixel Format is BGRA (0x%X).", s->index,
+ s->pixel_format);
+ return PIXMAN_b8g8r8a8;
+ } else if ((s->pixel_format & GP_PIXEL_FORMAT_ARGB) ==
+ GP_PIXEL_FORMAT_ARGB) {
+ ADP_INFO("[gp%zu] Pixel Format is ARGB (0x%X).", s->index,
+ s->pixel_format);
+ return PIXMAN_a8r8g8b8;
+ } else {
+ error_report("[gp%zu] Pixel Format is unknown (0x%X).", s->index,
+ s->pixel_format);
+ return 0;
+ }
+}
+static uint8_t *adp_gp_read(ADPGenPipeState *s)
+{
+ uint8_t *buf;
+
+ // TODO: Decompress the data and display it properly.
+ if (s->pixel_format & GP_PIXEL_FORMAT_COMPRESSED) {
+ error_report("[gp%zu] Dropping frame as it's compressed.", s->index);
+ return NULL;
+ }
+
+ ADP_INFO("[gp%zu] Width and height is %dx%d.", s->index, s->buf_width,
+ s->buf_height);
+ ADP_INFO("[gp%zu] Stride is %d.", s->index, s->stride);
+
+ if (s->buf_height == 0 || s->buf_width == 0 || s->stride == 0) {
+ error_report(
+ "[gp%zu] Dropping frame as width, height or stride is zero.",
+ s->index);
+ return NULL;
+ }
+
+ if (s->buf_width > s->disp_width || s->buf_height > s->disp_height) {
+ error_report("[gp%zu] Dropping frame as it's larger than the screen.",
+ s->index);
+ return NULL;
+ }
+
+
+ buf = g_malloc(s->buf_height * s->buf_width * s->stride);
+ if (dma_memory_read(s->dma_as, s->base, buf, s->end - s->base,
+ MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
+ error_report("[gp%zu] Failed to read from DMA.", s->index);
+ g_free(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+static void adp_gp_reset(ADPGenPipeState *s, size_t index, AddressSpace *dma_as,
+ uint16_t disp_width, uint16_t disp_height)
+{
+ memset(s, 0, sizeof(*s));
+ s->index = index;
+ s->dma_as = dma_as;
+ s->disp_width = disp_width;
+ s->disp_height = disp_height;
+ s->dirty = true;
+}
+
+static void adp_blend_reg_write(ADPBlendUnitState *s, uint64_t addr,
+ uint64_t data)
+{
+ switch (addr) {
+ case REG_BLEND_LAYER_0_CONFIG: {
+ ADP_INFO("[blend] Layer 0 Config <- 0x" HWADDR_FMT_plx, data);
+ s->layer_config[0] = (uint32_t)data;
+ s->dirty = true;
+ break;
+ }
+ case REG_BLEND_LAYER_1_CONFIG: {
+ s->layer_config[1] = (uint32_t)data;
+ s->dirty = true;
+ ADP_INFO("[blend] Layer 1 Config <- 0x" HWADDR_FMT_plx, data);
+ break;
+ }
+ default: {
+ ADP_INFO("[blend] Unknown @ 0x" HWADDR_FMT_plx " <- 0x" HWADDR_FMT_plx,
+ addr, data);
+ break;
+ }
+ }
+}
+
+static uint64_t adp_blend_reg_read(ADPBlendUnitState *s, uint64_t addr)
+{
+ switch (addr) {
+ case REG_BLEND_LAYER_0_CONFIG: {
+ ADP_INFO("[blend] Layer 0 Config -> 0x%X", s->layer_config[0]);
+ return s->layer_config[0];
+ }
+ case REG_BLEND_LAYER_1_CONFIG: {
+ ADP_INFO("[blend] Layer 1 Config -> 0x%X", s->layer_config[1]);
+ return s->layer_config[1];
+ }
+ default: {
+ ADP_INFO("[blend] Unknown @ 0x" HWADDR_FMT_plx " -> 0x" HWADDR_FMT_plx,
+ addr, (hwaddr)0);
+ return 0;
+ }
+ }
+}
+
+static void adp_blend_reset(ADPBlendUnitState *s)
+{
+ memset(s, 0, sizeof(*s));
+}
+
+static void adp_reg_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned size)
+{
+ AppleDisplayPipeV4State *s;
+
+ s = APPLE_DISPLAY_PIPE_V4(opaque);
+
+ QEMU_LOCK_GUARD(&s->lock);
+
+ if (addr >= 0x200000) {
+ addr -= 0x200000;
+ }
+
+ switch (addr) {
+ case REG_CONTROL_INT_STATUS: {
+ s->int_status &= ~(uint32_t)data;
+ adp_update_irqs(s);
+ break;
+ }
+ case GP_BLOCK_BASE_FOR(0)... GP_BLOCK_END_FOR(0): {
+ adp_gp_reg_write(&s->generic_pipe[0], addr - GP_BLOCK_BASE_FOR(0),
+ data);
+ break;
+ }
+ case GP_BLOCK_BASE_FOR(1)... GP_BLOCK_END_FOR(1): {
+ adp_gp_reg_write(&s->generic_pipe[1], addr - GP_BLOCK_BASE_FOR(1),
+ data);
+ break;
+ }
+ case BLEND_BLOCK_BASE ...(BLEND_BLOCK_BASE + BLEND_BLOCK_SIZE): {
+ adp_blend_reg_write(&s->blend_unit, addr - BLEND_BLOCK_BASE, data);
+ break;
+ }
+ default: {
+ ADP_INFO("[disp] Unknown @ 0x" HWADDR_FMT_plx " <- 0x" HWADDR_FMT_plx,
+ addr, data);
+ break;
+ }
+ }
+}
+
+static uint64_t adp_reg_read(void *opaque, hwaddr addr, const unsigned size)
+{
+ AppleDisplayPipeV4State *s;
+
+ s = APPLE_DISPLAY_PIPE_V4(opaque);
+
+ QEMU_LOCK_GUARD(&s->lock);
+
+ if (addr >= 0x200000) {
+ addr -= 0x200000;
+ }
+
+ switch (addr) {
+ case REG_CONTROL_VERSION: {
+ ADP_INFO("[disp] Version -> 0x%x", CONTROL_VERSION_A0);
+ return CONTROL_VERSION_A0;
+ }
+ case REG_CONTROL_FRAME_SIZE: {
+ ADP_INFO("[disp] Frame Size -> 0x%x", (s->width << 16) | s->height);
+ return (s->width << 16) | s->height;
+ }
+ case REG_CONTROL_INT_STATUS: {
+ ADP_INFO("[disp] Int Status -> 0x%x", s->int_status);
+ return s->int_status;
+ }
+ case GP_BLOCK_BASE_FOR(0)... GP_BLOCK_END_FOR(0): {
+ return adp_gp_reg_read(&s->generic_pipe[0],
+ addr - GP_BLOCK_BASE_FOR(0));
+ }
+ case GP_BLOCK_BASE_FOR(1)... GP_BLOCK_END_FOR(1): {
+ return adp_gp_reg_read(&s->generic_pipe[1],
+ addr - GP_BLOCK_BASE_FOR(1));
+ }
+ case BLEND_BLOCK_BASE ...(BLEND_BLOCK_BASE + BLEND_BLOCK_SIZE): {
+ return adp_blend_reg_read(&s->blend_unit, addr - BLEND_BLOCK_BASE);
+ }
+ default: {
+ ADP_INFO("[disp] Unknown @ 0x" HWADDR_FMT_plx " -> 0x" HWADDR_FMT_plx,
+ addr, (hwaddr)0);
+ return 0;
+ }
+ }
+}
+
+static const MemoryRegionOps adp_reg_ops = {
+ .write = adp_reg_write,
+ .read = adp_reg_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+static void adp_draw_row(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int dest_pitch)
+{
+ while (width--) {
+ uint32_t colour = ldl_le_p(src);
+ memcpy(dest, &colour, sizeof(colour));
+ src += sizeof(colour);
+ dest += sizeof(colour);
+ }
+}
+
+static void adp_v4_invalidate(void *opaque)
+{
+ AppleDisplayPipeV4State *s = APPLE_DISPLAY_PIPE_V4(opaque);
+
+ QEMU_LOCK_GUARD(&s->lock);
+
+ s->invalidated = true;
+}
+
+static void adp_v4_blit_rect_black(AppleDisplayPipeV4State *s, uint16_t width,
+ uint16_t height)
+{
+ size_t y;
+ hwaddr off;
+
+ for (y = 0; y < height; y += 1) {
+ off = y * s->width * sizeof(uint32_t);
+ memset(memory_region_get_ram_ptr(&s->vram) + off, 0,
+ s->width * sizeof(uint32_t));
+ memory_region_set_dirty(&s->vram, off, width * sizeof(uint32_t));
+ }
+}
+
+// TODO: Where is the destination X and Y?
+static void adp_v4_update_disp_image(AppleDisplayPipeV4State *s)
+{
+ ADPGenPipeState *layer_0_pipe;
+ ADPGenPipeState *layer_1_pipe;
+ uint8_t layer_0_blend_mode;
+ uint8_t layer_1_blend_mode;
+ uint8_t *layer_0_buf;
+ uint8_t *layer_1_buf;
+ size_t i;
+ hwaddr off;
+ pixman_format_code_t layer_0_fmt;
+ pixman_format_code_t layer_1_fmt;
+ pixman_image_t *layer_0_image;
+ pixman_image_t *layer_1_image;
+
+ QEMU_LOCK_GUARD(&s->lock);
+
+ if (!s->blend_unit.dirty && !s->generic_pipe[0].dirty &&
+ !s->generic_pipe[1].dirty) {
+ return;
+ }
+
+ layer_0_pipe = &s->generic_pipe[BLEND_LAYER_CONFIG_PIPE(
+ s->blend_unit.layer_config[0])];
+ layer_1_pipe = &s->generic_pipe[BLEND_LAYER_CONFIG_PIPE(
+ s->blend_unit.layer_config[1])];
+ layer_0_blend_mode = BLEND_LAYER_CONFIG_MODE(s->blend_unit.layer_config[0]);
+ layer_1_blend_mode = BLEND_LAYER_CONFIG_MODE(s->blend_unit.layer_config[1]);
+
+ // Display is empty.
+ if ((layer_0_blend_mode == BLEND_MODE_NONE &&
+ layer_1_blend_mode == BLEND_MODE_NONE) ||
+ ((layer_0_pipe->width == 0 || layer_0_pipe->height == 0) &&
+ (layer_1_pipe->width == 0 || layer_1_pipe->height == 0))) {
+ adp_v4_blit_rect_black(s, s->width, s->height);
+ return;
+ }
+
+ if (layer_1_blend_mode == BLEND_MODE_BYPASS ||
+ (layer_1_blend_mode != BLEND_MODE_NONE &&
+ layer_0_blend_mode == BLEND_MODE_NONE)) {
+ if (layer_1_pipe->base == 0 || layer_1_pipe->end == 0) {
+ adp_v4_blit_rect_black(s, layer_1_pipe->width,
+ layer_1_pipe->height);
+ } else {
+ layer_1_buf = adp_gp_read(layer_1_pipe);
+ g_assert_nonnull(layer_1_buf);
+ for (i = 0; i < layer_1_pipe->buf_height; i += 1) {
+ off = i * s->width * sizeof(uint32_t);
+ memcpy(memory_region_get_ram_ptr(&s->vram) + off,
+ layer_1_buf + i * layer_1_pipe->stride,
+ layer_1_pipe->buf_width * sizeof(uint32_t));
+ memory_region_set_dirty(
+ &s->vram, off, layer_1_pipe->buf_width * sizeof(uint32_t));
+ }
+ g_free(layer_1_buf);
+ }
+ layer_1_pipe->dirty = false;
+ } else if (layer_0_blend_mode == BLEND_MODE_BYPASS ||
+ (layer_0_blend_mode != BLEND_MODE_NONE &&
+ layer_1_blend_mode == BLEND_MODE_NONE)) {
+ if (layer_0_pipe->base == 0 || layer_0_pipe->end == 0) {
+ adp_v4_blit_rect_black(s, layer_0_pipe->width,
+ layer_0_pipe->height);
+ } else {
+ layer_0_buf = adp_gp_read(layer_0_pipe);
+ g_assert_nonnull(layer_0_buf);
+ for (i = 0; i < layer_0_pipe->buf_height; i += 1) {
+ off = i * s->width * sizeof(uint32_t);
+ memcpy(memory_region_get_ram_ptr(&s->vram) + off,
+ layer_0_buf + i * layer_0_pipe->stride,
+ layer_0_pipe->buf_width * sizeof(uint32_t));
+ memory_region_set_dirty(
+ &s->vram, off, layer_0_pipe->buf_width * sizeof(uint32_t));
+ }
+ g_free(layer_0_buf);
+ }
+ layer_0_pipe->dirty = false;
+ } else {
+ g_assert(layer_0_pipe != layer_1_pipe);
+
+ layer_0_buf = adp_gp_read(layer_0_pipe);
+ g_assert_nonnull(layer_0_buf);
+ layer_0_fmt = adp_gp_fmt_to_pixman(layer_0_pipe);
+ g_assert_cmphex(layer_0_fmt, !=, 0);
+ layer_0_image = pixman_image_create_bits(
+ layer_0_fmt, layer_0_pipe->buf_width, layer_0_pipe->buf_height,
+ (uint32_t *)layer_0_buf, layer_0_pipe->stride);
+ g_assert_nonnull(layer_0_image);
+
+ layer_1_buf = adp_gp_read(layer_1_pipe);
+ g_assert_nonnull(layer_1_buf);
+ layer_1_fmt = adp_gp_fmt_to_pixman(layer_1_pipe);
+ g_assert_cmphex(layer_1_fmt, !=, 0);
+ layer_1_image = pixman_image_create_bits(
+ layer_1_fmt, layer_1_pipe->buf_width, layer_1_pipe->buf_height,
+ (uint32_t *)layer_1_buf, layer_1_pipe->stride);
+ g_assert_nonnull(layer_1_image);
+
+ adp_v4_blit_rect_black(s, s->width, s->height);
+
+ pixman_image_composite(PIXMAN_OP_OVER, layer_0_image, NULL,
+ s->disp_image, 0, 0, 0, 0, 0, 0,
+ layer_0_pipe->width, layer_0_pipe->height);
+ pixman_image_composite(PIXMAN_OP_OVER, layer_1_image, NULL,
+ s->disp_image, 0, 0, 0, 0, 0, 0,
+ layer_1_pipe->width, layer_1_pipe->height);
+
+ layer_0_pipe->dirty = false;
+ layer_1_pipe->dirty = false;
+ g_free(layer_0_buf);
+ g_free(layer_1_buf);
+ pixman_image_unref(layer_0_image);
+ pixman_image_unref(layer_1_image);
+
+ memory_region_set_dirty(&s->vram, 0,
+ s->height * s->width * sizeof(uint32_t));
+ }
+
+ s->blend_unit.dirty = false;
+}
+
+static void adp_v4_gfx_update(void *opaque)
+{
+ AppleDisplayPipeV4State *s = APPLE_DISPLAY_PIPE_V4(opaque);
+ DisplaySurface *surface = qemu_console_surface(s->console);
+
+ int stride = s->width * sizeof(uint32_t);
+ int first = 0, last = 0;
+
+ adp_v4_update_disp_image(s);
+
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->vram_section, &s->vram, 0,
+ s->height, stride);
+ s->invalidated = false;
+ }
+
+ framebuffer_update_display(surface, &s->vram_section, s->width, s->height,
+ stride, stride, 0, 0, adp_draw_row, s, &first,
+ &last);
+ if (first >= 0) {
+ dpy_gfx_update(s->console, 0, first, s->width, last - first + 1);
+ }
+
+ s->int_status |= CONTROL_INT_STATUS_VBLANK;
+ adp_update_irqs(s);
+}
+
+static const GraphicHwOps adp_v4_ops = {
+ .invalidate = adp_v4_invalidate,
+ .gfx_update = adp_v4_gfx_update,
+};
+
+static void adp_v4_reset_hold(Object *obj, ResetType type)
+{
+ AppleDisplayPipeV4State *s = APPLE_DISPLAY_PIPE_V4(obj);
+
+ QEMU_LOCK_GUARD(&s->lock);
+
+ s->invalidated = true;
+
+ s->int_status = 0;
+ adp_update_irqs(s);
+
+ qemu_pixman_image_unref(s->disp_image);
+ s->disp_image = pixman_image_create_bits(
+ PIXMAN_a8r8g8b8, s->width, s->height,
+ (uint32_t *)memory_region_get_ram_ptr(&s->vram),
+ s->width * sizeof(uint32_t));
+
+ adp_gp_reset(&s->generic_pipe[0], 0, &s->dma_as, s->width, s->height);
+ adp_gp_reset(&s->generic_pipe[1], 1, &s->dma_as, s->width, s->height);
+
+ adp_blend_reset(&s->blend_unit);
+}
+
+static void adp_v4_realize(DeviceState *dev, Error **errp)
+{
+ AppleDisplayPipeV4State *s = APPLE_DISPLAY_PIPE_V4(dev);
+
+ QEMU_LOCK_GUARD(&s->lock);
+
+ s->console = graphic_console_init(dev, 0, &adp_v4_ops, s);
+ qemu_console_resize(s->console, s->width, s->height);
+}
+
+static Property adp_v4_props[] = {
+ DEFINE_PROP_UINT32("width", AppleDisplayPipeV4State, width, 828),
+ DEFINE_PROP_UINT32("height", AppleDisplayPipeV4State, height, 1792),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void adp_v4_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ rc->phases.hold = adp_v4_reset_hold;
+
+ device_class_set_props(dc, adp_v4_props);
+ dc->realize = adp_v4_realize;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo adp_v4_type_info = {
+ .name = TYPE_APPLE_DISPLAY_PIPE_V4,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AppleDisplayPipeV4State),
+ .class_init = adp_v4_class_init,
+};
+
+static void adp_v4_register_types(void)
+{
+ type_register_static(&adp_v4_type_info);
+}
+
+type_init(adp_v4_register_types);
+
+static uint32_t adp_timing_info[] = { 0x33C, 0x90, 0x1, 0x1,
+ 0x700, 0x1, 0x1, 0x1 };
+
+AppleDisplayPipeV4State *adp_v4_create(DTBNode *node)
+{
+ DeviceState *dev;
+ SysBusDevice *sbd;
+ AppleDisplayPipeV4State *s;
+ DTBProp *prop;
+ uint64_t *reg;
+ int i;
+
+ dev = qdev_new(TYPE_APPLE_DISPLAY_PIPE_V4);
+ sbd = SYS_BUS_DEVICE(dev);
+ s = APPLE_DISPLAY_PIPE_V4(sbd);
+
+ qemu_mutex_init(&s->lock);
+
+ dtb_set_prop(node, "display-target", 15, "DisplayTarget5");
+ dtb_set_prop(node, "display-timing-info", sizeof(adp_timing_info),
+ adp_timing_info);
+ dtb_set_prop_u32(node, "bics-param-set", 0xD);
+ dtb_set_prop_u32(node, "dot-pitch", 326);
+ dtb_set_prop_null(node, "function-brightness_update");
+
+ prop = dtb_find_prop(node, "reg");
+ g_assert_nonnull(prop);
+ reg = (uint64_t *)prop->data;
+ memory_region_init_io(&s->up_regs, OBJECT(sbd), &adp_reg_ops, sbd,
+ "up.regs", reg[1]);
+ sysbus_init_mmio(sbd, &s->up_regs);
+ object_property_add_const_link(OBJECT(sbd), "up.regs", OBJECT(&s->up_regs));
+
+ for (i = 0; i < 9; i++) {
+ sysbus_init_irq(sbd, &s->irqs[i]);
+ }
+
+ return s;
+}
diff --git a/hw/display/meson.build b/hw/display/meson.build
index 8d117fafc8..adb467b6cc 100644
--- a/hw/display/meson.build
+++ b/hw/display/meson.build
@@ -1,6 +1,6 @@
hw_display_modules = {}
-system_ss.add(when: 'CONFIG_APPLE_SOC', if_true: files('apple_displaypipe_v2.c', 'adbe_v2.c'))
+system_ss.add(when: 'CONFIG_APPLE_SOC', if_true: files('apple_displaypipe_v4.c', 'adbe_v2.c'))
system_ss.add(when: 'CONFIG_DDC', if_true: files('i2c-ddc.c'))
system_ss.add(when: 'CONFIG_EDID', if_true: files('edid-generate.c', 'edid-region.c'))
diff --git a/include/hw/display/apple_displaypipe_v2.h b/include/hw/display/apple_displaypipe_v4.h
similarity index 68%
rename from include/hw/display/apple_displaypipe_v2.h
rename to include/hw/display/apple_displaypipe_v4.h
index d938e9eafb..14d99de681 100644
--- a/include/hw/display/apple_displaypipe_v2.h
+++ b/include/hw/display/apple_displaypipe_v4.h
@@ -1,5 +1,5 @@
/*
- * Apple Display Pipe V2 Controller.
+ * Apple Display Pipe V4 Controller.
*
* Copyright (c) 2023-2025 Visual Ehrmanntraut.
*
@@ -16,8 +16,8 @@
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see .
*/
-#ifndef APPLE_DISPLAYPIPE_V2_H
-#define APPLE_DISPLAYPIPE_V2_H
+#ifndef APPLE_DISPLAYPIPE_V4_H
+#define APPLE_DISPLAYPIPE_V4_H
#include "qemu/osdep.h"
#include "hw/arm/apple-silicon/dtb.h"
@@ -25,15 +25,14 @@
#include "qom/object.h"
#include "ui/console.h"
-#define TYPE_APPLE_DISPLAYPIPE_V2 "apple-displaypipe-v2"
-OBJECT_DECLARE_SIMPLE_TYPE(AppleDisplayPipeV2State, APPLE_DISPLAYPIPE_V2);
+#define TYPE_APPLE_DISPLAY_PIPE_V4 "apple-display-pipe-v4"
+OBJECT_DECLARE_SIMPLE_TYPE(AppleDisplayPipeV4State, APPLE_DISPLAY_PIPE_V4);
+
+#define ADP_V4_LAYER_COUNT (2)
typedef struct {
size_t index;
- pixman_image_t *disp_image;
- MemoryRegion *vram;
AddressSpace *dma_as;
- QEMUBH *bh;
uint16_t disp_width;
uint16_t disp_height;
uint32_t config_control;
@@ -43,14 +42,22 @@ typedef struct {
uint32_t base;
uint32_t end;
uint32_t stride;
- uint32_t size;
-} GenPipeState;
+ uint16_t buf_width;
+ uint16_t buf_height;
+ bool dirty;
+} ADPGenPipeState;
+
+typedef struct {
+ uint32_t layer_config[ADP_V4_LAYER_COUNT];
+ bool dirty;
+} ADPBlendUnitState;
-struct AppleDisplayPipeV2State {
+struct AppleDisplayPipeV4State {
/*< private >*/
SysBusDevice parent_obj;
/*< public >*/
+ QemuMutex lock;
uint32_t width;
uint32_t height;
pixman_image_t *disp_image;
@@ -61,11 +68,12 @@ struct AppleDisplayPipeV2State {
MemoryRegionSection vram_section;
qemu_irq irqs[9];
uint32_t int_status;
- GenPipeState genpipes[2];
+ ADPGenPipeState generic_pipe[ADP_V4_LAYER_COUNT];
+ ADPBlendUnitState blend_unit;
QemuConsole *console;
bool invalidated;
};
-AppleDisplayPipeV2State *apple_displaypipe_v2_create(DTBNode *node);
+AppleDisplayPipeV4State *adp_v4_create(DTBNode *node);
-#endif /* APPLE_DISPLAYPIPE_V2_H */
+#endif /* APPLE_DISPLAYPIPE_V4_H */