Contributors: 14
Author Tokens Token Proportion Commits Commit Proportion
Ville Syrjälä 1056 82.37% 32 44.44%
Jani Nikula 112 8.74% 19 26.39%
Dave Airlie 30 2.34% 4 5.56%
Stanislav Lisovskiy 20 1.56% 2 2.78%
Vinod Govindapillai 16 1.25% 1 1.39%
Maarten Lankhorst 13 1.01% 3 4.17%
Gustavo Sousa 8 0.62% 2 2.78%
Azhar Shaikh 6 0.47% 1 1.39%
Matt Roper 6 0.47% 2 2.78%
Chris Wilson 5 0.39% 1 1.39%
Jesse Barnes 4 0.31% 1 1.39%
Imre Deak 3 0.23% 2 2.78%
Pankaj Bharadiya 2 0.16% 1 1.39%
Paulo Zanoni 1 0.08% 1 1.39%
Total 1282 72


// SPDX-License-Identifier: MIT
/*
 * Copyright © 2025 Intel Corporation
 */

#include <drm/drm_print.h>

#include "intel_dbuf_bw.h"
#include "intel_display_core.h"
#include "intel_display_types.h"
#include "skl_watermark.h"

struct intel_dbuf_bw {
	unsigned int max_bw[I915_MAX_DBUF_SLICES];
	u8 active_planes[I915_MAX_DBUF_SLICES];
};

struct intel_dbuf_bw_state {
	struct intel_global_state base;
	struct intel_dbuf_bw dbuf_bw[I915_MAX_PIPES];
};

struct intel_dbuf_bw_state *to_intel_dbuf_bw_state(struct intel_global_state *obj_state)
{
	return container_of(obj_state, struct intel_dbuf_bw_state, base);
}

struct intel_dbuf_bw_state *
intel_atomic_get_old_dbuf_bw_state(struct intel_atomic_state *state)
{
	struct intel_display *display = to_intel_display(state);
	struct intel_global_state *dbuf_bw_state;

	dbuf_bw_state = intel_atomic_get_old_global_obj_state(state, &display->dbuf_bw.obj);

	return to_intel_dbuf_bw_state(dbuf_bw_state);
}

struct intel_dbuf_bw_state *
intel_atomic_get_new_dbuf_bw_state(struct intel_atomic_state *state)
{
	struct intel_display *display = to_intel_display(state);
	struct intel_global_state *dbuf_bw_state;

	dbuf_bw_state = intel_atomic_get_new_global_obj_state(state, &display->dbuf_bw.obj);

	return to_intel_dbuf_bw_state(dbuf_bw_state);
}

struct intel_dbuf_bw_state *
intel_atomic_get_dbuf_bw_state(struct intel_atomic_state *state)
{
	struct intel_display *display = to_intel_display(state);
	struct intel_global_state *dbuf_bw_state;

	dbuf_bw_state = intel_atomic_get_global_obj_state(state, &display->dbuf_bw.obj);
	if (IS_ERR(dbuf_bw_state))
		return ERR_CAST(dbuf_bw_state);

	return to_intel_dbuf_bw_state(dbuf_bw_state);
}

static bool intel_dbuf_bw_changed(struct intel_display *display,
				  const struct intel_dbuf_bw *old_dbuf_bw,
				  const struct intel_dbuf_bw *new_dbuf_bw)
{
	enum dbuf_slice slice;

	for_each_dbuf_slice(display, slice) {
		if (old_dbuf_bw->max_bw[slice] != new_dbuf_bw->max_bw[slice] ||
		    old_dbuf_bw->active_planes[slice] != new_dbuf_bw->active_planes[slice])
			return true;
	}

	return false;
}

