From 0ddf41fd0487b98eefbc5bb6cff215bb830e42ae Mon Sep 17 00:00:00 2001 From: Oliver Bestmann Date: Sun, 1 Feb 2026 16:26:35 +0100 Subject: [PATCH] drm/apple: support interchange compression Signed-off-by: Oliver Bestmann --- drivers/gpu/drm/apple/iomfb_plane.h | 31 ++++++- drivers/gpu/drm/apple/plane.c | 135 +++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_plane.h b/drivers/gpu/drm/apple/iomfb_plane.h index 0701978200311a..46eb7ab3fed4c7 100644 --- a/drivers/gpu/drm/apple/iomfb_plane.h +++ b/drivers/gpu/drm/apple/iomfb_plane.h @@ -49,6 +49,14 @@ enum dcp_xfer_func { DCP_XFER_FUNC_HDR = 16, }; +enum dcp_address_format { + DCP_ADDRESS_FORMAT_INTERCHANGE_TILED = 5, +}; + +enum dcp_compression_type { + DCP_COMPRESSION_TYPE_INTERCHANGE_TILED = 3, +}; + struct dcp_rect { u32 x; u32 y; @@ -67,7 +75,26 @@ struct dcp_plane_info { u16 tile_size; u8 tile_w; u8 tile_h; - u32 unk[13]; + u8 unk1[0xd]; + u8 address_format; + u8 unk2[0x26]; +} __packed; + +struct dcp_compression_info { + u32 tile_w; + u32 tile_h; + u32 meta_offset; + u32 data_offset; + u32 tile_meta_bytes; + u32 tiles_w; + u32 tiles_h; + u32 unk1; + u32 compresson_type; + u32 unk3; + u8 _pad1[3]; + u32 tile_bytes; + u32 row_stride; + u8 pad2; } __packed; struct dcp_component_types { @@ -100,7 +127,7 @@ struct dcp_surface { u64 has_comp; struct dcp_plane_info planes[DCP_SURF_MAX_PLANES]; u64 has_planes; - u32 compression_info[DCP_SURF_MAX_PLANES][13]; + struct dcp_compression_info compression_info[DCP_SURF_MAX_PLANES]; u64 has_compr_info; u32 unk_num; u32 unk_denom; diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index 2f0b76ad84ad65..5e3050d14f8af4 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -9,15 +9,61 @@ #include #include -#include #include +#include #include #include #include #include +#include +#include + +#ifndef DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED +#define DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED fourcc_mod_code(APPLE, 3) +#endif + #define FRAC_16_16(mult, div) (((mult) << 16) / (div)) +struct apple_interchange_layout { + u32 tiles_width; + u32 tiles_height; + u32 tile_size_B; + u32 meta_offset; + u32 meta_size; +}; + +static struct apple_interchange_layout +get_apple_interchange_layout(u32 width, u32 height, u32 bpp) +{ + const u32 cacheline = 0x80; + + u32 tw = DIV_ROUND_UP(width, 16); + u32 th = DIV_ROUND_UP(height, 16); + u32 tsize_B = 16 * 16 * (bpp / 8); + + u32 meta_offset = ALIGN(tw * th * tsize_B, cacheline); + + /* + * The metadata buffer contains 8 bytes per 16x16 compression tile. + * Addressing is fully twiddled, so both width and height are padded to + * powers-of-two. + */ + unsigned w_tl = DIV_ROUND_UP(roundup_pow_of_two(width), 16); + unsigned h_tl = DIV_ROUND_UP(roundup_pow_of_two(height), 16); + unsigned B_per_tl = 8; + + u32 meta_size = ALIGN(w_tl * h_tl * B_per_tl, cacheline); + + return (struct apple_interchange_layout){ + .tiles_width = tw, + .tiles_height = th, + .tile_size_B = tsize_B, + .meta_offset = meta_offset, + .meta_size = meta_size, + }; +} + static int apple_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state) { @@ -105,6 +151,38 @@ static int apple_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } + /* + * The calculated size of the compression meta data must fit into the size + * of the gem object + */ + for (u32 i = 0; i < new_plane_state->fb->format->num_planes; i++) { + struct drm_framebuffer *fb = new_plane_state->fb; + + if (fb->modifier != DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED) + continue; + + u32 width = + drm_format_info_plane_width(fb->format, fb->width, i); + u32 height = + drm_format_info_plane_height(fb->format, fb->height, i); + u32 bpp = drm_format_info_bpp(fb->format, i); + struct apple_interchange_layout l = + get_apple_interchange_layout(width, height, bpp); + + u32 required_size = + fb->offsets[0] + l.meta_offset + l.meta_size; + + struct drm_gem_dma_object *obj = drm_fb_dma_get_gem_obj(fb, i); + if (obj && required_size > obj->base.size) { + dev_err_ratelimited( + state->dev->dev, + "dcp: atomic_check, required size of %u bytes is less than gem size of %zu\n", + required_size, obj->base.size); + + return -EINVAL; + } + } + return 0; } @@ -248,7 +326,6 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .stride = fb->pitches[0], .width = fb->width, .height = fb->height, - .buf_size = fb->height * fb->pitches[0], // .surface_id = req->swap.surf_ids[l], /* Only used for compressed or multiplanar surfaces */ @@ -257,6 +334,7 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .pel_h = 1, .has_comp = 1, .has_planes = 1, + .has_compr_info = 1, }; /* Populate plane information for planar formats */ @@ -279,8 +357,33 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .tile_h = bh, }; - if (i > 0) - surf->buf_size += surf->planes[i].size; + if (fb->modifier == DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED) { + u32 bpp = drm_format_info_bpp(fmt, i); + struct apple_interchange_layout l = + get_apple_interchange_layout(width, height, bpp); + + surf->planes[i].tile_w = 16; + surf->planes[i].tile_h = 16; + surf->planes[i].stride = l.tiles_width * l.tile_size_B; + surf->planes[i].size = l.meta_offset + l.meta_size; + surf->planes[i].tile_size = l.tile_size_B; + surf->planes[i].address_format = DCP_ADDRESS_FORMAT_INTERCHANGE_TILED; + + surf->compression_info[i] = (struct dcp_compression_info){ + .tile_w = 16, + .tile_h = 16, + .data_offset = 0, + .meta_offset = l.meta_offset, + .tile_meta_bytes = 8, + .tiles_w = l.tiles_width, + .tiles_h = l.tiles_height, + .tile_bytes = l.tile_size_B, + .row_stride = l.tiles_width * l.tile_size_B, + .compresson_type = DCP_COMPRESSION_TYPE_INTERCHANGE_TILED, + }; + } + + surf->buf_size += surf->planes[i].size; } /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts @@ -337,6 +440,28 @@ apple_plane_duplicate_state(struct drm_plane *plane) return &apple_plane_state->base; } +static bool apple_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, uint64_t modifier) +{ + if (modifier != DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED) { + return true; + } + + // interchange is only supported with rgba formats for now. + switch (format) { + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return true; + + default: + return false; + } +} + // void apple_plane_destroy_state(struct drm_plane *plane, // struct drm_plane_state *state) // { @@ -350,6 +475,7 @@ static const struct drm_plane_funcs apple_plane_funcs = { .atomic_duplicate_state = apple_plane_duplicate_state, // .atomic_destroy_state = apple_plane_destroy_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .format_mod_supported = apple_plane_format_mod_supported, }; /* @@ -426,6 +552,7 @@ static const u32 dcp_overlay_formats_12_x[] = { }; u64 apple_format_modifiers[] = { + DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED, DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID };