162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
462306a36Sopenharmony_ci * Author: Liviu Dudau <Liviu.Dudau@arm.com>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * ARM Mali DP500/DP550/DP650 driver (crtc operations)
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/clk.h>
1062306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <video/videomode.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <drm/drm_atomic.h>
1562306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
1662306a36Sopenharmony_ci#include <drm/drm_crtc.h>
1762306a36Sopenharmony_ci#include <drm/drm_framebuffer.h>
1862306a36Sopenharmony_ci#include <drm/drm_print.h>
1962306a36Sopenharmony_ci#include <drm/drm_probe_helper.h>
2062306a36Sopenharmony_ci#include <drm/drm_vblank.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include "malidp_drv.h"
2362306a36Sopenharmony_ci#include "malidp_hw.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic enum drm_mode_status malidp_crtc_mode_valid(struct drm_crtc *crtc,
2662306a36Sopenharmony_ci						   const struct drm_display_mode *mode)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct malidp_drm *malidp = crtc_to_malidp_device(crtc);
2962306a36Sopenharmony_ci	struct malidp_hw_device *hwdev = malidp->dev;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	/*
3262306a36Sopenharmony_ci	 * check that the hardware can drive the required clock rate,
3362306a36Sopenharmony_ci	 * but skip the check if the clock is meant to be disabled (req_rate = 0)
3462306a36Sopenharmony_ci	 */
3562306a36Sopenharmony_ci	long rate, req_rate = mode->crtc_clock * 1000;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (req_rate) {
3862306a36Sopenharmony_ci		rate = clk_round_rate(hwdev->pxlclk, req_rate);
3962306a36Sopenharmony_ci		if (rate != req_rate) {
4062306a36Sopenharmony_ci			DRM_DEBUG_DRIVER("pxlclk doesn't support %ld Hz\n",
4162306a36Sopenharmony_ci					 req_rate);
4262306a36Sopenharmony_ci			return MODE_NOCLOCK;
4362306a36Sopenharmony_ci		}
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	return MODE_OK;
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic void malidp_crtc_atomic_enable(struct drm_crtc *crtc,
5062306a36Sopenharmony_ci				      struct drm_atomic_state *state)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct malidp_drm *malidp = crtc_to_malidp_device(crtc);
5362306a36Sopenharmony_ci	struct malidp_hw_device *hwdev = malidp->dev;
5462306a36Sopenharmony_ci	struct videomode vm;
5562306a36Sopenharmony_ci	int err = pm_runtime_get_sync(crtc->dev->dev);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (err < 0) {
5862306a36Sopenharmony_ci		DRM_DEBUG_DRIVER("Failed to enable runtime power management: %d\n", err);
5962306a36Sopenharmony_ci		return;
6062306a36Sopenharmony_ci	}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	drm_display_mode_to_videomode(&crtc->state->adjusted_mode, &vm);
6362306a36Sopenharmony_ci	clk_prepare_enable(hwdev->pxlclk);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	/* We rely on firmware to set mclk to a sensible level. */
6662306a36Sopenharmony_ci	clk_set_rate(hwdev->pxlclk, crtc->state->adjusted_mode.crtc_clock * 1000);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	hwdev->hw->modeset(hwdev, &vm);
6962306a36Sopenharmony_ci	hwdev->hw->leave_config_mode(hwdev);
7062306a36Sopenharmony_ci	drm_crtc_vblank_on(crtc);
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic void malidp_crtc_atomic_disable(struct drm_crtc *crtc,
7462306a36Sopenharmony_ci				       struct drm_atomic_state *state)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
7762306a36Sopenharmony_ci									 crtc);
7862306a36Sopenharmony_ci	struct malidp_drm *malidp = crtc_to_malidp_device(crtc);
7962306a36Sopenharmony_ci	struct malidp_hw_device *hwdev = malidp->dev;
8062306a36Sopenharmony_ci	int err;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	/* always disable planes on the CRTC that is being turned off */
8362306a36Sopenharmony_ci	drm_atomic_helper_disable_planes_on_crtc(old_state, false);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	drm_crtc_vblank_off(crtc);
8662306a36Sopenharmony_ci	hwdev->hw->enter_config_mode(hwdev);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	clk_disable_unprepare(hwdev->pxlclk);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	err = pm_runtime_put(crtc->dev->dev);
9162306a36Sopenharmony_ci	if (err < 0) {
9262306a36Sopenharmony_ci		DRM_DEBUG_DRIVER("Failed to disable runtime power management: %d\n", err);
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic const struct gamma_curve_segment {
9762306a36Sopenharmony_ci	u16 start;
9862306a36Sopenharmony_ci	u16 end;
9962306a36Sopenharmony_ci} segments[MALIDP_COEFFTAB_NUM_COEFFS] = {
10062306a36Sopenharmony_ci	/* sector 0 */
10162306a36Sopenharmony_ci	{    0,    0 }, {    1,    1 }, {    2,    2 }, {    3,    3 },
10262306a36Sopenharmony_ci	{    4,    4 }, {    5,    5 }, {    6,    6 }, {    7,    7 },
10362306a36Sopenharmony_ci	{    8,    8 }, {    9,    9 }, {   10,   10 }, {   11,   11 },
10462306a36Sopenharmony_ci	{   12,   12 }, {   13,   13 }, {   14,   14 }, {   15,   15 },
10562306a36Sopenharmony_ci	/* sector 1 */
10662306a36Sopenharmony_ci	{   16,   19 }, {   20,   23 }, {   24,   27 }, {   28,   31 },
10762306a36Sopenharmony_ci	/* sector 2 */
10862306a36Sopenharmony_ci	{   32,   39 }, {   40,   47 }, {   48,   55 }, {   56,   63 },
10962306a36Sopenharmony_ci	/* sector 3 */
11062306a36Sopenharmony_ci	{   64,   79 }, {   80,   95 }, {   96,  111 }, {  112,  127 },
11162306a36Sopenharmony_ci	/* sector 4 */
11262306a36Sopenharmony_ci	{  128,  159 }, {  160,  191 }, {  192,  223 }, {  224,  255 },
11362306a36Sopenharmony_ci	/* sector 5 */
11462306a36Sopenharmony_ci	{  256,  319 }, {  320,  383 }, {  384,  447 }, {  448,  511 },
11562306a36Sopenharmony_ci	/* sector 6 */
11662306a36Sopenharmony_ci	{  512,  639 }, {  640,  767 }, {  768,  895 }, {  896, 1023 },
11762306a36Sopenharmony_ci	{ 1024, 1151 }, { 1152, 1279 }, { 1280, 1407 }, { 1408, 1535 },
11862306a36Sopenharmony_ci	{ 1536, 1663 }, { 1664, 1791 }, { 1792, 1919 }, { 1920, 2047 },
11962306a36Sopenharmony_ci	{ 2048, 2175 }, { 2176, 2303 }, { 2304, 2431 }, { 2432, 2559 },
12062306a36Sopenharmony_ci	{ 2560, 2687 }, { 2688, 2815 }, { 2816, 2943 }, { 2944, 3071 },
12162306a36Sopenharmony_ci	{ 3072, 3199 }, { 3200, 3327 }, { 3328, 3455 }, { 3456, 3583 },
12262306a36Sopenharmony_ci	{ 3584, 3711 }, { 3712, 3839 }, { 3840, 3967 }, { 3968, 4095 },
12362306a36Sopenharmony_ci};
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci#define DE_COEFTAB_DATA(a, b) ((((a) & 0xfff) << 16) | (((b) & 0xfff)))
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic void malidp_generate_gamma_table(struct drm_property_blob *lut_blob,
12862306a36Sopenharmony_ci					u32 coeffs[MALIDP_COEFFTAB_NUM_COEFFS])
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	struct drm_color_lut *lut = (struct drm_color_lut *)lut_blob->data;
13162306a36Sopenharmony_ci	int i;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	for (i = 0; i < MALIDP_COEFFTAB_NUM_COEFFS; ++i) {
13462306a36Sopenharmony_ci		u32 a, b, delta_in, out_start, out_end;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci		delta_in = segments[i].end - segments[i].start;
13762306a36Sopenharmony_ci		/* DP has 12-bit internal precision for its LUTs. */
13862306a36Sopenharmony_ci		out_start = drm_color_lut_extract(lut[segments[i].start].green,
13962306a36Sopenharmony_ci						  12);
14062306a36Sopenharmony_ci		out_end = drm_color_lut_extract(lut[segments[i].end].green, 12);
14162306a36Sopenharmony_ci		a = (delta_in == 0) ? 0 : ((out_end - out_start) * 256) / delta_in;
14262306a36Sopenharmony_ci		b = out_start;
14362306a36Sopenharmony_ci		coeffs[i] = DE_COEFTAB_DATA(a, b);
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci/*
14862306a36Sopenharmony_ci * Check if there is a new gamma LUT and if it is of an acceptable size. Also,
14962306a36Sopenharmony_ci * reject any LUTs that use distinct red, green, and blue curves.
15062306a36Sopenharmony_ci */
15162306a36Sopenharmony_cistatic int malidp_crtc_atomic_check_gamma(struct drm_crtc *crtc,
15262306a36Sopenharmony_ci					  struct drm_crtc_state *state)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct malidp_crtc_state *mc = to_malidp_crtc_state(state);
15562306a36Sopenharmony_ci	struct drm_color_lut *lut;
15662306a36Sopenharmony_ci	size_t lut_size;
15762306a36Sopenharmony_ci	int i;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (!state->color_mgmt_changed || !state->gamma_lut)
16062306a36Sopenharmony_ci		return 0;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if (crtc->state->gamma_lut &&
16362306a36Sopenharmony_ci	    (crtc->state->gamma_lut->base.id == state->gamma_lut->base.id))
16462306a36Sopenharmony_ci		return 0;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (state->gamma_lut->length % sizeof(struct drm_color_lut))
16762306a36Sopenharmony_ci		return -EINVAL;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	lut_size = state->gamma_lut->length / sizeof(struct drm_color_lut);
17062306a36Sopenharmony_ci	if (lut_size != MALIDP_GAMMA_LUT_SIZE)
17162306a36Sopenharmony_ci		return -EINVAL;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	lut = (struct drm_color_lut *)state->gamma_lut->data;
17462306a36Sopenharmony_ci	for (i = 0; i < lut_size; ++i)
17562306a36Sopenharmony_ci		if (!((lut[i].red == lut[i].green) &&
17662306a36Sopenharmony_ci		      (lut[i].red == lut[i].blue)))
17762306a36Sopenharmony_ci			return -EINVAL;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (!state->mode_changed) {
18062306a36Sopenharmony_ci		int ret;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci		state->mode_changed = true;
18362306a36Sopenharmony_ci		/*
18462306a36Sopenharmony_ci		 * Kerneldoc for drm_atomic_helper_check_modeset mandates that
18562306a36Sopenharmony_ci		 * it be invoked when the driver sets ->mode_changed. Since
18662306a36Sopenharmony_ci		 * changing the gamma LUT doesn't depend on any external
18762306a36Sopenharmony_ci		 * resources, it is safe to call it only once.
18862306a36Sopenharmony_ci		 */
18962306a36Sopenharmony_ci		ret = drm_atomic_helper_check_modeset(crtc->dev, state->state);
19062306a36Sopenharmony_ci		if (ret)
19162306a36Sopenharmony_ci			return ret;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	malidp_generate_gamma_table(state->gamma_lut, mc->gamma_coeffs);
19562306a36Sopenharmony_ci	return 0;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci/*
19962306a36Sopenharmony_ci * Check if there is a new CTM and if it contains valid input. Valid here means
20062306a36Sopenharmony_ci * that the number is inside the representable range for a Q3.12 number,
20162306a36Sopenharmony_ci * excluding truncating the fractional part of the input data.
20262306a36Sopenharmony_ci *
20362306a36Sopenharmony_ci * The COLORADJ registers can be changed atomically.
20462306a36Sopenharmony_ci */
20562306a36Sopenharmony_cistatic int malidp_crtc_atomic_check_ctm(struct drm_crtc *crtc,
20662306a36Sopenharmony_ci					struct drm_crtc_state *state)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	struct malidp_crtc_state *mc = to_malidp_crtc_state(state);
20962306a36Sopenharmony_ci	struct drm_color_ctm *ctm;
21062306a36Sopenharmony_ci	int i;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	if (!state->color_mgmt_changed)
21362306a36Sopenharmony_ci		return 0;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	if (!state->ctm)
21662306a36Sopenharmony_ci		return 0;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	if (crtc->state->ctm && (crtc->state->ctm->base.id ==
21962306a36Sopenharmony_ci				 state->ctm->base.id))
22062306a36Sopenharmony_ci		return 0;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	/*
22362306a36Sopenharmony_ci	 * The size of the ctm is checked in
22462306a36Sopenharmony_ci	 * drm_atomic_replace_property_blob_from_id.
22562306a36Sopenharmony_ci	 */
22662306a36Sopenharmony_ci	ctm = (struct drm_color_ctm *)state->ctm->data;
22762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(ctm->matrix); ++i) {
22862306a36Sopenharmony_ci		/* Convert from S31.32 to Q3.12. */
22962306a36Sopenharmony_ci		s64 val = ctm->matrix[i];
23062306a36Sopenharmony_ci		u32 mag = ((((u64)val) & ~BIT_ULL(63)) >> 20) &
23162306a36Sopenharmony_ci			  GENMASK_ULL(14, 0);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci		/*
23462306a36Sopenharmony_ci		 * Convert to 2s complement and check the destination's top bit
23562306a36Sopenharmony_ci		 * for overflow. NB: Can't check before converting or it'd
23662306a36Sopenharmony_ci		 * incorrectly reject the case:
23762306a36Sopenharmony_ci		 * sign == 1
23862306a36Sopenharmony_ci		 * mag == 0x2000
23962306a36Sopenharmony_ci		 */
24062306a36Sopenharmony_ci		if (val & BIT_ULL(63))
24162306a36Sopenharmony_ci			mag = ~mag + 1;
24262306a36Sopenharmony_ci		if (!!(val & BIT_ULL(63)) != !!(mag & BIT(14)))
24362306a36Sopenharmony_ci			return -EINVAL;
24462306a36Sopenharmony_ci		mc->coloradj_coeffs[i] = mag;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return 0;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic int malidp_crtc_atomic_check_scaling(struct drm_crtc *crtc,
25162306a36Sopenharmony_ci					    struct drm_crtc_state *state)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	struct malidp_drm *malidp = crtc_to_malidp_device(crtc);
25462306a36Sopenharmony_ci	struct malidp_hw_device *hwdev = malidp->dev;
25562306a36Sopenharmony_ci	struct malidp_crtc_state *cs = to_malidp_crtc_state(state);
25662306a36Sopenharmony_ci	struct malidp_se_config *s = &cs->scaler_config;
25762306a36Sopenharmony_ci	struct drm_plane *plane;
25862306a36Sopenharmony_ci	struct videomode vm;
25962306a36Sopenharmony_ci	const struct drm_plane_state *pstate;
26062306a36Sopenharmony_ci	u32 h_upscale_factor = 0; /* U16.16 */
26162306a36Sopenharmony_ci	u32 v_upscale_factor = 0; /* U16.16 */
26262306a36Sopenharmony_ci	u8 scaling = cs->scaled_planes_mask;
26362306a36Sopenharmony_ci	int ret;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	if (!scaling) {
26662306a36Sopenharmony_ci		s->scale_enable = false;
26762306a36Sopenharmony_ci		goto mclk_calc;
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	/* The scaling engine can only handle one plane at a time. */
27162306a36Sopenharmony_ci	if (scaling & (scaling - 1))
27262306a36Sopenharmony_ci		return -EINVAL;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, state) {
27562306a36Sopenharmony_ci		struct malidp_plane *mp = to_malidp_plane(plane);
27662306a36Sopenharmony_ci		u32 phase;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci		if (!(mp->layer->id & scaling))
27962306a36Sopenharmony_ci			continue;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci		/*
28262306a36Sopenharmony_ci		 * Convert crtc_[w|h] to U32.32, then divide by U16.16 src_[w|h]
28362306a36Sopenharmony_ci		 * to get the U16.16 result.
28462306a36Sopenharmony_ci		 */
28562306a36Sopenharmony_ci		h_upscale_factor = div_u64((u64)pstate->crtc_w << 32,
28662306a36Sopenharmony_ci					   pstate->src_w);
28762306a36Sopenharmony_ci		v_upscale_factor = div_u64((u64)pstate->crtc_h << 32,
28862306a36Sopenharmony_ci					   pstate->src_h);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci		s->enhancer_enable = ((h_upscale_factor >> 16) >= 2 ||
29162306a36Sopenharmony_ci				      (v_upscale_factor >> 16) >= 2);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci		if (pstate->rotation & MALIDP_ROTATED_MASK) {
29462306a36Sopenharmony_ci			s->input_w = pstate->src_h >> 16;
29562306a36Sopenharmony_ci			s->input_h = pstate->src_w >> 16;
29662306a36Sopenharmony_ci		} else {
29762306a36Sopenharmony_ci			s->input_w = pstate->src_w >> 16;
29862306a36Sopenharmony_ci			s->input_h = pstate->src_h >> 16;
29962306a36Sopenharmony_ci		}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci		s->output_w = pstate->crtc_w;
30262306a36Sopenharmony_ci		s->output_h = pstate->crtc_h;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci#define SE_N_PHASE 4
30562306a36Sopenharmony_ci#define SE_SHIFT_N_PHASE 12
30662306a36Sopenharmony_ci		/* Calculate initial_phase and delta_phase for horizontal. */
30762306a36Sopenharmony_ci		phase = s->input_w;
30862306a36Sopenharmony_ci		s->h_init_phase =
30962306a36Sopenharmony_ci				((phase << SE_N_PHASE) / s->output_w + 1) / 2;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci		phase = s->input_w;
31262306a36Sopenharmony_ci		phase <<= (SE_SHIFT_N_PHASE + SE_N_PHASE);
31362306a36Sopenharmony_ci		s->h_delta_phase = phase / s->output_w;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci		/* Same for vertical. */
31662306a36Sopenharmony_ci		phase = s->input_h;
31762306a36Sopenharmony_ci		s->v_init_phase =
31862306a36Sopenharmony_ci				((phase << SE_N_PHASE) / s->output_h + 1) / 2;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci		phase = s->input_h;
32162306a36Sopenharmony_ci		phase <<= (SE_SHIFT_N_PHASE + SE_N_PHASE);
32262306a36Sopenharmony_ci		s->v_delta_phase = phase / s->output_h;
32362306a36Sopenharmony_ci#undef SE_N_PHASE
32462306a36Sopenharmony_ci#undef SE_SHIFT_N_PHASE
32562306a36Sopenharmony_ci		s->plane_src_id = mp->layer->id;
32662306a36Sopenharmony_ci	}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	s->scale_enable = true;
32962306a36Sopenharmony_ci	s->hcoeff = malidp_se_select_coeffs(h_upscale_factor);
33062306a36Sopenharmony_ci	s->vcoeff = malidp_se_select_coeffs(v_upscale_factor);
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_cimclk_calc:
33362306a36Sopenharmony_ci	drm_display_mode_to_videomode(&state->adjusted_mode, &vm);
33462306a36Sopenharmony_ci	ret = hwdev->hw->se_calc_mclk(hwdev, s, &vm);
33562306a36Sopenharmony_ci	if (ret < 0)
33662306a36Sopenharmony_ci		return -EINVAL;
33762306a36Sopenharmony_ci	return 0;
33862306a36Sopenharmony_ci}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_cistatic int malidp_crtc_atomic_check(struct drm_crtc *crtc,
34162306a36Sopenharmony_ci				    struct drm_atomic_state *state)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
34462306a36Sopenharmony_ci									  crtc);
34562306a36Sopenharmony_ci	struct malidp_drm *malidp = crtc_to_malidp_device(crtc);
34662306a36Sopenharmony_ci	struct malidp_hw_device *hwdev = malidp->dev;
34762306a36Sopenharmony_ci	struct drm_plane *plane;
34862306a36Sopenharmony_ci	const struct drm_plane_state *pstate;
34962306a36Sopenharmony_ci	u32 rot_mem_free, rot_mem_usable;
35062306a36Sopenharmony_ci	int rotated_planes = 0;
35162306a36Sopenharmony_ci	int ret;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	/*
35462306a36Sopenharmony_ci	 * check if there is enough rotation memory available for planes
35562306a36Sopenharmony_ci	 * that need 90° and 270° rotion or planes that are compressed.
35662306a36Sopenharmony_ci	 * Each plane has set its required memory size in the ->plane_check()
35762306a36Sopenharmony_ci	 * callback, here we only make sure that the sums are less that the
35862306a36Sopenharmony_ci	 * total usable memory.
35962306a36Sopenharmony_ci	 *
36062306a36Sopenharmony_ci	 * The rotation memory allocation algorithm (for each plane):
36162306a36Sopenharmony_ci	 *  a. If no more rotated or compressed planes exist, all remaining
36262306a36Sopenharmony_ci	 *     rotate memory in the bank is available for use by the plane.
36362306a36Sopenharmony_ci	 *  b. If other rotated or compressed planes exist, and plane's
36462306a36Sopenharmony_ci	 *     layer ID is DE_VIDEO1, it can use all the memory from first bank
36562306a36Sopenharmony_ci	 *     if secondary rotation memory bank is available, otherwise it can
36662306a36Sopenharmony_ci	 *     use up to half the bank's memory.
36762306a36Sopenharmony_ci	 *  c. If other rotated or compressed planes exist, and plane's layer ID
36862306a36Sopenharmony_ci	 *     is not DE_VIDEO1, it can use half of the available memory.
36962306a36Sopenharmony_ci	 *
37062306a36Sopenharmony_ci	 * Note: this algorithm assumes that the order in which the planes are
37162306a36Sopenharmony_ci	 * checked always has DE_VIDEO1 plane first in the list if it is
37262306a36Sopenharmony_ci	 * rotated. Because that is how we create the planes in the first
37362306a36Sopenharmony_ci	 * place, under current DRM version things work, but if ever the order
37462306a36Sopenharmony_ci	 * in which drm_atomic_crtc_state_for_each_plane() iterates over planes
37562306a36Sopenharmony_ci	 * changes, we need to pre-sort the planes before validation.
37662306a36Sopenharmony_ci	 */
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	/* first count the number of rotated planes */
37962306a36Sopenharmony_ci	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
38062306a36Sopenharmony_ci		struct drm_framebuffer *fb = pstate->fb;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci		if ((pstate->rotation & MALIDP_ROTATED_MASK) || fb->modifier)
38362306a36Sopenharmony_ci			rotated_planes++;
38462306a36Sopenharmony_ci	}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	rot_mem_free = hwdev->rotation_memory[0];
38762306a36Sopenharmony_ci	/*
38862306a36Sopenharmony_ci	 * if we have more than 1 plane using rotation memory, use the second
38962306a36Sopenharmony_ci	 * block of rotation memory as well
39062306a36Sopenharmony_ci	 */
39162306a36Sopenharmony_ci	if (rotated_planes > 1)
39262306a36Sopenharmony_ci		rot_mem_free += hwdev->rotation_memory[1];
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	/* now validate the rotation memory requirements */
39562306a36Sopenharmony_ci	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
39662306a36Sopenharmony_ci		struct malidp_plane *mp = to_malidp_plane(plane);
39762306a36Sopenharmony_ci		struct malidp_plane_state *ms = to_malidp_plane_state(pstate);
39862306a36Sopenharmony_ci		struct drm_framebuffer *fb = pstate->fb;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		if ((pstate->rotation & MALIDP_ROTATED_MASK) || fb->modifier) {
40162306a36Sopenharmony_ci			/* process current plane */
40262306a36Sopenharmony_ci			rotated_planes--;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci			if (!rotated_planes) {
40562306a36Sopenharmony_ci				/* no more rotated planes, we can use what's left */
40662306a36Sopenharmony_ci				rot_mem_usable = rot_mem_free;
40762306a36Sopenharmony_ci			} else {
40862306a36Sopenharmony_ci				if ((mp->layer->id != DE_VIDEO1) ||
40962306a36Sopenharmony_ci				    (hwdev->rotation_memory[1] == 0))
41062306a36Sopenharmony_ci					rot_mem_usable = rot_mem_free / 2;
41162306a36Sopenharmony_ci				else
41262306a36Sopenharmony_ci					rot_mem_usable = hwdev->rotation_memory[0];
41362306a36Sopenharmony_ci			}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci			rot_mem_free -= rot_mem_usable;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci			if (ms->rotmem_size > rot_mem_usable)
41862306a36Sopenharmony_ci				return -EINVAL;
41962306a36Sopenharmony_ci		}
42062306a36Sopenharmony_ci	}
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	/* If only the writeback routing has changed, we don't need a modeset */
42362306a36Sopenharmony_ci	if (crtc_state->connectors_changed) {
42462306a36Sopenharmony_ci		u32 old_mask = crtc->state->connector_mask;
42562306a36Sopenharmony_ci		u32 new_mask = crtc_state->connector_mask;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci		if ((old_mask ^ new_mask) ==
42862306a36Sopenharmony_ci		    (1 << drm_connector_index(&malidp->mw_connector.base)))
42962306a36Sopenharmony_ci			crtc_state->connectors_changed = false;
43062306a36Sopenharmony_ci	}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	ret = malidp_crtc_atomic_check_gamma(crtc, crtc_state);
43362306a36Sopenharmony_ci	ret = ret ? ret : malidp_crtc_atomic_check_ctm(crtc, crtc_state);
43462306a36Sopenharmony_ci	ret = ret ? ret : malidp_crtc_atomic_check_scaling(crtc, crtc_state);
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	return ret;
43762306a36Sopenharmony_ci}
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_cistatic const struct drm_crtc_helper_funcs malidp_crtc_helper_funcs = {
44062306a36Sopenharmony_ci	.mode_valid = malidp_crtc_mode_valid,
44162306a36Sopenharmony_ci	.atomic_check = malidp_crtc_atomic_check,
44262306a36Sopenharmony_ci	.atomic_enable = malidp_crtc_atomic_enable,
44362306a36Sopenharmony_ci	.atomic_disable = malidp_crtc_atomic_disable,
44462306a36Sopenharmony_ci};
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_cistatic struct drm_crtc_state *malidp_crtc_duplicate_state(struct drm_crtc *crtc)
44762306a36Sopenharmony_ci{
44862306a36Sopenharmony_ci	struct malidp_crtc_state *state, *old_state;
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	if (WARN_ON(!crtc->state))
45162306a36Sopenharmony_ci		return NULL;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	old_state = to_malidp_crtc_state(crtc->state);
45462306a36Sopenharmony_ci	state = kmalloc(sizeof(*state), GFP_KERNEL);
45562306a36Sopenharmony_ci	if (!state)
45662306a36Sopenharmony_ci		return NULL;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
45962306a36Sopenharmony_ci	memcpy(state->gamma_coeffs, old_state->gamma_coeffs,
46062306a36Sopenharmony_ci	       sizeof(state->gamma_coeffs));
46162306a36Sopenharmony_ci	memcpy(state->coloradj_coeffs, old_state->coloradj_coeffs,
46262306a36Sopenharmony_ci	       sizeof(state->coloradj_coeffs));
46362306a36Sopenharmony_ci	memcpy(&state->scaler_config, &old_state->scaler_config,
46462306a36Sopenharmony_ci	       sizeof(state->scaler_config));
46562306a36Sopenharmony_ci	state->scaled_planes_mask = 0;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	return &state->base;
46862306a36Sopenharmony_ci}
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_cistatic void malidp_crtc_destroy_state(struct drm_crtc *crtc,
47162306a36Sopenharmony_ci				      struct drm_crtc_state *state)
47262306a36Sopenharmony_ci{
47362306a36Sopenharmony_ci	struct malidp_crtc_state *mali_state = NULL;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	if (state) {
47662306a36Sopenharmony_ci		mali_state = to_malidp_crtc_state(state);
47762306a36Sopenharmony_ci		__drm_atomic_helper_crtc_destroy_state(state);
47862306a36Sopenharmony_ci	}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	kfree(mali_state);
48162306a36Sopenharmony_ci}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_cistatic void malidp_crtc_reset(struct drm_crtc *crtc)
48462306a36Sopenharmony_ci{
48562306a36Sopenharmony_ci	struct malidp_crtc_state *state =
48662306a36Sopenharmony_ci		kzalloc(sizeof(*state), GFP_KERNEL);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	if (crtc->state)
48962306a36Sopenharmony_ci		malidp_crtc_destroy_state(crtc, crtc->state);
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	if (state)
49262306a36Sopenharmony_ci		__drm_atomic_helper_crtc_reset(crtc, &state->base);
49362306a36Sopenharmony_ci	else
49462306a36Sopenharmony_ci		__drm_atomic_helper_crtc_reset(crtc, NULL);
49562306a36Sopenharmony_ci}
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_cistatic int malidp_crtc_enable_vblank(struct drm_crtc *crtc)
49862306a36Sopenharmony_ci{
49962306a36Sopenharmony_ci	struct malidp_drm *malidp = crtc_to_malidp_device(crtc);
50062306a36Sopenharmony_ci	struct malidp_hw_device *hwdev = malidp->dev;
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	malidp_hw_enable_irq(hwdev, MALIDP_DE_BLOCK,
50362306a36Sopenharmony_ci			     hwdev->hw->map.de_irq_map.vsync_irq);
50462306a36Sopenharmony_ci	return 0;
50562306a36Sopenharmony_ci}
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_cistatic void malidp_crtc_disable_vblank(struct drm_crtc *crtc)
50862306a36Sopenharmony_ci{
50962306a36Sopenharmony_ci	struct malidp_drm *malidp = crtc_to_malidp_device(crtc);
51062306a36Sopenharmony_ci	struct malidp_hw_device *hwdev = malidp->dev;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	malidp_hw_disable_irq(hwdev, MALIDP_DE_BLOCK,
51362306a36Sopenharmony_ci			      hwdev->hw->map.de_irq_map.vsync_irq);
51462306a36Sopenharmony_ci}
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cistatic const struct drm_crtc_funcs malidp_crtc_funcs = {
51762306a36Sopenharmony_ci	.set_config = drm_atomic_helper_set_config,
51862306a36Sopenharmony_ci	.page_flip = drm_atomic_helper_page_flip,
51962306a36Sopenharmony_ci	.reset = malidp_crtc_reset,
52062306a36Sopenharmony_ci	.atomic_duplicate_state = malidp_crtc_duplicate_state,
52162306a36Sopenharmony_ci	.atomic_destroy_state = malidp_crtc_destroy_state,
52262306a36Sopenharmony_ci	.enable_vblank = malidp_crtc_enable_vblank,
52362306a36Sopenharmony_ci	.disable_vblank = malidp_crtc_disable_vblank,
52462306a36Sopenharmony_ci};
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ciint malidp_crtc_init(struct drm_device *drm)
52762306a36Sopenharmony_ci{
52862306a36Sopenharmony_ci	struct malidp_drm *malidp = drm_to_malidp(drm);
52962306a36Sopenharmony_ci	struct drm_plane *primary = NULL, *plane;
53062306a36Sopenharmony_ci	int ret;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	ret = malidp_de_planes_init(drm);
53362306a36Sopenharmony_ci	if (ret < 0) {
53462306a36Sopenharmony_ci		DRM_ERROR("Failed to initialise planes\n");
53562306a36Sopenharmony_ci		return ret;
53662306a36Sopenharmony_ci	}
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	drm_for_each_plane(plane, drm) {
53962306a36Sopenharmony_ci		if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
54062306a36Sopenharmony_ci			primary = plane;
54162306a36Sopenharmony_ci			break;
54262306a36Sopenharmony_ci		}
54362306a36Sopenharmony_ci	}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	if (!primary) {
54662306a36Sopenharmony_ci		DRM_ERROR("no primary plane found\n");
54762306a36Sopenharmony_ci		return -EINVAL;
54862306a36Sopenharmony_ci	}
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	ret = drmm_crtc_init_with_planes(drm, &malidp->crtc, primary, NULL,
55162306a36Sopenharmony_ci					 &malidp_crtc_funcs, NULL);
55262306a36Sopenharmony_ci	if (ret)
55362306a36Sopenharmony_ci		return ret;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	drm_crtc_helper_add(&malidp->crtc, &malidp_crtc_helper_funcs);
55662306a36Sopenharmony_ci	drm_mode_crtc_set_gamma_size(&malidp->crtc, MALIDP_GAMMA_LUT_SIZE);
55762306a36Sopenharmony_ci	/* No inverse-gamma: it is per-plane. */
55862306a36Sopenharmony_ci	drm_crtc_enable_color_mgmt(&malidp->crtc, 0, true, MALIDP_GAMMA_LUT_SIZE);
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci	malidp_se_set_enh_coeffs(malidp->dev);
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci	return 0;
56362306a36Sopenharmony_ci}
564