static bool intel_dbuf_bw_state_changed(struct intel_display *display,
					const struct intel_dbuf_bw_state *old_dbuf_bw_state,
					const struct intel_dbuf_bw_state *new_dbuf_bw_state)
{
	enum pipe pipe;

	for_each_pipe(display, pipe) {
		const struct intel_dbuf_bw *old_dbuf_bw =
			&old_dbuf_bw_state->dbuf_bw[pipe];
		const struct intel_dbuf_bw *new_dbuf_bw =
			&new_dbuf_bw_state->dbuf_bw[pipe];

		if (intel_dbuf_bw_changed(display, old_dbuf_bw, new_dbuf_bw))
			return true;
	}

	return false;
}

static void skl_plane_calc_dbuf_bw(struct intel_dbuf_bw *dbuf_bw,
				   struct intel_crtc *crtc,
				   enum plane_id plane_id,
				   const struct skl_ddb_entry *ddb,
				   unsigned int data_rate)
{
	struct intel_display *display = to_intel_display(crtc);
	unsigned int dbuf_mask = skl_ddb_dbuf_slice_mask(display, ddb);
	enum dbuf_slice slice;

	/*
	 * The arbiter can only really guarantee an
	 * equal share of the total bw to each plane.
	 */
	for_each_dbuf_slice_in_mask(display, slice, dbuf_mask) {
		dbuf_bw->max_bw[slice] = max(dbuf_bw->max_bw[slice], data_rate);
		dbuf_bw->active_planes[slice] |= BIT(plane_id);
	}
}

static void skl_crtc_calc_dbuf_bw(struct intel_dbuf_bw *dbuf_bw,
				  const struct intel_crtc_state *crtc_state)
{
	struct intel_display *display = to_intel_display(crtc_state);
	struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
	enum plane_id plane_id;

	memset(dbuf_bw, 0, sizeof(*dbuf_bw));

	if (!crtc_state->hw.active)
		return;

	for_each_plane_id_on_crtc(crtc, plane_id) {
		/*
		 * We assume cursors are small enough
		 * to not cause bandwidth problems.
		 */
		if (plane_id == PLANE_CURSOR)
			continue;

		skl_plane_calc_dbuf_bw(dbuf_bw, crtc, plane_id,
				       &crtc_state->wm.skl.plane_ddb[plane_id],
				       crtc_state->data_rate[plane_id]);

		if (DISPLAY_VER(display) < 11)
			skl_plane_calc_dbuf_bw(dbuf_bw, crtc, plane_id,
					       &crtc_state->wm.skl.plane_ddb_y[plane_id],
					       crtc_state->data_rate[plane_id]);
	}
}

/* "Maximum Data Buffer Bandwidth" */
int intel_dbuf_bw_min_cdclk(struct intel_display *display,
			    const struct intel_dbuf_bw_state *dbuf_bw_state)
{
	unsigned int total_max_bw = 0;
	enum dbuf_slice slice;

	for_each_dbuf_slice(display, slice) {
		int num_active_planes = 0;
		unsigned int max_bw = 0;
		enum pipe pipe;

		/*
		 * The arbiter can only really guarantee an
		 * equal share of the total bw to each plane.
		 */
		for_each_pipe(display, pipe) {
			const struct intel_dbuf_bw *dbuf_bw = &dbuf_bw_state->dbuf_bw[pipe];

			max_bw = max(dbuf_bw->max_bw[slice], max_bw);
			num_active_planes += hweight8(dbuf_bw->active_planes[slice]);
		}
		max_bw *= num_active_planes;

		total_max_bw = max(total_max_bw, max_bw);
	}

	return DIV_ROUND_UP(total_max_bw, 64);
}

