root/drivers/gpu/drm/i915/display/intel_fb.c
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2021 Intel Corporation
 */

#include <linux/dma-fence.h>
#include <linux/dma-resv.h>

#include <drm/drm_blend.h>
#include <drm/drm_gem.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_print.h>

#include "intel_bo.h"
#include "intel_display.h"
#include "intel_display_core.h"
#include "intel_display_types.h"
#include "intel_display_utils.h"
#include "intel_dpt.h"
#include "intel_fb.h"
#include "intel_fb_bo.h"
#include "intel_frontbuffer.h"
#include "intel_parent.h"
#include "intel_plane.h"

#define check_array_bounds(display, a, i) drm_WARN_ON((display)->drm, (i) >= ARRAY_SIZE(a))

/*
 * From the Sky Lake PRM:
 * "The Color Control Surface (CCS) contains the compression status of
 *  the cache-line pairs. The compression state of the cache-line pair
 *  is specified by 2 bits in the CCS. Each CCS cache-line represents
 *  an area on the main surface of 16 x16 sets of 128 byte Y-tiled
 *  cache-line-pairs. CCS is always Y tiled."
 *
 * Since cache line pairs refers to horizontally adjacent cache lines,
 * each cache line in the CCS corresponds to an area of 32x16 cache
 * lines on the main surface. Since each pixel is 4 bytes, this gives
 * us a ratio of one byte in the CCS for each 8x16 pixels in the
 * main surface.
 */
static const struct drm_format_info skl_ccs_formats[] = {
        { .format = DRM_FORMAT_XRGB8888, .depth = 24, .num_planes = 2,
          .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, },
        { .format = DRM_FORMAT_XBGR8888, .depth = 24, .num_planes = 2,
          .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, },
        { .format = DRM_FORMAT_ARGB8888, .depth = 32, .num_planes = 2,
          .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, .has_alpha = true, },
        { .format = DRM_FORMAT_ABGR8888, .depth = 32, .num_planes = 2,
          .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, .has_alpha = true, },
        { .format = DRM_FORMAT_XRGB2101010, .depth = 30, .num_planes = 2,
          .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, },
        { .format = DRM_FORMAT_XBGR2101010, .depth = 30, .num_planes = 2,
          .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, },
        { .format = DRM_FORMAT_ARGB2101010, .depth = 30, .num_planes = 2,
          .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, .has_alpha = true, },
        { .format = DRM_FORMAT_ABGR2101010, .depth = 30, .num_planes = 2,
          .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, .has_alpha = true, },
};

/*
 * Gen-12 compression uses 4 bits of CCS data for each cache line pair in the
 * main surface. And each 64B CCS cache line represents an area of 4x1 Y-tiles
 * in the main surface. With 4 byte pixels and each Y-tile having dimensions of
 * 32x32 pixels, the ratio turns out to 1B in the CCS for every 2x32 pixels in
 * the main surface.
 */
static const struct drm_format_info gen12_ccs_formats[] = {
        { .format = DRM_FORMAT_XRGB8888, .depth = 24, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR8888, .depth = 24, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB8888, .depth = 32, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR8888, .depth = 32, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_XRGB2101010, .depth = 30, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR2101010, .depth = 30, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB2101010, .depth = 30, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR2101010, .depth = 30, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_XRGB16161616F, .depth = 0, .num_planes = 2,
          .char_per_block = { 8, 1 }, .block_w = { 1, 1 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR16161616F, .depth = 0, .num_planes = 2,
          .char_per_block = { 8, 1 }, .block_w = { 1, 1 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB16161616F, .depth = 0, .num_planes = 2,
          .char_per_block = { 8, 1 }, .block_w = { 1, 1 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR16161616F, .depth = 0, .num_planes = 2,
          .char_per_block = { 8, 1 }, .block_w = { 1, 1 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_YUYV, .num_planes = 2,
          .char_per_block = { 2, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 2, .vsub = 1, .is_yuv = true },
        { .format = DRM_FORMAT_YVYU, .num_planes = 2,
          .char_per_block = { 2, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 2, .vsub = 1, .is_yuv = true },
        { .format = DRM_FORMAT_UYVY, .num_planes = 2,
          .char_per_block = { 2, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 2, .vsub = 1, .is_yuv = true },
        { .format = DRM_FORMAT_VYUY, .num_planes = 2,
          .char_per_block = { 2, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 2, .vsub = 1, .is_yuv = true },
        { .format = DRM_FORMAT_XYUV8888, .num_planes = 2,
          .char_per_block = { 4, 1 }, .block_w = { 1, 2 }, .block_h = { 1, 1 },
          .hsub = 1, .vsub = 1, .is_yuv = true },
        { .format = DRM_FORMAT_NV12, .num_planes = 4,
          .char_per_block = { 1, 2, 1, 1 }, .block_w = { 1, 1, 4, 4 }, .block_h = { 1, 1, 1, 1 },
          .hsub = 2, .vsub = 2, .is_yuv = true },
        { .format = DRM_FORMAT_P010, .num_planes = 4,
          .char_per_block = { 2, 4, 1, 1 }, .block_w = { 1, 1, 2, 2 }, .block_h = { 1, 1, 1, 1 },
          .hsub = 2, .vsub = 2, .is_yuv = true },
        { .format = DRM_FORMAT_P012, .num_planes = 4,
          .char_per_block = { 2, 4, 1, 1 }, .block_w = { 1, 1, 2, 2 }, .block_h = { 1, 1, 1, 1 },
          .hsub = 2, .vsub = 2, .is_yuv = true },
        { .format = DRM_FORMAT_P016, .num_planes = 4,
          .char_per_block = { 2, 4, 1, 1 }, .block_w = { 1, 1, 2, 2 }, .block_h = { 1, 1, 1, 1 },
          .hsub = 2, .vsub = 2, .is_yuv = true },
};

/*
 * Same as gen12_ccs_formats[] above, but with additional surface used
 * to pass Clear Color information in plane 2 with 64 bits of data.
 */
static const struct drm_format_info gen12_ccs_cc_formats[] = {
        { .format = DRM_FORMAT_XRGB8888, .depth = 24, .num_planes = 3,
          .char_per_block = { 4, 1, 0 }, .block_w = { 1, 2, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR8888, .depth = 24, .num_planes = 3,
          .char_per_block = { 4, 1, 0 }, .block_w = { 1, 2, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB8888, .depth = 32, .num_planes = 3,
          .char_per_block = { 4, 1, 0 }, .block_w = { 1, 2, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR8888, .depth = 32, .num_planes = 3,
          .char_per_block = { 4, 1, 0 }, .block_w = { 1, 2, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_XRGB2101010, .depth = 30, .num_planes = 3,
          .char_per_block = { 4, 1, 0 }, .block_w = { 1, 2, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR2101010, .depth = 30, .num_planes = 3,
          .char_per_block = { 4, 1, 0 }, .block_w = { 1, 2, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB2101010, .depth = 30, .num_planes = 3,
          .char_per_block = { 4, 1, 0 }, .block_w = { 1, 2, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR2101010, .depth = 30, .num_planes = 3,
          .char_per_block = { 4, 1, 0 }, .block_w = { 1, 2, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_XRGB16161616F, .depth = 0, .num_planes = 3,
          .char_per_block = { 8, 1, 0 }, .block_w = { 1, 1, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR16161616F, .depth = 0, .num_planes = 3,
          .char_per_block = { 8, 1, 0 }, .block_w = { 1, 1, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB16161616F, .depth = 0, .num_planes = 3,
          .char_per_block = { 8, 1, 0 }, .block_w = { 1, 1, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR16161616F, .depth = 0, .num_planes = 3,
          .char_per_block = { 8, 1, 0 }, .block_w = { 1, 1, 0 }, .block_h = { 1, 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
};

static const struct drm_format_info gen12_flat_ccs_cc_formats[] = {
        { .format = DRM_FORMAT_XRGB8888, .depth = 24, .num_planes = 2,
          .char_per_block = { 4, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR8888, .depth = 24, .num_planes = 2,
          .char_per_block = { 4, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB8888, .depth = 32, .num_planes = 2,
          .char_per_block = { 4, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR8888, .depth = 32, .num_planes = 2,
          .char_per_block = { 4, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_XRGB2101010, .depth = 30, .num_planes = 2,
          .char_per_block = { 4, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR2101010, .depth = 30, .num_planes = 2,
          .char_per_block = { 4, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB2101010, .depth = 30, .num_planes = 2,
          .char_per_block = { 4, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR2101010, .depth = 30, .num_planes = 2,
          .char_per_block = { 4, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_XRGB16161616F, .depth = 0, .num_planes = 2,
          .char_per_block = { 8, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_XBGR16161616F, .depth = 0, .num_planes = 2,
          .char_per_block = { 8, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, },
        { .format = DRM_FORMAT_ARGB16161616F, .depth = 0, .num_planes = 2,
          .char_per_block = { 8, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
        { .format = DRM_FORMAT_ABGR16161616F, .depth = 0, .num_planes = 2,
          .char_per_block = { 8, 0 }, .block_w = { 1, 0 }, .block_h = { 1, 0 },
          .hsub = 1, .vsub = 1, .has_alpha = true },
};

struct intel_modifier_desc {
        u64 modifier;
        struct {
                u8 from;
                u8 until;
        } display_ver;
#define DISPLAY_VER_ALL         { 0, -1 }

        const struct drm_format_info *formats;
        int format_count;
#define FORMAT_OVERRIDE(format_list) \
        .formats = format_list, \
        .format_count = ARRAY_SIZE(format_list)

        u8 plane_caps;

        struct {
                u8 cc_planes:3;
                u8 packed_aux_planes:4;
                u8 planar_aux_planes:4;
        } ccs;
};

#define INTEL_PLANE_CAP_CCS_MASK        (INTEL_PLANE_CAP_CCS_RC | \
                                         INTEL_PLANE_CAP_CCS_RC_CC | \
                                         INTEL_PLANE_CAP_CCS_MC)
#define INTEL_PLANE_CAP_TILING_MASK     (INTEL_PLANE_CAP_TILING_X | \
                                         INTEL_PLANE_CAP_TILING_Y | \
                                         INTEL_PLANE_CAP_TILING_Yf | \
                                         INTEL_PLANE_CAP_TILING_4)
#define INTEL_PLANE_CAP_TILING_NONE     0

static const struct intel_modifier_desc intel_modifiers[] = {
        {
                .modifier = I915_FORMAT_MOD_4_TILED_LNL_CCS,
                .display_ver = { 20, -1 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4,
        }, {
                .modifier = I915_FORMAT_MOD_4_TILED_BMG_CCS,
                .display_ver = { 14, -1 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4 | INTEL_PLANE_CAP_NEED64K_PHYS,
        }, {
                .modifier = I915_FORMAT_MOD_4_TILED_MTL_MC_CCS,
                .display_ver = { 14, 14 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4 | INTEL_PLANE_CAP_CCS_MC,

                .ccs.packed_aux_planes = BIT(1),
                .ccs.planar_aux_planes = BIT(2) | BIT(3),

                FORMAT_OVERRIDE(gen12_ccs_formats),
        }, {
                .modifier = I915_FORMAT_MOD_4_TILED_MTL_RC_CCS,
                .display_ver = { 14, 14 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4 | INTEL_PLANE_CAP_CCS_RC,

                .ccs.packed_aux_planes = BIT(1),

                FORMAT_OVERRIDE(gen12_ccs_formats),
        }, {
                .modifier = I915_FORMAT_MOD_4_TILED_MTL_RC_CCS_CC,
                .display_ver = { 14, 14 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4 | INTEL_PLANE_CAP_CCS_RC_CC,

                .ccs.cc_planes = BIT(2),
                .ccs.packed_aux_planes = BIT(1),

                FORMAT_OVERRIDE(gen12_ccs_cc_formats),
        }, {
                .modifier = I915_FORMAT_MOD_4_TILED_DG2_MC_CCS,
                .display_ver = { 13, 13 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4 | INTEL_PLANE_CAP_CCS_MC,
        }, {
                .modifier = I915_FORMAT_MOD_4_TILED_DG2_RC_CCS_CC,
                .display_ver = { 13, 13 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4 | INTEL_PLANE_CAP_CCS_RC_CC,

                .ccs.cc_planes = BIT(1),

                FORMAT_OVERRIDE(gen12_flat_ccs_cc_formats),
        }, {
                .modifier = I915_FORMAT_MOD_4_TILED_DG2_RC_CCS,
                .display_ver = { 13, 13 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4 | INTEL_PLANE_CAP_CCS_RC,
        }, {
                .modifier = I915_FORMAT_MOD_4_TILED,
                .display_ver = { 13, -1 },
                .plane_caps = INTEL_PLANE_CAP_TILING_4,
        }, {
                .modifier = I915_FORMAT_MOD_Y_TILED_GEN12_MC_CCS,
                .display_ver = { 12, 13 },
                .plane_caps = INTEL_PLANE_CAP_TILING_Y | INTEL_PLANE_CAP_CCS_MC,

                .ccs.packed_aux_planes = BIT(1),
                .ccs.planar_aux_planes = BIT(2) | BIT(3),

                FORMAT_OVERRIDE(gen12_ccs_formats),
        }, {
                .modifier = I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS,
                .display_ver = { 12, 13 },
                .plane_caps = INTEL_PLANE_CAP_TILING_Y | INTEL_PLANE_CAP_CCS_RC,

                .ccs.packed_aux_planes = BIT(1),

                FORMAT_OVERRIDE(gen12_ccs_formats),
        }, {
                .modifier = I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC,
                .display_ver = { 12, 13 },
                .plane_caps = INTEL_PLANE_CAP_TILING_Y | INTEL_PLANE_CAP_CCS_RC_CC,

                .ccs.cc_planes = BIT(2),
                .ccs.packed_aux_planes = BIT(1),

                FORMAT_OVERRIDE(gen12_ccs_cc_formats),
        }, {
                .modifier = I915_FORMAT_MOD_Yf_TILED_CCS,
                .display_ver = { 9, 11 },
                .plane_caps = INTEL_PLANE_CAP_TILING_Yf | INTEL_PLANE_CAP_CCS_RC,

                .ccs.packed_aux_planes = BIT(1),

                FORMAT_OVERRIDE(skl_ccs_formats),
        }, {
                .modifier = I915_FORMAT_MOD_Y_TILED_CCS,
                .display_ver = { 9, 11 },
                .plane_caps = INTEL_PLANE_CAP_TILING_Y | INTEL_PLANE_CAP_CCS_RC,

                .ccs.packed_aux_planes = BIT(1),

                FORMAT_OVERRIDE(skl_ccs_formats),
        }, {
                .modifier = I915_FORMAT_MOD_Yf_TILED,
                .display_ver = { 9, 11 },
                .plane_caps = INTEL_PLANE_CAP_TILING_Yf,
        }, {
                .modifier = I915_FORMAT_MOD_Y_TILED,
                .display_ver = { 9, 13 },
                .plane_caps = INTEL_PLANE_CAP_TILING_Y,
        }, {
                .modifier = I915_FORMAT_MOD_X_TILED,
                .display_ver = { 0, 29 },
                .plane_caps = INTEL_PLANE_CAP_TILING_X,
        }, {
                .modifier = DRM_FORMAT_MOD_LINEAR,
                .display_ver = DISPLAY_VER_ALL,
        },
};

static const struct intel_modifier_desc *lookup_modifier_or_null(u64 modifier)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(intel_modifiers); i++)
                if (intel_modifiers[i].modifier == modifier)
                        return &intel_modifiers[i];

        return NULL;
}

static const struct intel_modifier_desc *lookup_modifier(u64 modifier)
{
        const struct intel_modifier_desc *md = lookup_modifier_or_null(modifier);

        if (WARN_ON(!md))
                return &intel_modifiers[0];

        return md;
}

static const struct drm_format_info *
lookup_format_info(const struct drm_format_info formats[],
                   int num_formats, u32 format)
{
        int i;

        for (i = 0; i < num_formats; i++) {
                if (formats[i].format == format)
                        return &formats[i];
        }

        return NULL;
}

unsigned int intel_fb_modifier_to_tiling(u64 fb_modifier)
{
        const struct intel_modifier_desc *md;
        u8 tiling_caps;

        md = lookup_modifier_or_null(fb_modifier);
        if (!md)
                return I915_TILING_NONE;

        tiling_caps = lookup_modifier_or_null(fb_modifier)->plane_caps &
                         INTEL_PLANE_CAP_TILING_MASK;

        switch (tiling_caps) {
        case INTEL_PLANE_CAP_TILING_Y:
                return I915_TILING_Y;
        case INTEL_PLANE_CAP_TILING_X:
                return I915_TILING_X;
        case INTEL_PLANE_CAP_TILING_4:
        case INTEL_PLANE_CAP_TILING_Yf:
        case INTEL_PLANE_CAP_TILING_NONE:
                return I915_TILING_NONE;
        default:
                MISSING_CASE(tiling_caps);
                return I915_TILING_NONE;
        }
}

/**
 * intel_fb_get_format_info: Get a modifier specific format information
 * @pixel_format: pixel format
 * @modifier: modifier
 *
 * Returns:
 * Returns the format information for @pixel_format specific to @modifier,
 * or %NULL if the modifier doesn't override the format.
 */
const struct drm_format_info *
intel_fb_get_format_info(u32 pixel_format, u64 modifier)
{
        const struct intel_modifier_desc *md = lookup_modifier_or_null(modifier);

        if (!md || !md->formats)
                return NULL;

        return lookup_format_info(md->formats, md->format_count, pixel_format);
}

static bool plane_caps_contain_any(u8 caps, u8 mask)
{
        return caps & mask;
}

static bool plane_caps_contain_all(u8 caps, u8 mask)
{
        return (caps & mask) == mask;
}

/**
 * intel_fb_is_tiled_modifier: Check if a modifier is a tiled modifier type
 * @modifier: Modifier to check
 *
 * Returns:
 * Returns %true if @modifier is a tiled modifier.
 */
bool intel_fb_is_tiled_modifier(u64 modifier)
{
        return plane_caps_contain_any(lookup_modifier(modifier)->plane_caps,
                                      INTEL_PLANE_CAP_TILING_MASK);
}

/**
 * intel_fb_is_ccs_modifier: Check if a modifier is a CCS modifier type
 * @modifier: Modifier to check
 *
 * Returns:
 * Returns %true if @modifier is a render, render with color clear or
 * media compression modifier.
 */
bool intel_fb_is_ccs_modifier(u64 modifier)
{
        return plane_caps_contain_any(lookup_modifier(modifier)->plane_caps,
                                      INTEL_PLANE_CAP_CCS_MASK);
}

/**
 * intel_fb_is_rc_ccs_cc_modifier: Check if a modifier is an RC CCS CC modifier type
 * @modifier: Modifier to check
 *
 * Returns:
 * Returns %true if @modifier is a render with color clear modifier.
 */
bool intel_fb_is_rc_ccs_cc_modifier(u64 modifier)
{
        return plane_caps_contain_any(lookup_modifier(modifier)->plane_caps,
                                      INTEL_PLANE_CAP_CCS_RC_CC);
}

/**
 * intel_fb_is_mc_ccs_modifier: Check if a modifier is an MC CCS modifier type
 * @modifier: Modifier to check
 *
 * Returns:
 * Returns %true if @modifier is a media compression modifier.
 */
bool intel_fb_is_mc_ccs_modifier(u64 modifier)
{
        return plane_caps_contain_any(lookup_modifier(modifier)->plane_caps,
                                      INTEL_PLANE_CAP_CCS_MC);
}

/**
 * intel_fb_needs_64k_phys: Check if modifier requires 64k physical placement.
 * @modifier: Modifier to check
 *
 * Returns:
 * Returns %true if @modifier requires 64k aligned physical pages.
 */
bool intel_fb_needs_64k_phys(u64 modifier)
{
        const struct intel_modifier_desc *md = lookup_modifier_or_null(modifier);

        if (!md)
                return false;

        return plane_caps_contain_any(md->plane_caps,
                                      INTEL_PLANE_CAP_NEED64K_PHYS);
}

/**
 * intel_fb_is_tile4_modifier: Check if a modifier is a tile4 modifier type
 * @modifier: Modifier to check
 *
 * Returns:
 * Returns %true if @modifier is a tile4 modifier.
 */
bool intel_fb_is_tile4_modifier(u64 modifier)
{
        return plane_caps_contain_any(lookup_modifier(modifier)->plane_caps,
                                      INTEL_PLANE_CAP_TILING_4);
}

static bool check_modifier_display_ver_range(const struct intel_modifier_desc *md,
                                             u8 display_ver_from, u8 display_ver_until)
{
        return md->display_ver.from <= display_ver_until &&
                display_ver_from <= md->display_ver.until;
}

static bool plane_has_modifier(struct intel_display *display,
                               u8 plane_caps,
                               const struct intel_modifier_desc *md)
{
        if (!IS_DISPLAY_VER(display, md->display_ver.from, md->display_ver.until))
                return false;

        if (!plane_caps_contain_all(plane_caps, md->plane_caps))
                return false;

        /*
         * Separate AuxCCS and Flat CCS modifiers to be run only on platforms
         * where supported.
         */
        if (intel_fb_is_ccs_modifier(md->modifier) &&
            intel_parent_has_auxccs(display) != !!md->ccs.packed_aux_planes)
                return false;

        if (md->modifier == I915_FORMAT_MOD_4_TILED_BMG_CCS &&
            (DISPLAY_VER(display) < 14 || !display->platform.dgfx))
                return false;

        if (md->modifier == I915_FORMAT_MOD_4_TILED_LNL_CCS &&
            (DISPLAY_VER(display) < 20 || display->platform.dgfx))
                return false;

        return true;
}

/**
 * intel_fb_plane_get_modifiers: Get the modifiers for the given platform and plane capabilities
 * @display: display instance
 * @plane_caps: capabilities for the plane the modifiers are queried for
 *
 * Returns:
 * Returns the list of modifiers allowed by the @display platform and @plane_caps.
 * The caller must free the returned buffer.
 */
u64 *intel_fb_plane_get_modifiers(struct intel_display *display,
                                  u8 plane_caps)
{
        u64 *list, *p;
        int count = 1;          /* +1 for invalid modifier terminator */
        int i;

        for (i = 0; i < ARRAY_SIZE(intel_modifiers); i++) {
                if (plane_has_modifier(display, plane_caps, &intel_modifiers[i]))
                        count++;
        }

        list = kmalloc_array(count, sizeof(*list), GFP_KERNEL);
        if (drm_WARN_ON(display->drm, !list))
                return NULL;

        p = list;
        for (i = 0; i < ARRAY_SIZE(intel_modifiers); i++) {
                if (plane_has_modifier(display, plane_caps, &intel_modifiers[i]))
                        *p++ = intel_modifiers[i].modifier;
        }
        *p++ = DRM_FORMAT_MOD_INVALID;

        return list;
}

/**
 * intel_fb_plane_supports_modifier: Determine if a modifier is supported by the given plane
 * @plane: Plane to check the modifier support for
 * @modifier: The modifier to check the support for
 *
 * Returns:
 * %true if the @modifier is supported on @plane.
 */
bool intel_fb_plane_supports_modifier(struct intel_plane *plane, u64 modifier)
{
        int i;

        for (i = 0; i < plane->base.modifier_count; i++)
                if (plane->base.modifiers[i] == modifier)
                        return true;

        return false;
}

static bool format_is_yuv_semiplanar(const struct intel_modifier_desc *md,
                                     const struct drm_format_info *info)
{
        if (!info->is_yuv)
                return false;

        if (hweight8(md->ccs.planar_aux_planes) == 2)
                return info->num_planes == 4;
        else
                return info->num_planes == 2;
}

/**
 * intel_format_info_is_yuv_semiplanar: Check if the given format is YUV semiplanar
 * @info: format to check
 * @modifier: modifier used with the format
 *
 * Returns:
 * %true if @info / @modifier is YUV semiplanar.
 */
bool intel_format_info_is_yuv_semiplanar(const struct drm_format_info *info,
                                         u64 modifier)
{
        return format_is_yuv_semiplanar(lookup_modifier(modifier), info);
}

static u8 ccs_aux_plane_mask(const struct intel_modifier_desc *md,
                             const struct drm_format_info *format)
{
        if (format_is_yuv_semiplanar(md, format))
                return md->ccs.planar_aux_planes;
        else
                return md->ccs.packed_aux_planes;
}

/**
 * intel_fb_is_ccs_aux_plane: Check if a framebuffer color plane is a CCS AUX plane
 * @fb: Framebuffer
 * @color_plane: color plane index to check
 *
 * Returns:
 * Returns %true if @fb's color plane at index @color_plane is a CCS AUX plane.
 */
bool intel_fb_is_ccs_aux_plane(const struct drm_framebuffer *fb, int color_plane)
{
        const struct intel_modifier_desc *md = lookup_modifier(fb->modifier);

        return ccs_aux_plane_mask(md, fb->format) & BIT(color_plane);
}

/**
 * intel_fb_is_gen12_ccs_aux_plane: Check if a framebuffer color plane is a GEN12 CCS AUX plane
 * @fb: Framebuffer
 * @color_plane: color plane index to check
 *
 * Returns:
 * Returns %true if @fb's color plane at index @color_plane is a GEN12 CCS AUX plane.
 */
static bool intel_fb_is_gen12_ccs_aux_plane(const struct drm_framebuffer *fb, int color_plane)
{
        const struct intel_modifier_desc *md = lookup_modifier(fb->modifier);

        return check_modifier_display_ver_range(md, 12, 14) &&
               ccs_aux_plane_mask(md, fb->format) & BIT(color_plane);
}

/**
 * intel_fb_rc_ccs_cc_plane: Get the CCS CC color plane index for a framebuffer
 * @fb: Framebuffer
 *
 * Returns:
 * Returns the index of the color clear plane for @fb, or -1 if @fb is not a
 * framebuffer using a render compression/color clear modifier.
 */
int intel_fb_rc_ccs_cc_plane(const struct drm_framebuffer *fb)
{
        const struct intel_modifier_desc *md = lookup_modifier(fb->modifier);

        if (!md->ccs.cc_planes)
                return -1;

        drm_WARN_ON_ONCE(fb->dev, hweight8(md->ccs.cc_planes) > 1);

        return ilog2((int)md->ccs.cc_planes);
}

static bool is_gen12_ccs_cc_plane(const struct drm_framebuffer *fb, int color_plane)
{
        return intel_fb_rc_ccs_cc_plane(fb) == color_plane;
}

bool is_surface_linear(const struct drm_framebuffer *fb, int color_plane)
{
        return fb->modifier == DRM_FORMAT_MOD_LINEAR ||
               intel_fb_is_gen12_ccs_aux_plane(fb, color_plane) ||
               is_gen12_ccs_cc_plane(fb, color_plane);
}

int main_to_ccs_plane(const struct drm_framebuffer *fb, int main_plane)
{
        drm_WARN_ON(fb->dev, !intel_fb_is_ccs_modifier(fb->modifier) ||
                    (main_plane && main_plane >= fb->format->num_planes / 2));

        return fb->format->num_planes / 2 + main_plane;
}

int skl_ccs_to_main_plane(const struct drm_framebuffer *fb, int ccs_plane)
{
        drm_WARN_ON(fb->dev, !intel_fb_is_ccs_modifier(fb->modifier) ||
                    ccs_plane < fb->format->num_planes / 2);

        if (is_gen12_ccs_cc_plane(fb, ccs_plane))
                return 0;

        return ccs_plane - fb->format->num_planes / 2;
}

static unsigned int gen12_ccs_aux_stride(struct intel_framebuffer *fb, int ccs_plane)
{
        int main_plane = skl_ccs_to_main_plane(&fb->base, ccs_plane);
        unsigned int main_stride = fb->base.pitches[main_plane];
        unsigned int main_tile_width = intel_tile_width_bytes(&fb->base, main_plane);

        return DIV_ROUND_UP(main_stride, 4 * main_tile_width) * 64;
}

int skl_main_to_aux_plane(const struct drm_framebuffer *fb, int main_plane)
{
        const struct intel_modifier_desc *md = lookup_modifier(fb->modifier);
        struct intel_display *display = to_intel_display(fb->dev);

        if (md->ccs.packed_aux_planes | md->ccs.planar_aux_planes)
                return main_to_ccs_plane(fb, main_plane);
        else if (DISPLAY_VER(display) < 11 &&
                 format_is_yuv_semiplanar(md, fb->format))
                return 1;
        else
                return 0;
}

unsigned int intel_tile_size(struct intel_display *display)
{
        return DISPLAY_VER(display) == 2 ? 2048 : 4096;
}

unsigned int
intel_tile_width_bytes(const struct drm_framebuffer *fb, int color_plane)
{
        struct intel_display *display = to_intel_display(fb->dev);
        unsigned int cpp = fb->format->cpp[color_plane];

        switch (fb->modifier) {
        case DRM_FORMAT_MOD_LINEAR:
                return intel_tile_size(display);
        case I915_FORMAT_MOD_X_TILED:
                if (DISPLAY_VER(display) == 2)
                        return 128;
                else
                        return 512;
        case I915_FORMAT_MOD_4_TILED_BMG_CCS:
        case I915_FORMAT_MOD_4_TILED_LNL_CCS:
        case I915_FORMAT_MOD_4_TILED_DG2_RC_CCS:
        case I915_FORMAT_MOD_4_TILED_DG2_RC_CCS_CC:
        case I915_FORMAT_MOD_4_TILED_DG2_MC_CCS:
        case I915_FORMAT_MOD_4_TILED:
                /*
                 * Each 4K tile consists of 64B(8*8) subtiles, with
                 * same shape as Y Tile(i.e 4*16B OWords)
                 */
                return 128;
        case I915_FORMAT_MOD_Y_TILED_CCS:
                if (intel_fb_is_ccs_aux_plane(fb, color_plane))
                        return 128;
                fallthrough;
        case I915_FORMAT_MOD_4_TILED_MTL_RC_CCS:
        case I915_FORMAT_MOD_4_TILED_MTL_RC_CCS_CC:
        case I915_FORMAT_MOD_4_TILED_MTL_MC_CCS:
        case I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS:
        case I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC:
        case I915_FORMAT_MOD_Y_TILED_GEN12_MC_CCS:
                if (intel_fb_is_ccs_aux_plane(fb, color_plane) ||
                    is_gen12_ccs_cc_plane(fb, color_plane))
                        return 64;
                fallthrough;
        case I915_FORMAT_MOD_Y_TILED:
                if (HAS_128B_Y_TILING(display))
                        return 128;
                else
                        return 512;
        case I915_FORMAT_MOD_Yf_TILED_CCS:
                if (intel_fb_is_ccs_aux_plane(fb, color_plane))
                        return 128;
                fallthrough;
        case I915_FORMAT_MOD_Yf_TILED:
                switch (cpp) {
                case 1:
                        return 64;
                case 2:
                case 4:
                        return 128;
                case 8:
                case 16:
                        return 256;
                default:
                        MISSING_CASE(cpp);
                        return cpp;
                }
                break;
        default:
                MISSING_CASE(fb->modifier);
                return cpp;
        }
}

unsigned int intel_tile_height(const struct drm_framebuffer *fb, int color_plane)
{
        struct intel_display *display = to_intel_display(fb->dev);

        return intel_tile_size(display) /
                intel_tile_width_bytes(fb, color_plane);
}

/*
 * Return the tile dimensions in pixel units, based on the (2 or 4 kbyte) GTT
 * page tile size.
 */
static void intel_tile_dims(const struct drm_framebuffer *fb, int color_plane,
                            unsigned int *tile_width,
                            unsigned int *tile_height)
{
        unsigned int tile_width_bytes = intel_tile_width_bytes(fb, color_plane);
        unsigned int cpp = fb->format->cpp[color_plane];

        *tile_width = tile_width_bytes / cpp;
        *tile_height = intel_tile_height(fb, color_plane);
}

/*
 * Return the tile dimensions in pixel units, based on the tile block size.
 * The block covers the full GTT page sized tile on all tiled surfaces and
 * it's a 64 byte portion of the tile on TGL+ CCS surfaces.
 */
static void intel_tile_block_dims(const struct drm_framebuffer *fb, int color_plane,
                                  unsigned int *tile_width,
                                  unsigned int *tile_height)
{
        intel_tile_dims(fb, color_plane, tile_width, tile_height);

        if (intel_fb_is_gen12_ccs_aux_plane(fb, color_plane))
                *tile_height = 1;
}

unsigned int intel_tile_row_size(const struct drm_framebuffer *fb, int color_plane)
{
        unsigned int tile_width, tile_height;

        intel_tile_dims(fb, color_plane, &tile_width, &tile_height);

        return fb->pitches[color_plane] * tile_height;
}

unsigned int
intel_fb_align_height(const struct drm_framebuffer *fb,
                      int color_plane, unsigned int height)
{
        unsigned int tile_height = intel_tile_height(fb, color_plane);

        return ALIGN(height, tile_height);
}

bool intel_fb_modifier_uses_dpt(struct intel_display *display, u64 modifier)
{
        return HAS_DPT(display) && modifier != DRM_FORMAT_MOD_LINEAR;
}

bool intel_fb_uses_dpt(const struct drm_framebuffer *fb)
{
        struct intel_display *display = to_intel_display(fb->dev);

        return display->params.enable_dpt &&
                intel_fb_modifier_uses_dpt(display, fb->modifier);
}

void intel_fb_plane_get_subsampling(int *hsub, int *vsub,
                                    const struct drm_framebuffer *fb,
                                    int color_plane)
{
        int main_plane;

        if (color_plane == 0) {
                *hsub = 1;
                *vsub = 1;

                return;
        }

        /*
         * TODO: Deduct the subsampling from the char block for all CCS
         * formats and planes.
         */
        if (!intel_fb_is_gen12_ccs_aux_plane(fb, color_plane)) {
                *hsub = fb->format->hsub;
                *vsub = fb->format->vsub;

                return;
        }

        main_plane = skl_ccs_to_main_plane(fb, color_plane);
        *hsub = drm_format_info_block_width(fb->format, color_plane) /
                drm_format_info_block_width(fb->format, main_plane);

        /*
         * The min stride check in the core framebuffer_check() function
         * assumes that format->hsub applies to every plane except for the
         * first plane. That's incorrect for the CCS AUX plane of the first
         * plane, but for the above check to pass we must define the block
         * width with that subsampling applied to it. Adjust the width here
         * accordingly, so we can calculate the actual subsampling factor.
         */
        if (main_plane == 0)
                *hsub *= fb->format->hsub;

        *vsub = 32;
}

static void intel_fb_plane_dims(const struct intel_framebuffer *fb, int color_plane, int *w, int *h)
{
        int main_plane = intel_fb_is_ccs_aux_plane(&fb->base, color_plane) ?
                         skl_ccs_to_main_plane(&fb->base, color_plane) : 0;
        unsigned int main_width = fb->base.width;
        unsigned int main_height = fb->base.height;
        int main_hsub, main_vsub;
        int hsub, vsub;

        intel_fb_plane_get_subsampling(&main_hsub, &main_vsub, &fb->base, main_plane);
        intel_fb_plane_get_subsampling(&hsub, &vsub, &fb->base, color_plane);

        *w = DIV_ROUND_UP(main_width, main_hsub * hsub);
        *h = DIV_ROUND_UP(main_height, main_vsub * vsub);
}

static u32 intel_adjust_tile_offset(int *x, int *y,
                                    unsigned int tile_width,
                                    unsigned int tile_height,
                                    unsigned int tile_size,
                                    unsigned int pitch_tiles,
                                    u32 old_offset,
                                    u32 new_offset)
{
        unsigned int pitch_pixels = pitch_tiles * tile_width;
        unsigned int tiles;

        WARN_ON(old_offset & (tile_size - 1));
        WARN_ON(new_offset & (tile_size - 1));
        WARN_ON(new_offset > old_offset);

        tiles = (old_offset - new_offset) / tile_size;

        *y += tiles / pitch_tiles * tile_height;
        *x += tiles % pitch_tiles * tile_width;

        /* minimize x in case it got needlessly big */
        *y += *x / pitch_pixels * tile_height;
        *x %= pitch_pixels;

        return new_offset;
}

static u32 intel_adjust_linear_offset(int *x, int *y,
                                      unsigned int cpp,
                                      unsigned int pitch,
                                      u32 old_offset,
                                      u32 new_offset)
{
        old_offset += *y * pitch + *x * cpp;

        *y = (old_offset - new_offset) / pitch;
        *x = ((old_offset - new_offset) - *y * pitch) / cpp;

        return new_offset;
}

static u32 intel_adjust_aligned_offset(int *x, int *y,
                                       const struct drm_framebuffer *fb,
                                       int color_plane,
                                       unsigned int rotation,
                                       unsigned int pitch,
                                       u32 old_offset, u32 new_offset)
{
        struct intel_display *display = to_intel_display(fb->dev);
        unsigned int cpp = fb->format->cpp[color_plane];

        drm_WARN_ON(display->drm, new_offset > old_offset);

        if (!is_surface_linear(fb, color_plane)) {
                unsigned int tile_size, tile_width, tile_height;
                unsigned int pitch_tiles;

                tile_size = intel_tile_size(display);
                intel_tile_dims(fb, color_plane, &tile_width, &tile_height);

                if (drm_rotation_90_or_270(rotation)) {
                        pitch_tiles = pitch / tile_height;
                        swap(tile_width, tile_height);
                } else {
                        pitch_tiles = pitch / (tile_width * cpp);
                }

                intel_adjust_tile_offset(x, y, tile_width, tile_height,
                                         tile_size, pitch_tiles,
                                         old_offset, new_offset);
        } else {
                intel_adjust_linear_offset(x, y, cpp, pitch,
                                           old_offset, new_offset);
        }

        return new_offset;
}

/*
 * Adjust the tile offset by moving the difference into
 * the x/y offsets.
 */
u32 intel_plane_adjust_aligned_offset(int *x, int *y,
                                      const struct intel_plane_state *plane_state,
                                      int color_plane,
                                      u32 old_offset, u32 new_offset)
{
        return intel_adjust_aligned_offset(x, y, plane_state->hw.fb, color_plane,
                                           plane_state->hw.rotation,
                                           plane_state->view.color_plane[color_plane].mapping_stride,
                                           old_offset, new_offset);
}

/*
 * Computes the aligned offset to the base tile and adjusts
 * x, y. bytes per pixel is assumed to be a power-of-two.
 *
 * In the 90/270 rotated case, x and y are assumed
 * to be already rotated to match the rotated GTT view, and
 * pitch is the tile_height aligned framebuffer height.
 *
 * This function is used when computing the derived information
 * under intel_framebuffer, so using any of that information
 * here is not allowed. Anything under drm_framebuffer can be
 * used. This is why the user has to pass in the pitch since it
 * is specified in the rotated orientation.
 */
static u32 intel_compute_aligned_offset(struct intel_display *display,
                                        int *x, int *y,
                                        const struct drm_framebuffer *fb,
                                        int color_plane,
                                        unsigned int pitch,
                                        unsigned int rotation,
                                        unsigned int alignment)
{
        unsigned int cpp = fb->format->cpp[color_plane];
        u32 offset, offset_aligned;

        if (!is_surface_linear(fb, color_plane)) {
                unsigned int tile_size, tile_width, tile_height;
                unsigned int tile_rows, tiles, pitch_tiles;

                tile_size = intel_tile_size(display);
                intel_tile_dims(fb, color_plane, &tile_width, &tile_height);

                if (drm_rotation_90_or_270(rotation)) {
                        pitch_tiles = pitch / tile_height;
                        swap(tile_width, tile_height);
                } else {
                        pitch_tiles = pitch / (tile_width * cpp);
                }

                tile_rows = *y / tile_height;
                *y %= tile_height;

                tiles = *x / tile_width;
                *x %= tile_width;

                offset = (tile_rows * pitch_tiles + tiles) * tile_size;

                offset_aligned = offset;
                if (alignment)
                        offset_aligned = rounddown(offset_aligned, alignment);

                intel_adjust_tile_offset(x, y, tile_width, tile_height,
                                         tile_size, pitch_tiles,
                                         offset, offset_aligned);
        } else {
                offset = *y * pitch + *x * cpp;
                offset_aligned = offset;
                if (alignment) {
                        offset_aligned = rounddown(offset_aligned, alignment);
                        *y = (offset % alignment) / pitch;
                        *x = ((offset % alignment) - *y * pitch) / cpp;
                } else {
                        *y = *x = 0;
                }
        }

        return offset_aligned;
}

u32 intel_plane_compute_aligned_offset(int *x, int *y,
                                       const struct intel_plane_state *plane_state,
                                       int color_plane)
{
        struct intel_display *display = to_intel_display(plane_state);
        struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane);
        const struct drm_framebuffer *fb = plane_state->hw.fb;
        unsigned int rotation = plane_state->hw.rotation;
        unsigned int pitch = plane_state->view.color_plane[color_plane].mapping_stride;
        unsigned int alignment = plane->min_alignment(plane, fb, color_plane);

        return intel_compute_aligned_offset(display, x, y, fb, color_plane,
                                            pitch, rotation, alignment);
}

/* Convert the fb->offset[] into x/y offsets */
static int intel_fb_offset_to_xy(int *x, int *y,
                                 const struct drm_framebuffer *fb,
                                 int color_plane)
{
        struct intel_display *display = to_intel_display(fb->dev);
        unsigned int height, alignment, unused;

        if (fb->modifier != DRM_FORMAT_MOD_LINEAR)
                alignment = intel_tile_size(display);
        else
                alignment = 0;

        if (alignment != 0 && fb->offsets[color_plane] % alignment) {
                drm_dbg_kms(display->drm,
                            "Misaligned offset 0x%08x for color plane %d\n",
                            fb->offsets[color_plane], color_plane);
                return -EINVAL;
        }

        height = drm_format_info_plane_height(fb->format, fb->height, color_plane);
        height = ALIGN(height, intel_tile_height(fb, color_plane));

        /* Catch potential overflows early */
        if (check_add_overflow(mul_u32_u32(height, fb->pitches[color_plane]),
                               fb->offsets[color_plane], &unused)) {
                drm_dbg_kms(display->drm,
                            "Bad offset 0x%08x or pitch %d for color plane %d\n",
                            fb->offsets[color_plane], fb->pitches[color_plane],
                            color_plane);
                return -ERANGE;
        }

        *x = 0;
        *y = 0;

        intel_adjust_aligned_offset(x, y,
                                    fb, color_plane, DRM_MODE_ROTATE_0,
                                    fb->pitches[color_plane],
                                    fb->offsets[color_plane], 0);

        return 0;
}

static int intel_fb_check_ccs_xy(const struct drm_framebuffer *fb, int ccs_plane, int x, int y)
{
        struct intel_display *display = to_intel_display(fb->dev);
        const struct intel_framebuffer *intel_fb = to_intel_framebuffer(fb);
        int main_plane;
        int hsub, vsub;
        int tile_width, tile_height;
        int ccs_x, ccs_y;
        int main_x, main_y;

        if (!intel_fb_is_ccs_aux_plane(fb, ccs_plane))
                return 0;

        /*
         * While all the tile dimensions are based on a 2k or 4k GTT page size
         * here the main and CCS coordinates must match only within a (64 byte
         * on TGL+) block inside the tile.
         */
        intel_tile_block_dims(fb, ccs_plane, &tile_width, &tile_height);
        intel_fb_plane_get_subsampling(&hsub, &vsub, fb, ccs_plane);

        tile_width *= hsub;
        tile_height *= vsub;

        ccs_x = (x * hsub) % tile_width;
        ccs_y = (y * vsub) % tile_height;

        main_plane = skl_ccs_to_main_plane(fb, ccs_plane);
        main_x = intel_fb->normal_view.color_plane[main_plane].x % tile_width;
        main_y = intel_fb->normal_view.color_plane[main_plane].y % tile_height;

        /*
         * CCS doesn't have its own x/y offset register, so the intra CCS tile
         * x/y offsets must match between CCS and the main surface.
         */
        if (main_x != ccs_x || main_y != ccs_y) {
                drm_dbg_kms(display->drm,
                            "Bad CCS x/y (main %d,%d ccs %d,%d) full (main %d,%d ccs %d,%d)\n",
                            main_x, main_y, ccs_x, ccs_y,
                            intel_fb->normal_view.color_plane[main_plane].x,
                            intel_fb->normal_view.color_plane[main_plane].y,
                            x, y);
                return -EINVAL;
        }

        return 0;
}

static bool intel_plane_can_remap(const struct intel_plane_state *plane_state)
{
        struct intel_display *display = to_intel_display(plane_state);
        struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane);
        const struct drm_framebuffer *fb = plane_state->hw.fb;
        int i;

        /* We don't want to deal with remapping with cursors */
        if (plane->id == PLANE_CURSOR)
                return false;

        /*
         * The display engine limits already match/exceed the
         * render engine limits, so not much point in remapping.
         * Would also need to deal with the fence POT alignment
         * and gen2 2KiB GTT tile size.
         */
        if (DISPLAY_VER(display) < 4)
                return false;

        /*
         * The new CCS hash mode isn't compatible with remapping as
         * the virtual address of the pages affects the compressed data.
         */
        if (intel_fb_is_ccs_modifier(fb->modifier))
                return false;

        /* Linear needs a page aligned stride for remapping */
        if (fb->modifier == DRM_FORMAT_MOD_LINEAR) {
                unsigned int alignment = intel_tile_size(display) - 1;

                for (i = 0; i < fb->format->num_planes; i++) {
                        if (fb->pitches[i] & alignment)
                                return false;
                }
        }

        return true;
}

bool intel_fb_needs_pot_stride_remap(const struct intel_framebuffer *fb)
{
        struct intel_display *display = to_intel_display(fb->base.dev);

        return (display->platform.alderlake_p || DISPLAY_VER(display) >= 14) &&
                intel_fb_uses_dpt(&fb->base);
}

bool intel_plane_uses_fence(const struct intel_plane_state *plane_state)
{
        struct intel_display *display = to_intel_display(plane_state);
        struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane);

        return DISPLAY_VER(display) < 4 ||
                (plane->fbc && !plane_state->no_fbc_reason &&
                 plane_state->view.gtt.type == I915_GTT_VIEW_NORMAL);
}

static int intel_fb_pitch(const struct intel_framebuffer *fb, int color_plane, unsigned int rotation)
{
        if (drm_rotation_90_or_270(rotation))
                return fb->rotated_view.color_plane[color_plane].mapping_stride;
        else if (intel_fb_needs_pot_stride_remap(fb))
                return fb->remapped_view.color_plane[color_plane].mapping_stride;
        else
                return fb->normal_view.color_plane[color_plane].mapping_stride;
}

static bool intel_plane_needs_remap(const struct intel_plane_state *plane_state)
{
        struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane);
        const struct intel_framebuffer *fb = to_intel_framebuffer(plane_state->hw.fb);
        unsigned int rotation = plane_state->hw.rotation;
        u32 stride, max_stride;

        /*
         * No remapping for invisible planes since we don't have
         * an actual source viewport to remap.
         */
        if (!plane_state->uapi.visible)
                return false;

        if (!intel_plane_can_remap(plane_state))
                return false;

        /*
         * FIXME: aux plane limits on gen9+ are
         * unclear in Bspec, for now no checking.
         */
        stride = intel_fb_pitch(fb, 0, rotation);
        max_stride = plane->max_stride(plane, fb->base.format,
                                       fb->base.modifier, rotation);

        return stride > max_stride;
}

static int convert_plane_offset_to_xy(const struct intel_framebuffer *fb, int color_plane,
                                      int plane_width, int *x, int *y)
{
        struct intel_display *display = to_intel_display(fb->base.dev);
        struct drm_gem_object *obj = intel_fb_bo(&fb->base);
        int ret;

        ret = intel_fb_offset_to_xy(x, y, &fb->base, color_plane);
        if (ret) {
                drm_dbg_kms(display->drm,
                            "bad fb plane %d offset: 0x%x\n",
                            color_plane, fb->base.offsets[color_plane]);
                return ret;
        }

        ret = intel_fb_check_ccs_xy(&fb->base, color_plane, *x, *y);
        if (ret)
                return ret;

        /*
         * The fence (if used) is aligned to the start of the object
         * so having the framebuffer wrap around across the edge of the
         * fenced region doesn't really work. We have no API to configure
         * the fence start offset within the object (nor could we probably
         * on gen2/3). So it's just easier if we just require that the
         * fb layout agrees with the fence layout. We already check that the
         * fb stride matches the fence stride elsewhere.
         */
        if (color_plane == 0 && intel_bo_is_tiled(obj) &&
            (*x + plane_width) * fb->base.format->cpp[color_plane] > fb->base.pitches[color_plane]) {
                drm_dbg_kms(display->drm,
                            "bad fb plane %d offset: 0x%x\n",
                            color_plane, fb->base.offsets[color_plane]);
                return -EINVAL;
        }

        return 0;
}

static u32 calc_plane_aligned_offset(const struct intel_framebuffer *fb, int color_plane, int *x, int *y)
{
        struct intel_display *display = to_intel_display(fb->base.dev);
        unsigned int tile_size = intel_tile_size(display);
        u32 offset;

        offset = intel_compute_aligned_offset(display, x, y, &fb->base, color_plane,
                                              fb->base.pitches[color_plane],
                                              DRM_MODE_ROTATE_0,
                                              tile_size);

        return offset / tile_size;
}

struct fb_plane_view_dims {
        unsigned int width, height;
        unsigned int tile_width, tile_height;
};

static void init_plane_view_dims(const struct intel_framebuffer *fb, int color_plane,
                                 unsigned int width, unsigned int height,
                                 struct fb_plane_view_dims *dims)
{
        dims->width = width;
        dims->height = height;

        intel_tile_dims(&fb->base, color_plane, &dims->tile_width, &dims->tile_height);
}

static unsigned int
plane_view_src_stride_tiles(const struct intel_framebuffer *fb, int color_plane,
                            const struct fb_plane_view_dims *dims)
{
        return DIV_ROUND_UP(fb->base.pitches[color_plane],
                            dims->tile_width * fb->base.format->cpp[color_plane]);
}

static unsigned int
plane_view_dst_stride_tiles(const struct intel_framebuffer *fb, int color_plane,
                            unsigned int pitch_tiles)
{
        if (intel_fb_needs_pot_stride_remap(fb)) {
                /*
                 * ADL_P, the only platform needing a POT stride has a minimum
                 * of 8 main surface tiles.
                 */
                return roundup_pow_of_two(max(pitch_tiles, 8u));
        } else {
                return pitch_tiles;
        }
}

static unsigned int
plane_view_scanout_stride(const struct intel_framebuffer *fb, int color_plane,
                          unsigned int tile_width,
                          unsigned int src_stride_tiles, unsigned int dst_stride_tiles)
{
        struct intel_display *display = to_intel_display(fb->base.dev);
        unsigned int stride_tiles;

        if ((display->platform.alderlake_p || DISPLAY_VER(display) >= 14) &&
            src_stride_tiles < dst_stride_tiles)
                stride_tiles = src_stride_tiles;
        else
                stride_tiles = dst_stride_tiles;

        return stride_tiles * tile_width * fb->base.format->cpp[color_plane];
}

static unsigned int
plane_view_width_tiles(const struct intel_framebuffer *fb, int color_plane,
                       const struct fb_plane_view_dims *dims,
                       int x)
{
        return DIV_ROUND_UP(x + dims->width, dims->tile_width);
}

static unsigned int
plane_view_height_tiles(const struct intel_framebuffer *fb, int color_plane,
                        const struct fb_plane_view_dims *dims,
                        int y)
{
        return DIV_ROUND_UP(y + dims->height, dims->tile_height);
}

static unsigned int
plane_view_linear_tiles(const struct intel_framebuffer *fb, int color_plane,
                        const struct fb_plane_view_dims *dims,
                        int x, int y)
{
        struct intel_display *display = to_intel_display(fb->base.dev);
        unsigned int size;

        size = (y + dims->height) * fb->base.pitches[color_plane] +
                x * fb->base.format->cpp[color_plane];

        return DIV_ROUND_UP(size, intel_tile_size(display));
}

#define assign_chk_ovf(display, var, val) ({ \
        drm_WARN_ON((display)->drm, overflows_type(val, var)); \
        (var) = (val); \
})

#define assign_bfld_chk_ovf(display, var, val) ({ \
        (var) = (val); \
        drm_WARN_ON((display)->drm, (var) != (val)); \
        (var); \
})

static u32 calc_plane_remap_info(const struct intel_framebuffer *fb, int color_plane,
                                 const struct fb_plane_view_dims *dims,
                                 u32 obj_offset, u32 gtt_offset, int x, int y,
                                 struct intel_fb_view *view)
{
        struct intel_display *display = to_intel_display(fb->base.dev);
        struct intel_remapped_plane_info *remap_info = &view->gtt.remapped.plane[color_plane];
        struct i915_color_plane_view *color_plane_info = &view->color_plane[color_plane];
        unsigned int tile_width = dims->tile_width;
        unsigned int tile_height = dims->tile_height;
        unsigned int tile_size = intel_tile_size(display);
        struct drm_rect r;
        u32 size = 0;

        assign_bfld_chk_ovf(display, remap_info->offset, obj_offset);

        if (intel_fb_is_gen12_ccs_aux_plane(&fb->base, color_plane)) {
                remap_info->linear = 1;

                assign_chk_ovf(display, remap_info->size,
                               plane_view_linear_tiles(fb, color_plane, dims, x, y));
        } else {
                remap_info->linear = 0;

                assign_chk_ovf(display, remap_info->src_stride,
                               plane_view_src_stride_tiles(fb, color_plane, dims));
                assign_chk_ovf(display, remap_info->width,
                               plane_view_width_tiles(fb, color_plane, dims, x));
                assign_chk_ovf(display, remap_info->height,
                               plane_view_height_tiles(fb, color_plane, dims, y));
        }

        if (view->gtt.type == I915_GTT_VIEW_ROTATED) {
                drm_WARN_ON(display->drm, remap_info->linear);
                check_array_bounds(display, view->gtt.rotated.plane, color_plane);

                assign_chk_ovf(display, remap_info->dst_stride,
                               plane_view_dst_stride_tiles(fb, color_plane, remap_info->height));

                /* rotate the x/y offsets to match the GTT view */
                drm_rect_init(&r, x, y, dims->width, dims->height);
                drm_rect_rotate(&r,
                                remap_info->width * tile_width,
                                remap_info->height * tile_height,
                                DRM_MODE_ROTATE_270);

                color_plane_info->x = r.x1;
                color_plane_info->y = r.y1;

                color_plane_info->mapping_stride = remap_info->dst_stride * tile_height;
                color_plane_info->scanout_stride = color_plane_info->mapping_stride;

                size += remap_info->dst_stride * remap_info->width;

                /* rotate the tile dimensions to match the GTT view */
                swap(tile_width, tile_height);
        } else {
                drm_WARN_ON(display->drm, view->gtt.type != I915_GTT_VIEW_REMAPPED);

                check_array_bounds(display, view->gtt.remapped.plane, color_plane);

                if (view->gtt.remapped.plane_alignment) {
                        u32 aligned_offset = ALIGN(gtt_offset,
                                                   view->gtt.remapped.plane_alignment);

                        size += aligned_offset - gtt_offset;
                        gtt_offset = aligned_offset;
                }

                color_plane_info->x = x;
                color_plane_info->y = y;

                if (remap_info->linear) {
                        color_plane_info->mapping_stride = fb->base.pitches[color_plane];
                        color_plane_info->scanout_stride = color_plane_info->mapping_stride;

                        size += remap_info->size;
                } else {
                        unsigned int dst_stride;

                        /*
                         * The hardware automagically calculates the CCS AUX surface
                         * stride from the main surface stride so can't really remap a
                         * smaller subset (unless we'd remap in whole AUX page units).
                         */
                        if (intel_fb_needs_pot_stride_remap(fb) &&
                            intel_fb_is_ccs_modifier(fb->base.modifier))
                                dst_stride = remap_info->src_stride;
                        else
                                dst_stride = remap_info->width;

                        dst_stride = plane_view_dst_stride_tiles(fb, color_plane, dst_stride);

                        assign_chk_ovf(display, remap_info->dst_stride, dst_stride);
                        color_plane_info->mapping_stride = dst_stride *
                                                           tile_width *
                                                           fb->base.format->cpp[color_plane];
                        color_plane_info->scanout_stride =
                                plane_view_scanout_stride(fb, color_plane, tile_width,
                                                          remap_info->src_stride,
                                                          dst_stride);

                        size += dst_stride * remap_info->height;
                }
        }

        /*
         * We only keep the x/y offsets, so push all of the gtt offset into
         * the x/y offsets.  x,y will hold the first pixel of the framebuffer
         * plane from the start of the remapped/rotated gtt mapping.
         */
        if (remap_info->linear)
                intel_adjust_linear_offset(&color_plane_info->x, &color_plane_info->y,
                                           fb->base.format->cpp[color_plane],
                                           color_plane_info->mapping_stride,
                                           gtt_offset * tile_size, 0);
        else
                intel_adjust_tile_offset(&color_plane_info->x, &color_plane_info->y,
                                         tile_width, tile_height,
                                         tile_size, remap_info->dst_stride,
                                         gtt_offset * tile_size, 0);

        return size;
}

#undef assign_chk_ovf

/* Return number of tiles @color_plane needs. */
static unsigned int
calc_plane_normal_size(const struct intel_framebuffer *fb, int color_plane,
                       const struct fb_plane_view_dims *dims,
                       int x, int y)
{
        unsigned int tiles;

        if (is_surface_linear(&fb->base, color_plane)) {
                tiles = plane_view_linear_tiles(fb, color_plane, dims, x, y);
        } else {
                tiles = plane_view_src_stride_tiles(fb, color_plane, dims) *
                        plane_view_height_tiles(fb, color_plane, dims, y);
                /*
                 * If the plane isn't horizontally tile aligned,
                 * we need one more tile.
                 */
                if (x != 0)
                        tiles++;
        }

        return tiles;
}

static void intel_fb_view_init(struct intel_display *display,
                               struct intel_fb_view *view,
                               enum i915_gtt_view_type view_type)
{
        memset(view, 0, sizeof(*view));
        view->gtt.type = view_type;

        if (view_type == I915_GTT_VIEW_REMAPPED &&
            (display->platform.alderlake_p || DISPLAY_VER(display) >= 14))
                view->gtt.remapped.plane_alignment = SZ_2M / PAGE_SIZE;
}

bool intel_fb_supports_90_270_rotation(const struct intel_framebuffer *fb)
{
        struct intel_display *display = to_intel_display(fb->base.dev);

        if (DISPLAY_VER(display) >= 13)
                return false;

        return fb->base.modifier == I915_FORMAT_MOD_Y_TILED ||
               fb->base.modifier == I915_FORMAT_MOD_Yf_TILED;
}

static unsigned int intel_fb_min_alignment(const struct drm_framebuffer *fb)
{
        struct intel_display *display = to_intel_display(fb->dev);
        struct intel_plane *plane;
        unsigned int min_alignment = 0;

        for_each_intel_plane(display->drm, plane) {
                unsigned int plane_min_alignment;

                if (!drm_plane_has_format(&plane->base, fb->format->format, fb->modifier))
                        continue;

                plane_min_alignment = plane->min_alignment(plane, fb, 0);

                drm_WARN_ON(display->drm, plane_min_alignment &&
                            !is_power_of_2(plane_min_alignment));

                if (intel_plane_needs_physical(plane))
                        continue;

                min_alignment = max(min_alignment, plane_min_alignment);
        }

        return min_alignment;
}

static unsigned int intel_fb_vtd_guard(const struct drm_framebuffer *fb)
{
        struct intel_display *display = to_intel_display(fb->dev);
        struct intel_plane *plane;
        unsigned int vtd_guard = 0;

        for_each_intel_plane(display->drm, plane) {
                if (!drm_plane_has_format(&plane->base, fb->format->format, fb->modifier))
                        continue;

                vtd_guard = max_t(unsigned int, vtd_guard, plane->vtd_guard);
        }

        return vtd_guard;
}

int intel_fill_fb_info(struct intel_display *display, struct intel_framebuffer *fb)
{
        struct drm_gem_object *obj = intel_fb_bo(&fb->base);
        u32 gtt_offset_rotated = 0;
        u32 gtt_offset_remapped = 0;
        unsigned int max_size = 0;
        int i, num_planes = fb->base.format->num_planes;
        unsigned int tile_size = intel_tile_size(display);

        intel_fb_view_init(display, &fb->normal_view, I915_GTT_VIEW_NORMAL);

        drm_WARN_ON(display->drm,
                    intel_fb_supports_90_270_rotation(fb) &&
                    intel_fb_needs_pot_stride_remap(fb));

        if (intel_fb_supports_90_270_rotation(fb))
                intel_fb_view_init(display, &fb->rotated_view, I915_GTT_VIEW_ROTATED);
        if (intel_fb_needs_pot_stride_remap(fb))
                intel_fb_view_init(display, &fb->remapped_view, I915_GTT_VIEW_REMAPPED);

        for (i = 0; i < num_planes; i++) {
                struct fb_plane_view_dims view_dims;
                unsigned int width, height;
                unsigned int size;
                u32 offset;
                int x, y;
                int ret;

                /*
                 * Plane 2 of Render Compression with Clear Color fb modifier
                 * is consumed by the driver and not passed to DE. Skip the
                 * arithmetic related to alignment and offset calculation.
                 */
                if (is_gen12_ccs_cc_plane(&fb->base, i)) {
                        unsigned int end;

                        if (!IS_ALIGNED(fb->base.offsets[i], 64)) {
                                drm_dbg_kms(display->drm,
                                            "fb misaligned clear color plane %d offset (0x%x)\n",
                                            i, fb->base.offsets[i]);
                                return -EINVAL;
                        }

                        if (check_add_overflow(fb->base.offsets[i], 64, &end)) {
                                drm_dbg_kms(display->drm,
                                            "fb bad clear color plane %d offset (0x%x)\n",
                                            i, fb->base.offsets[i]);
                                return -EINVAL;
                        }

                        max_size = max(max_size, DIV_ROUND_UP(end, tile_size));
                        continue;
                }

                intel_fb_plane_dims(fb, i, &width, &height);

                ret = convert_plane_offset_to_xy(fb, i, width, &x, &y);
                if (ret)
                        return ret;

                init_plane_view_dims(fb, i, width, height, &view_dims);

                /*
                 * First pixel of the framebuffer from
                 * the start of the normal gtt mapping.
                 */
                fb->normal_view.color_plane[i].x = x;
                fb->normal_view.color_plane[i].y = y;
                fb->normal_view.color_plane[i].mapping_stride = fb->base.pitches[i];
                fb->normal_view.color_plane[i].scanout_stride =
                        fb->normal_view.color_plane[i].mapping_stride;

                offset = calc_plane_aligned_offset(fb, i, &x, &y);

                if (intel_fb_supports_90_270_rotation(fb))
                        gtt_offset_rotated += calc_plane_remap_info(fb, i, &view_dims,
                                                                    offset, gtt_offset_rotated, x, y,
                                                                    &fb->rotated_view);

                if (intel_fb_needs_pot_stride_remap(fb))
                        gtt_offset_remapped += calc_plane_remap_info(fb, i, &view_dims,
                                                                     offset, gtt_offset_remapped, x, y,
                                                                     &fb->remapped_view);

                size = calc_plane_normal_size(fb, i, &view_dims, x, y);
                /* how many tiles in total needed in the bo */
                max_size = max(max_size, offset + size);
        }

        if (mul_u32_u32(max_size, tile_size) > obj->size) {
                drm_dbg_kms(display->drm,
                            "fb too big for bo (need %llu bytes, have %zu bytes)\n",
                            mul_u32_u32(max_size, tile_size), obj->size);
                return -EINVAL;
        }

        fb->min_alignment = intel_fb_min_alignment(&fb->base);
        fb->vtd_guard = intel_fb_vtd_guard(&fb->base);

        return 0;
}

unsigned int intel_fb_view_vtd_guard(const struct drm_framebuffer *fb,
                                     const struct intel_fb_view *view,
                                     unsigned int rotation)
{
        unsigned int vtd_guard;
        int color_plane;

        vtd_guard = to_intel_framebuffer(fb)->vtd_guard;
        if (!vtd_guard)
                return 0;

        for (color_plane = 0; color_plane < fb->format->num_planes; color_plane++) {
                unsigned int stride, tile;

                if (intel_fb_is_ccs_aux_plane(fb, color_plane) ||
                    is_gen12_ccs_cc_plane(fb, color_plane))
                        continue;

                stride = view->color_plane[color_plane].mapping_stride;

                if (drm_rotation_90_or_270(rotation))
                        tile = intel_tile_height(fb, color_plane);
                else
                        tile = intel_tile_width_bytes(fb, color_plane);

                vtd_guard = max(vtd_guard, DIV_ROUND_UP(stride, tile));
        }

        return vtd_guard;
}

static void intel_plane_remap_gtt(struct intel_plane_state *plane_state)
{
        struct intel_display *display = to_intel_display(plane_state);
        struct drm_framebuffer *fb = plane_state->hw.fb;
        struct intel_framebuffer *intel_fb = to_intel_framebuffer(fb);
        unsigned int rotation = plane_state->hw.rotation;
        int i, num_planes = fb->format->num_planes;
        unsigned int src_x, src_y;
        unsigned int src_w, src_h;
        u32 gtt_offset = 0;

        intel_fb_view_init(display, &plane_state->view,
                           drm_rotation_90_or_270(rotation) ? I915_GTT_VIEW_ROTATED :
                                                              I915_GTT_VIEW_REMAPPED);

        src_x = plane_state->uapi.src.x1 >> 16;
        src_y = plane_state->uapi.src.y1 >> 16;
        src_w = drm_rect_width(&plane_state->uapi.src) >> 16;
        src_h = drm_rect_height(&plane_state->uapi.src) >> 16;

        drm_WARN_ON(display->drm, intel_fb_is_ccs_modifier(fb->modifier));

        /* Make src coordinates relative to the viewport */
        drm_rect_translate(&plane_state->uapi.src,
                           -(src_x << 16), -(src_y << 16));

        /* Rotate src coordinates to match rotated GTT view */
        if (drm_rotation_90_or_270(rotation))
                drm_rect_rotate(&plane_state->uapi.src,
                                src_w << 16, src_h << 16,
                                DRM_MODE_ROTATE_270);

        for (i = 0; i < num_planes; i++) {
                unsigned int hsub = i ? fb->format->hsub : 1;
                unsigned int vsub = i ? fb->format->vsub : 1;
                struct fb_plane_view_dims view_dims;
                unsigned int width, height;
                unsigned int x, y;
                u32 offset;

                x = src_x / hsub;
                y = src_y / vsub;
                width = src_w / hsub;
                height = src_h / vsub;

                init_plane_view_dims(intel_fb, i, width, height, &view_dims);

                /*
                 * First pixel of the src viewport from the
                 * start of the normal gtt mapping.
                 */
                x += intel_fb->normal_view.color_plane[i].x;
                y += intel_fb->normal_view.color_plane[i].y;

                offset = calc_plane_aligned_offset(intel_fb, i, &x, &y);

                gtt_offset += calc_plane_remap_info(intel_fb, i, &view_dims,
                                                    offset, gtt_offset, x, y,
                                                    &plane_state->view);
        }
}

unsigned int intel_rotation_info_size(const struct intel_rotation_info *rot_info)
{
        unsigned int size = 0;
        int i;

        for (i = 0 ; i < ARRAY_SIZE(rot_info->plane); i++)
                size += rot_info->plane[i].dst_stride * rot_info->plane[i].width;

        return size;
}

unsigned int intel_remapped_info_size(const struct intel_remapped_info *rem_info)
{
        unsigned int size = 0;
        int i;

        for (i = 0 ; i < ARRAY_SIZE(rem_info->plane); i++) {
                unsigned int plane_size;

                if (rem_info->plane[i].linear)
                        plane_size = rem_info->plane[i].size;
                else
                        plane_size = rem_info->plane[i].dst_stride * rem_info->plane[i].height;

                if (plane_size == 0)
                        continue;

                if (rem_info->plane_alignment)
                        size = ALIGN(size, rem_info->plane_alignment);

                size += plane_size;
        }

        return size;
}

void intel_fb_fill_view(const struct intel_framebuffer *fb, unsigned int rotation,
                        struct intel_fb_view *view)
{
        if (drm_rotation_90_or_270(rotation))
                *view = fb->rotated_view;
        else if (intel_fb_needs_pot_stride_remap(fb))
                *view = fb->remapped_view;
        else
                *view = fb->normal_view;
}

/*
 * Convert the x/y offsets into a linear offset.
 * Only valid with 0/180 degree rotation, which is fine since linear
 * offset is only used with linear buffers on pre-hsw and tiled buffers
 * with gen2/3, and 90/270 degree rotations isn't supported on any of them.
 */
u32 intel_fb_xy_to_linear(int x, int y,
                          const struct intel_plane_state *plane_state,
                          int color_plane)
{
        const struct drm_framebuffer *fb = plane_state->hw.fb;
        unsigned int cpp = fb->format->cpp[color_plane];
        unsigned int pitch = plane_state->view.color_plane[color_plane].mapping_stride;

        return y * pitch + x * cpp;
}

/*
 * Add the x/y offsets derived from fb->offsets[] to the user
 * specified plane src x/y offsets. The resulting x/y offsets
 * specify the start of scanout from the beginning of the gtt mapping.
 */
void intel_add_fb_offsets(int *x, int *y,
                          const struct intel_plane_state *plane_state,
                          int color_plane)

{
        *x += plane_state->view.color_plane[color_plane].x;
        *y += plane_state->view.color_plane[color_plane].y;
}

static
u32 intel_fb_max_stride(struct intel_display *display,
                        const struct drm_format_info *info,
                        u64 modifier)
{
        /*
         * Arbitrary limit for gen4+ chosen to match the
         * render engine max stride.
         *
         * The new CCS hash mode makes remapping impossible
         */
        if (DISPLAY_VER(display) < 4 || intel_fb_is_ccs_modifier(modifier) ||
            intel_fb_modifier_uses_dpt(display, modifier))
                return intel_plane_fb_max_stride(display, info, modifier);
        else if (DISPLAY_VER(display) >= 7)
                return 256 * 1024;
        else
                return 128 * 1024;
}

static unsigned int
intel_fb_stride_alignment(const struct drm_framebuffer *fb, int color_plane)
{
        struct intel_display *display = to_intel_display(fb->dev);
        unsigned int tile_width;

        if (is_surface_linear(fb, color_plane)) {
                unsigned int max_stride = intel_plane_fb_max_stride(display,
                                                                    fb->format,
                                                                    fb->modifier);

                /*
                 * To make remapping with linear generally feasible
                 * we need the stride to be page aligned.
                 */
                if (fb->pitches[color_plane] > max_stride &&
                    !intel_fb_is_ccs_modifier(fb->modifier))
                        return intel_tile_size(display);
                else
                        return 64;
        }

        tile_width = intel_tile_width_bytes(fb, color_plane);
        if (intel_fb_is_ccs_modifier(fb->modifier)) {
                /*
                 * On TGL the surface stride must be 4 tile aligned, mapped by
                 * one 64 byte cacheline on the CCS AUX surface.
                 */
                if (DISPLAY_VER(display) >= 12)
                        tile_width *= 4;
                /*
                 * Display WA #0531: skl,bxt,kbl,glk
                 *
                 * Render decompression and plane width > 3840
                 * combined with horizontal panning requires the
                 * plane stride to be a multiple of 4. We'll just
                 * require the entire fb to accommodate that to avoid
                 * potential runtime errors at plane configuration time.
                 */
                else if ((DISPLAY_VER(display) == 9 || display->platform.geminilake) &&
                         color_plane == 0 && fb->width > 3840)
                        tile_width *= 4;
        }
        return tile_width;
}

static int intel_plane_check_stride(const struct intel_plane_state *plane_state)
{
        struct intel_display *display = to_intel_display(plane_state);
        struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane);
        const struct drm_framebuffer *fb = plane_state->hw.fb;
        unsigned int rotation = plane_state->hw.rotation;
        u32 stride, max_stride;

        /*
         * We ignore stride for all invisible planes that
         * can be remapped. Otherwise we could end up
         * with a false positive when the remapping didn't
         * kick in due the plane being invisible.
         */
        if (intel_plane_can_remap(plane_state) &&
            !plane_state->uapi.visible)
                return 0;

        /* FIXME other color planes? */
        stride = plane_state->view.color_plane[0].mapping_stride;
        max_stride = plane->max_stride(plane, fb->format,
                                       fb->modifier, rotation);

        if (stride > max_stride) {
                drm_dbg_kms(display->drm,
                            "[FB:%d] stride (%d) exceeds [PLANE:%d:%s] max stride (%d)\n",
                            fb->base.id, stride,
                            plane->base.base.id, plane->base.name, max_stride);
                return -EINVAL;
        }

        return 0;
}

int intel_plane_compute_gtt(struct intel_plane_state *plane_state)
{
        const struct intel_framebuffer *fb =
                to_intel_framebuffer(plane_state->hw.fb);
        unsigned int rotation = plane_state->hw.rotation;

        if (!fb)
                return 0;

        if (intel_plane_needs_remap(plane_state)) {
                intel_plane_remap_gtt(plane_state);

                /*
                 * Sometimes even remapping can't overcome
                 * the stride limitations :( Can happen with
                 * big plane sizes and suitably misaligned
                 * offsets.
                 */
                return intel_plane_check_stride(plane_state);
        }

        intel_fb_fill_view(fb, rotation, &plane_state->view);

        /* Rotate src coordinates to match rotated GTT view */
        if (drm_rotation_90_or_270(rotation))
                drm_rect_rotate(&plane_state->uapi.src,
                                fb->base.width << 16, fb->base.height << 16,
                                DRM_MODE_ROTATE_270);

        return intel_plane_check_stride(plane_state);
}

static void intel_user_framebuffer_destroy(struct drm_framebuffer *fb)
{
        struct intel_framebuffer *intel_fb = to_intel_framebuffer(fb);

        drm_framebuffer_cleanup(fb);

        if (intel_fb_uses_dpt(fb))
                intel_dpt_destroy(intel_fb->dpt_vm);

        intel_fb_bo_framebuffer_fini(intel_fb_bo(fb));

        intel_frontbuffer_put(intel_fb->frontbuffer);

        kfree(intel_fb->panic);
        kfree(intel_fb);
}

static int intel_user_framebuffer_create_handle(struct drm_framebuffer *fb,
                                                struct drm_file *file,
                                                unsigned int *handle)
{
        struct drm_gem_object *obj = intel_fb_bo(fb);
        struct intel_display *display = to_intel_display(obj->dev);

        if (intel_bo_is_userptr(obj)) {
                drm_dbg(display->drm,
                        "attempting to use a userptr for a framebuffer, denied\n");
                return -EINVAL;
        }

        return drm_gem_handle_create(file, obj, handle);
}

struct frontbuffer_fence_cb {
        struct dma_fence_cb base;
        struct intel_frontbuffer *front;
};

static void intel_user_framebuffer_fence_wake(struct dma_fence *dma,
                                              struct dma_fence_cb *data)
{
        struct frontbuffer_fence_cb *cb = container_of(data, typeof(*cb), base);

        intel_frontbuffer_queue_flush(cb->front);
        kfree(cb);
        dma_fence_put(dma);
}

static int intel_user_framebuffer_dirty(struct drm_framebuffer *fb,
                                        struct drm_file *file,
                                        unsigned int flags, unsigned int color,
                                        struct drm_clip_rect *clips,
                                        unsigned int num_clips)
{
        struct drm_gem_object *obj = intel_fb_bo(fb);
        struct intel_frontbuffer *front = to_intel_frontbuffer(fb);
        struct dma_fence *fence;
        struct frontbuffer_fence_cb *cb;
        int ret = 0;

        if (!atomic_read(&front->bits))
                return 0;

        if (dma_resv_test_signaled(obj->resv, dma_resv_usage_rw(false)))
                goto flush;

        ret = dma_resv_get_singleton(obj->resv, dma_resv_usage_rw(false),
                                     &fence);
        if (ret || !fence)
                goto flush;

        cb = kmalloc_obj(*cb);
        if (!cb) {
                dma_fence_put(fence);
                ret = -ENOMEM;
                goto flush;
        }

        cb->front = front;

        intel_frontbuffer_invalidate(front, ORIGIN_DIRTYFB);

        ret = dma_fence_add_callback(fence, &cb->base,
                                     intel_user_framebuffer_fence_wake);
        if (ret) {
                intel_user_framebuffer_fence_wake(fence, &cb->base);
                if (ret == -ENOENT)
                        ret = 0;
        }

        return ret;

flush:
        intel_frontbuffer_flush(front, ORIGIN_DIRTYFB);
        return ret;
}

static const struct drm_framebuffer_funcs intel_fb_funcs = {
        .destroy = intel_user_framebuffer_destroy,
        .create_handle = intel_user_framebuffer_create_handle,
        .dirty = intel_user_framebuffer_dirty,
};

int intel_framebuffer_init(struct intel_framebuffer *intel_fb,
                           struct drm_gem_object *obj,
                           const struct drm_format_info *info,
                           struct drm_mode_fb_cmd2 *mode_cmd)
{
        struct intel_display *display = to_intel_display(obj->dev);
        struct drm_framebuffer *fb = &intel_fb->base;
        u32 max_stride;
        int ret;
        int i;

        intel_fb->panic = intel_parent_panic_alloc(display);
        if (!intel_fb->panic)
                return -ENOMEM;

        /*
         * intel_frontbuffer_get() must be done before
         * intel_fb_bo_framebuffer_init() to avoid set_tiling vs. addfb race.
         */
        intel_fb->frontbuffer = intel_frontbuffer_get(obj);
        if (!intel_fb->frontbuffer) {
                ret = -ENOMEM;
                goto err_free_panic;
        }

        ret = intel_fb_bo_framebuffer_init(obj, mode_cmd);
        if (ret)
                goto err_frontbuffer_put;

        if (!drm_any_plane_has_format(display->drm,
                                      mode_cmd->pixel_format,
                                      mode_cmd->modifier[0])) {
                drm_dbg_kms(display->drm,
                            "unsupported pixel format %p4cc / modifier 0x%llx\n",
                            &mode_cmd->pixel_format, mode_cmd->modifier[0]);
                ret = -EINVAL;
                goto err_bo_framebuffer_fini;
        }

        max_stride = intel_fb_max_stride(display, info, mode_cmd->modifier[0]);
        if (mode_cmd->pitches[0] > max_stride) {
                drm_dbg_kms(display->drm,
                            "%s pitch (%u) must be at most %d\n",
                            mode_cmd->modifier[0] != DRM_FORMAT_MOD_LINEAR ?
                            "tiled" : "linear",
                            mode_cmd->pitches[0], max_stride);
                ret = -EINVAL;
                goto err_bo_framebuffer_fini;
        }

        /* FIXME need to adjust LINOFF/TILEOFF accordingly. */
        if (mode_cmd->offsets[0] != 0) {
                drm_dbg_kms(display->drm,
                            "plane 0 offset (0x%08x) must be 0\n",
                            mode_cmd->offsets[0]);
                ret = -EINVAL;
                goto err_bo_framebuffer_fini;
        }

        drm_helper_mode_fill_fb_struct(display->drm, fb, info, mode_cmd);

        for (i = 0; i < fb->format->num_planes; i++) {
                unsigned int stride_alignment;

                if (mode_cmd->handles[i] != mode_cmd->handles[0]) {
                        drm_dbg_kms(display->drm, "bad plane %d handle\n", i);
                        ret = -EINVAL;
                        goto err_bo_framebuffer_fini;
                }

                stride_alignment = intel_fb_stride_alignment(fb, i);
                if (fb->pitches[i] & (stride_alignment - 1)) {
                        drm_dbg_kms(display->drm,
                                    "plane %d pitch (%d) must be at least %u byte aligned\n",
                                    i, fb->pitches[i], stride_alignment);
                        ret = -EINVAL;
                        goto err_bo_framebuffer_fini;
                }

                if (intel_fb_is_gen12_ccs_aux_plane(fb, i)) {
                        unsigned int ccs_aux_stride = gen12_ccs_aux_stride(intel_fb, i);

                        if (fb->pitches[i] != ccs_aux_stride) {
                                drm_dbg_kms(display->drm,
                                            "ccs aux plane %d pitch (%d) must be %d\n",
                                            i, fb->pitches[i], ccs_aux_stride);
                                ret = -EINVAL;
                                goto err_bo_framebuffer_fini;
                        }
                }

                fb->obj[i] = obj;
        }

        ret = intel_fill_fb_info(display, intel_fb);
        if (ret)
                goto err_bo_framebuffer_fini;

        if (intel_fb_uses_dpt(fb)) {
                struct i915_address_space *vm;

                vm = intel_dpt_create(intel_fb);
                if (IS_ERR(vm)) {
                        drm_dbg_kms(display->drm, "failed to create DPT\n");
                        ret = PTR_ERR(vm);
                        goto err_frontbuffer_put;
                }

                intel_fb->dpt_vm = vm;
        }

        ret = drm_framebuffer_init(display->drm, fb, &intel_fb_funcs);
        if (ret) {
                drm_err(display->drm, "framebuffer init failed %d\n", ret);
                goto err_free_dpt;
        }

        return 0;

err_free_dpt:
        if (intel_fb_uses_dpt(fb))
                intel_dpt_destroy(intel_fb->dpt_vm);
err_bo_framebuffer_fini:
        intel_fb_bo_framebuffer_fini(obj);
err_frontbuffer_put:
        intel_frontbuffer_put(intel_fb->frontbuffer);
err_free_panic:
        kfree(intel_fb->panic);

        return ret;
}

struct drm_framebuffer *
intel_user_framebuffer_create(struct drm_device *dev,
                              struct drm_file *filp,
                              const struct drm_format_info *info,
                              const struct drm_mode_fb_cmd2 *user_mode_cmd)
{
        struct drm_framebuffer *fb;
        struct drm_gem_object *obj;
        struct drm_mode_fb_cmd2 mode_cmd = *user_mode_cmd;

        obj = intel_fb_bo_lookup_valid_bo(dev, filp, &mode_cmd);
        if (IS_ERR(obj))
                return ERR_CAST(obj);

        fb = intel_framebuffer_create(obj, info, &mode_cmd);
        drm_gem_object_put(obj);

        return fb;
}

struct intel_framebuffer *intel_framebuffer_alloc(void)
{
        struct intel_framebuffer *intel_fb;

        intel_fb = kzalloc_obj(*intel_fb);
        if (!intel_fb)
                return NULL;

        return intel_fb;
}

struct drm_framebuffer *
intel_framebuffer_create(struct drm_gem_object *obj,
                         const struct drm_format_info *info,
                         struct drm_mode_fb_cmd2 *mode_cmd)
{
        struct intel_framebuffer *intel_fb;
        int ret;

        intel_fb = intel_framebuffer_alloc();
        if (!intel_fb)
                return ERR_PTR(-ENOMEM);

        ret = intel_framebuffer_init(intel_fb, obj, info, mode_cmd);
        if (ret)
                goto err;

        return &intel_fb->base;

err:
        kfree(intel_fb);
        return ERR_PTR(ret);
}

struct drm_gem_object *intel_fb_bo(const struct drm_framebuffer *fb)
{
        return fb ? fb->obj[0] : NULL;
}