int intel_dbuf_bw_calc_min_cdclk(struct intel_atomic_state *state,
				 bool *need_cdclk_calc)
{
	struct intel_display *display = to_intel_display(state);
	struct intel_dbuf_bw_state *new_dbuf_bw_state = NULL;
	const struct intel_dbuf_bw_state *old_dbuf_bw_state = NULL;
	const struct intel_crtc_state *old_crtc_state;
	const struct intel_crtc_state *new_crtc_state;
	struct intel_crtc *crtc;
	int ret, i;

	if (DISPLAY_VER(display) < 9)
		return 0;

	for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state,
					    new_crtc_state, i) {
		struct intel_dbuf_bw old_dbuf_bw, new_dbuf_bw;

		skl_crtc_calc_dbuf_bw(&old_dbuf_bw, old_crtc_state);
		skl_crtc_calc_dbuf_bw(&new_dbuf_bw, new_crtc_state);

		if (!intel_dbuf_bw_changed(display, &old_dbuf_bw, &new_dbuf_bw))
			continue;

		new_dbuf_bw_state = intel_atomic_get_dbuf_bw_state(state);
		if (IS_ERR(new_dbuf_bw_state))
			return PTR_ERR(new_dbuf_bw_state);

		old_dbuf_bw_state = intel_atomic_get_old_dbuf_bw_state(state);

		new_dbuf_bw_state->dbuf_bw[crtc->pipe] = new_dbuf_bw;
	}

	if (!old_dbuf_bw_state)
		return 0;

	if (intel_dbuf_bw_state_changed(display, old_dbuf_bw_state, new_dbuf_bw_state)) {
		ret = intel_atomic_lock_global_state(&new_dbuf_bw_state->base);
		if (ret)
			return ret;
	}

	ret = intel_cdclk_update_dbuf_bw_min_cdclk(state,
						   intel_dbuf_bw_min_cdclk(display, old_dbuf_bw_state),
						   intel_dbuf_bw_min_cdclk(display, new_dbuf_bw_state),
						   need_cdclk_calc);
	if (ret)
		return ret;

	return 0;
}

void intel_dbuf_bw_update_hw_state(struct intel_display *display)
{
	struct intel_dbuf_bw_state *dbuf_bw_state =
		to_intel_dbuf_bw_state(display->dbuf_bw.obj.state);
	struct intel_crtc *crtc;

	if (DISPLAY_VER(display) < 9)
		return;

	for_each_intel_crtc(display->drm, crtc) {
		const struct intel_crtc_state *crtc_state =
			to_intel_crtc_state(crtc->base.state);

		skl_crtc_calc_dbuf_bw(&dbuf_bw_state->dbuf_bw[crtc->pipe], crtc_state);
	}
}

void intel_dbuf_bw_crtc_disable_noatomic(struct intel_crtc *crtc)
{
	struct intel_display *display = to_intel_display(crtc);
	struct intel_dbuf_bw_state *dbuf_bw_state =
		to_intel_dbuf_bw_state(display->dbuf_bw.obj.state);
	enum pipe pipe = crtc->pipe;

	if (DISPLAY_VER(display) < 9)
		return;

	memset(&dbuf_bw_state->dbuf_bw[pipe], 0, sizeof(dbuf_bw_state->dbuf_bw[pipe]));
}

static struct intel_global_state *
intel_dbuf_bw_duplicate_state(struct intel_global_obj *obj)
{
	struct intel_dbuf_bw_state *state;

	state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL);
	if (!state)
		return NULL;

	return &state->base;
}

static void intel_dbuf_bw_destroy_state(struct intel_global_obj *obj,
					struct intel_global_state *state)
{
	kfree(state);
}

static const struct intel_global_state_funcs intel_dbuf_bw_funcs = {
	.atomic_duplicate_state = intel_dbuf_bw_duplicate_state,
	.atomic_destroy_state = intel_dbuf_bw_destroy_state,
};

int intel_dbuf_bw_init(struct intel_display *display)
{
	struct intel_dbuf_bw_state *state;

	state = kzalloc(sizeof(*state), GFP_KERNEL);
	if (!state)
		return -ENOMEM;

	intel_atomic_global_obj_init(display, &display->dbuf_bw.obj,
				     &state->base, &intel_dbuf_bw_funcs);

	return 0;
}