162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2015 Broadcom
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci/**
762306a36Sopenharmony_ci * DOC: VC4 HVS module.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * The Hardware Video Scaler (HVS) is the piece of hardware that does
1062306a36Sopenharmony_ci * translation, scaling, colorspace conversion, and compositing of
1162306a36Sopenharmony_ci * pixels stored in framebuffers into a FIFO of pixels going out to
1262306a36Sopenharmony_ci * the Pixel Valve (CRTC).  It operates at the system clock rate (the
1362306a36Sopenharmony_ci * system audio clock gate, specifically), which is much higher than
1462306a36Sopenharmony_ci * the pixel clock rate.
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * There is a single global HVS, with multiple output FIFOs that can
1762306a36Sopenharmony_ci * be consumed by the PVs.  This file just manages the resources for
1862306a36Sopenharmony_ci * the HVS, while the vc4_crtc.c code actually drives HVS setup for
1962306a36Sopenharmony_ci * each CRTC.
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <linux/bitfield.h>
2362306a36Sopenharmony_ci#include <linux/clk.h>
2462306a36Sopenharmony_ci#include <linux/component.h>
2562306a36Sopenharmony_ci#include <linux/platform_device.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
2862306a36Sopenharmony_ci#include <drm/drm_drv.h>
2962306a36Sopenharmony_ci#include <drm/drm_vblank.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#include <soc/bcm2835/raspberrypi-firmware.h>
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#include "vc4_drv.h"
3462306a36Sopenharmony_ci#include "vc4_regs.h"
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic const struct debugfs_reg32 hvs_regs[] = {
3762306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPCTRL),
3862306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPSTAT),
3962306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPID),
4062306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPECTRL),
4162306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPPROF),
4262306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPDITHER),
4362306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPEOLN),
4462306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPLIST0),
4562306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPLIST1),
4662306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPLIST2),
4762306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPLSTAT),
4862306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPLACT0),
4962306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPLACT1),
5062306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPLACT2),
5162306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPCTRL0),
5262306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPBKGND0),
5362306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPSTAT0),
5462306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPBASE0),
5562306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPCTRL1),
5662306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPBKGND1),
5762306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPSTAT1),
5862306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPBASE1),
5962306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPCTRL2),
6062306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPBKGND2),
6162306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPSTAT2),
6262306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPBASE2),
6362306a36Sopenharmony_ci	VC4_REG32(SCALER_DISPALPHA2),
6462306a36Sopenharmony_ci	VC4_REG32(SCALER_OLEDOFFS),
6562306a36Sopenharmony_ci	VC4_REG32(SCALER_OLEDCOEF0),
6662306a36Sopenharmony_ci	VC4_REG32(SCALER_OLEDCOEF1),
6762306a36Sopenharmony_ci	VC4_REG32(SCALER_OLEDCOEF2),
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_civoid vc4_hvs_dump_state(struct vc4_hvs *hvs)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct drm_device *drm = &hvs->vc4->base;
7362306a36Sopenharmony_ci	struct drm_printer p = drm_info_printer(&hvs->pdev->dev);
7462306a36Sopenharmony_ci	int idx, i;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
7762306a36Sopenharmony_ci		return;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	drm_print_regset32(&p, &hvs->regset);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	DRM_INFO("HVS ctx:\n");
8262306a36Sopenharmony_ci	for (i = 0; i < 64; i += 4) {
8362306a36Sopenharmony_ci		DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n",
8462306a36Sopenharmony_ci			 i * 4, i < HVS_BOOTLOADER_DLIST_END ? "B" : "D",
8562306a36Sopenharmony_ci			 readl((u32 __iomem *)hvs->dlist + i + 0),
8662306a36Sopenharmony_ci			 readl((u32 __iomem *)hvs->dlist + i + 1),
8762306a36Sopenharmony_ci			 readl((u32 __iomem *)hvs->dlist + i + 2),
8862306a36Sopenharmony_ci			 readl((u32 __iomem *)hvs->dlist + i + 3));
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	drm_dev_exit(idx);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct drm_debugfs_entry *entry = m->private;
9762306a36Sopenharmony_ci	struct drm_device *dev = entry->dev;
9862306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
9962306a36Sopenharmony_ci	struct drm_printer p = drm_seq_file_printer(m);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	drm_printf(&p, "%d\n", atomic_read(&vc4->underrun));
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	return 0;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic int vc4_hvs_debugfs_dlist(struct seq_file *m, void *data)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct drm_debugfs_entry *entry = m->private;
10962306a36Sopenharmony_ci	struct drm_device *dev = entry->dev;
11062306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
11162306a36Sopenharmony_ci	struct vc4_hvs *hvs = vc4->hvs;
11262306a36Sopenharmony_ci	struct drm_printer p = drm_seq_file_printer(m);
11362306a36Sopenharmony_ci	unsigned int next_entry_start = 0;
11462306a36Sopenharmony_ci	unsigned int i, j;
11562306a36Sopenharmony_ci	u32 dlist_word, dispstat;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	for (i = 0; i < SCALER_CHANNELS_COUNT; i++) {
11862306a36Sopenharmony_ci		dispstat = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(i)),
11962306a36Sopenharmony_ci					 SCALER_DISPSTATX_MODE);
12062306a36Sopenharmony_ci		if (dispstat == SCALER_DISPSTATX_MODE_DISABLED ||
12162306a36Sopenharmony_ci		    dispstat == SCALER_DISPSTATX_MODE_EOF) {
12262306a36Sopenharmony_ci			drm_printf(&p, "HVS chan %u disabled\n", i);
12362306a36Sopenharmony_ci			continue;
12462306a36Sopenharmony_ci		}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci		drm_printf(&p, "HVS chan %u:\n", i);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci		for (j = HVS_READ(SCALER_DISPLISTX(i)); j < 256; j++) {
12962306a36Sopenharmony_ci			dlist_word = readl((u32 __iomem *)vc4->hvs->dlist + j);
13062306a36Sopenharmony_ci			drm_printf(&p, "dlist: %02d: 0x%08x\n", j,
13162306a36Sopenharmony_ci				   dlist_word);
13262306a36Sopenharmony_ci			if (!next_entry_start ||
13362306a36Sopenharmony_ci			    next_entry_start == j) {
13462306a36Sopenharmony_ci				if (dlist_word & SCALER_CTL0_END)
13562306a36Sopenharmony_ci					break;
13662306a36Sopenharmony_ci				next_entry_start = j +
13762306a36Sopenharmony_ci					VC4_GET_FIELD(dlist_word,
13862306a36Sopenharmony_ci						      SCALER_CTL0_SIZE);
13962306a36Sopenharmony_ci			}
14062306a36Sopenharmony_ci		}
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	return 0;
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci/* The filter kernel is composed of dwords each containing 3 9-bit
14762306a36Sopenharmony_ci * signed integers packed next to each other.
14862306a36Sopenharmony_ci */
14962306a36Sopenharmony_ci#define VC4_INT_TO_COEFF(coeff) (coeff & 0x1ff)
15062306a36Sopenharmony_ci#define VC4_PPF_FILTER_WORD(c0, c1, c2)				\
15162306a36Sopenharmony_ci	((((c0) & 0x1ff) << 0) |				\
15262306a36Sopenharmony_ci	 (((c1) & 0x1ff) << 9) |				\
15362306a36Sopenharmony_ci	 (((c2) & 0x1ff) << 18))
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/* The whole filter kernel is arranged as the coefficients 0-16 going
15662306a36Sopenharmony_ci * up, then a pad, then 17-31 going down and reversed within the
15762306a36Sopenharmony_ci * dwords.  This means that a linear phase kernel (where it's
15862306a36Sopenharmony_ci * symmetrical at the boundary between 15 and 16) has the last 5
15962306a36Sopenharmony_ci * dwords matching the first 5, but reversed.
16062306a36Sopenharmony_ci */
16162306a36Sopenharmony_ci#define VC4_LINEAR_PHASE_KERNEL(c0, c1, c2, c3, c4, c5, c6, c7, c8,	\
16262306a36Sopenharmony_ci				c9, c10, c11, c12, c13, c14, c15)	\
16362306a36Sopenharmony_ci	{VC4_PPF_FILTER_WORD(c0, c1, c2),				\
16462306a36Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c3, c4, c5),				\
16562306a36Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c6, c7, c8),				\
16662306a36Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c9, c10, c11),				\
16762306a36Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c12, c13, c14),				\
16862306a36Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c15, c15, 0)}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci#define VC4_LINEAR_PHASE_KERNEL_DWORDS 6
17162306a36Sopenharmony_ci#define VC4_KERNEL_DWORDS (VC4_LINEAR_PHASE_KERNEL_DWORDS * 2 - 1)
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci/* Recommended B=1/3, C=1/3 filter choice from Mitchell/Netravali.
17462306a36Sopenharmony_ci * http://www.cs.utexas.edu/~fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf
17562306a36Sopenharmony_ci */
17662306a36Sopenharmony_cistatic const u32 mitchell_netravali_1_3_1_3_kernel[] =
17762306a36Sopenharmony_ci	VC4_LINEAR_PHASE_KERNEL(0, -2, -6, -8, -10, -8, -3, 2, 18,
17862306a36Sopenharmony_ci				50, 82, 119, 155, 187, 213, 227);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs,
18162306a36Sopenharmony_ci					struct drm_mm_node *space,
18262306a36Sopenharmony_ci					const u32 *kernel)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	int ret, i;
18562306a36Sopenharmony_ci	u32 __iomem *dst_kernel;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/*
18862306a36Sopenharmony_ci	 * NOTE: We don't need a call to drm_dev_enter()/drm_dev_exit()
18962306a36Sopenharmony_ci	 * here since that function is only called from vc4_hvs_bind().
19062306a36Sopenharmony_ci	 */
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS);
19362306a36Sopenharmony_ci	if (ret) {
19462306a36Sopenharmony_ci		DRM_ERROR("Failed to allocate space for filter kernel: %d\n",
19562306a36Sopenharmony_ci			  ret);
19662306a36Sopenharmony_ci		return ret;
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	dst_kernel = hvs->dlist + space->start;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	for (i = 0; i < VC4_KERNEL_DWORDS; i++) {
20262306a36Sopenharmony_ci		if (i < VC4_LINEAR_PHASE_KERNEL_DWORDS)
20362306a36Sopenharmony_ci			writel(kernel[i], &dst_kernel[i]);
20462306a36Sopenharmony_ci		else {
20562306a36Sopenharmony_ci			writel(kernel[VC4_KERNEL_DWORDS - i - 1],
20662306a36Sopenharmony_ci			       &dst_kernel[i]);
20762306a36Sopenharmony_ci		}
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	return 0;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic void vc4_hvs_lut_load(struct vc4_hvs *hvs,
21462306a36Sopenharmony_ci			     struct vc4_crtc *vc4_crtc)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	struct drm_device *drm = &hvs->vc4->base;
21762306a36Sopenharmony_ci	struct drm_crtc *crtc = &vc4_crtc->base;
21862306a36Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
21962306a36Sopenharmony_ci	int idx;
22062306a36Sopenharmony_ci	u32 i;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
22362306a36Sopenharmony_ci		return;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	/* The LUT memory is laid out with each HVS channel in order,
22662306a36Sopenharmony_ci	 * each of which takes 256 writes for R, 256 for G, then 256
22762306a36Sopenharmony_ci	 * for B.
22862306a36Sopenharmony_ci	 */
22962306a36Sopenharmony_ci	HVS_WRITE(SCALER_GAMADDR,
23062306a36Sopenharmony_ci		  SCALER_GAMADDR_AUTOINC |
23162306a36Sopenharmony_ci		  (vc4_state->assigned_channel * 3 * crtc->gamma_size));
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	for (i = 0; i < crtc->gamma_size; i++)
23462306a36Sopenharmony_ci		HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_r[i]);
23562306a36Sopenharmony_ci	for (i = 0; i < crtc->gamma_size; i++)
23662306a36Sopenharmony_ci		HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_g[i]);
23762306a36Sopenharmony_ci	for (i = 0; i < crtc->gamma_size; i++)
23862306a36Sopenharmony_ci		HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	drm_dev_exit(idx);
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs,
24462306a36Sopenharmony_ci				     struct vc4_crtc *vc4_crtc)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state = vc4_crtc->base.state;
24762306a36Sopenharmony_ci	struct drm_color_lut *lut = crtc_state->gamma_lut->data;
24862306a36Sopenharmony_ci	u32 length = drm_color_lut_size(crtc_state->gamma_lut);
24962306a36Sopenharmony_ci	u32 i;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	for (i = 0; i < length; i++) {
25262306a36Sopenharmony_ci		vc4_crtc->lut_r[i] = drm_color_lut_extract(lut[i].red, 8);
25362306a36Sopenharmony_ci		vc4_crtc->lut_g[i] = drm_color_lut_extract(lut[i].green, 8);
25462306a36Sopenharmony_ci		vc4_crtc->lut_b[i] = drm_color_lut_extract(lut[i].blue, 8);
25562306a36Sopenharmony_ci	}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	vc4_hvs_lut_load(hvs, vc4_crtc);
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ciu8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo)
26162306a36Sopenharmony_ci{
26262306a36Sopenharmony_ci	struct drm_device *drm = &hvs->vc4->base;
26362306a36Sopenharmony_ci	u8 field = 0;
26462306a36Sopenharmony_ci	int idx;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
26762306a36Sopenharmony_ci		return 0;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	switch (fifo) {
27062306a36Sopenharmony_ci	case 0:
27162306a36Sopenharmony_ci		field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1),
27262306a36Sopenharmony_ci				      SCALER_DISPSTAT1_FRCNT0);
27362306a36Sopenharmony_ci		break;
27462306a36Sopenharmony_ci	case 1:
27562306a36Sopenharmony_ci		field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1),
27662306a36Sopenharmony_ci				      SCALER_DISPSTAT1_FRCNT1);
27762306a36Sopenharmony_ci		break;
27862306a36Sopenharmony_ci	case 2:
27962306a36Sopenharmony_ci		field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT2),
28062306a36Sopenharmony_ci				      SCALER_DISPSTAT2_FRCNT2);
28162306a36Sopenharmony_ci		break;
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	drm_dev_exit(idx);
28562306a36Sopenharmony_ci	return field;
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ciint vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	struct vc4_dev *vc4 = hvs->vc4;
29162306a36Sopenharmony_ci	u32 reg;
29262306a36Sopenharmony_ci	int ret;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	if (!vc4->is_vc5)
29562306a36Sopenharmony_ci		return output;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	/*
29862306a36Sopenharmony_ci	 * NOTE: We should probably use drm_dev_enter()/drm_dev_exit()
29962306a36Sopenharmony_ci	 * here, but this function is only used during the DRM device
30062306a36Sopenharmony_ci	 * initialization, so we should be fine.
30162306a36Sopenharmony_ci	 */
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	switch (output) {
30462306a36Sopenharmony_ci	case 0:
30562306a36Sopenharmony_ci		return 0;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	case 1:
30862306a36Sopenharmony_ci		return 1;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	case 2:
31162306a36Sopenharmony_ci		reg = HVS_READ(SCALER_DISPECTRL);
31262306a36Sopenharmony_ci		ret = FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg);
31362306a36Sopenharmony_ci		if (ret == 0)
31462306a36Sopenharmony_ci			return 2;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci		return 0;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	case 3:
31962306a36Sopenharmony_ci		reg = HVS_READ(SCALER_DISPCTRL);
32062306a36Sopenharmony_ci		ret = FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg);
32162306a36Sopenharmony_ci		if (ret == 3)
32262306a36Sopenharmony_ci			return -EPIPE;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci		return ret;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	case 4:
32762306a36Sopenharmony_ci		reg = HVS_READ(SCALER_DISPEOLN);
32862306a36Sopenharmony_ci		ret = FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg);
32962306a36Sopenharmony_ci		if (ret == 3)
33062306a36Sopenharmony_ci			return -EPIPE;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci		return ret;
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	case 5:
33562306a36Sopenharmony_ci		reg = HVS_READ(SCALER_DISPDITHER);
33662306a36Sopenharmony_ci		ret = FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg);
33762306a36Sopenharmony_ci		if (ret == 3)
33862306a36Sopenharmony_ci			return -EPIPE;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci		return ret;
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	default:
34362306a36Sopenharmony_ci		return -EPIPE;
34462306a36Sopenharmony_ci	}
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc,
34862306a36Sopenharmony_ci				struct drm_display_mode *mode, bool oneshot)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	struct vc4_dev *vc4 = hvs->vc4;
35162306a36Sopenharmony_ci	struct drm_device *drm = &vc4->base;
35262306a36Sopenharmony_ci	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
35362306a36Sopenharmony_ci	struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state);
35462306a36Sopenharmony_ci	unsigned int chan = vc4_crtc_state->assigned_channel;
35562306a36Sopenharmony_ci	bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
35662306a36Sopenharmony_ci	u32 dispbkgndx;
35762306a36Sopenharmony_ci	u32 dispctrl;
35862306a36Sopenharmony_ci	int idx;
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
36162306a36Sopenharmony_ci		return -ENODEV;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan), 0);
36462306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET);
36562306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan), 0);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	/* Turn on the scaler, which will wait for vstart to start
36862306a36Sopenharmony_ci	 * compositing.
36962306a36Sopenharmony_ci	 * When feeding the transposer, we should operate in oneshot
37062306a36Sopenharmony_ci	 * mode.
37162306a36Sopenharmony_ci	 */
37262306a36Sopenharmony_ci	dispctrl = SCALER_DISPCTRLX_ENABLE;
37362306a36Sopenharmony_ci	dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(chan));
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	if (!vc4->is_vc5) {
37662306a36Sopenharmony_ci		dispctrl |= VC4_SET_FIELD(mode->hdisplay,
37762306a36Sopenharmony_ci					  SCALER_DISPCTRLX_WIDTH) |
37862306a36Sopenharmony_ci			    VC4_SET_FIELD(mode->vdisplay,
37962306a36Sopenharmony_ci					  SCALER_DISPCTRLX_HEIGHT) |
38062306a36Sopenharmony_ci			    (oneshot ? SCALER_DISPCTRLX_ONESHOT : 0);
38162306a36Sopenharmony_ci		dispbkgndx |= SCALER_DISPBKGND_AUTOHS;
38262306a36Sopenharmony_ci	} else {
38362306a36Sopenharmony_ci		dispctrl |= VC4_SET_FIELD(mode->hdisplay,
38462306a36Sopenharmony_ci					  SCALER5_DISPCTRLX_WIDTH) |
38562306a36Sopenharmony_ci			    VC4_SET_FIELD(mode->vdisplay,
38662306a36Sopenharmony_ci					  SCALER5_DISPCTRLX_HEIGHT) |
38762306a36Sopenharmony_ci			    (oneshot ? SCALER5_DISPCTRLX_ONESHOT : 0);
38862306a36Sopenharmony_ci		dispbkgndx &= ~SCALER5_DISPBKGND_BCK2BCK;
38962306a36Sopenharmony_ci	}
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan), dispctrl);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
39462306a36Sopenharmony_ci	dispbkgndx &= ~SCALER_DISPBKGND_INTERLACE;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPBKGNDX(chan), dispbkgndx |
39762306a36Sopenharmony_ci		  ((!vc4->is_vc5) ? SCALER_DISPBKGND_GAMMA : 0) |
39862306a36Sopenharmony_ci		  (interlace ? SCALER_DISPBKGND_INTERLACE : 0));
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	/* Reload the LUT, since the SRAMs would have been disabled if
40162306a36Sopenharmony_ci	 * all CRTCs had SCALER_DISPBKGND_GAMMA unset at once.
40262306a36Sopenharmony_ci	 */
40362306a36Sopenharmony_ci	vc4_hvs_lut_load(hvs, vc4_crtc);
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	drm_dev_exit(idx);
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	return 0;
40862306a36Sopenharmony_ci}
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_civoid vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	struct drm_device *drm = &hvs->vc4->base;
41362306a36Sopenharmony_ci	int idx;
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
41662306a36Sopenharmony_ci		return;
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE)
41962306a36Sopenharmony_ci		goto out;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan),
42262306a36Sopenharmony_ci		  HVS_READ(SCALER_DISPCTRLX(chan)) | SCALER_DISPCTRLX_RESET);
42362306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan),
42462306a36Sopenharmony_ci		  HVS_READ(SCALER_DISPCTRLX(chan)) & ~SCALER_DISPCTRLX_ENABLE);
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	/* Once we leave, the scaler should be disabled and its fifo empty. */
42762306a36Sopenharmony_ci	WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)),
43062306a36Sopenharmony_ci				   SCALER_DISPSTATX_MODE) !=
43162306a36Sopenharmony_ci		     SCALER_DISPSTATX_MODE_DISABLED);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) &
43462306a36Sopenharmony_ci		      (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) !=
43562306a36Sopenharmony_ci		     SCALER_DISPSTATX_EMPTY);
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ciout:
43862306a36Sopenharmony_ci	drm_dev_exit(idx);
43962306a36Sopenharmony_ci}
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ciint vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
44262306a36Sopenharmony_ci{
44362306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
44462306a36Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
44562306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
44662306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
44762306a36Sopenharmony_ci	struct drm_plane *plane;
44862306a36Sopenharmony_ci	unsigned long flags;
44962306a36Sopenharmony_ci	const struct drm_plane_state *plane_state;
45062306a36Sopenharmony_ci	u32 dlist_count = 0;
45162306a36Sopenharmony_ci	int ret;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	/* The pixelvalve can only feed one encoder (and encoders are
45462306a36Sopenharmony_ci	 * 1:1 with connectors.)
45562306a36Sopenharmony_ci	 */
45662306a36Sopenharmony_ci	if (hweight32(crtc_state->connector_mask) > 1)
45762306a36Sopenharmony_ci		return -EINVAL;
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state)
46062306a36Sopenharmony_ci		dlist_count += vc4_plane_dlist_size(plane_state);
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	dlist_count++; /* Account for SCALER_CTL0_END. */
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	spin_lock_irqsave(&vc4->hvs->mm_lock, flags);
46562306a36Sopenharmony_ci	ret = drm_mm_insert_node(&vc4->hvs->dlist_mm, &vc4_state->mm,
46662306a36Sopenharmony_ci				 dlist_count);
46762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags);
46862306a36Sopenharmony_ci	if (ret)
46962306a36Sopenharmony_ci		return ret;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	return 0;
47262306a36Sopenharmony_ci}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_cistatic void vc4_hvs_install_dlist(struct drm_crtc *crtc)
47562306a36Sopenharmony_ci{
47662306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
47762306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
47862306a36Sopenharmony_ci	struct vc4_hvs *hvs = vc4->hvs;
47962306a36Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
48062306a36Sopenharmony_ci	int idx;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	if (!drm_dev_enter(dev, &idx))
48362306a36Sopenharmony_ci		return;
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel),
48662306a36Sopenharmony_ci		  vc4_state->mm.start);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	drm_dev_exit(idx);
48962306a36Sopenharmony_ci}
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_cistatic void vc4_hvs_update_dlist(struct drm_crtc *crtc)
49262306a36Sopenharmony_ci{
49362306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
49462306a36Sopenharmony_ci	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
49562306a36Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
49662306a36Sopenharmony_ci	unsigned long flags;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	if (crtc->state->event) {
49962306a36Sopenharmony_ci		crtc->state->event->pipe = drm_crtc_index(crtc);
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci		spin_lock_irqsave(&dev->event_lock, flags);
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci		if (!vc4_crtc->feeds_txp || vc4_state->txp_armed) {
50662306a36Sopenharmony_ci			vc4_crtc->event = crtc->state->event;
50762306a36Sopenharmony_ci			crtc->state->event = NULL;
50862306a36Sopenharmony_ci		}
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci		spin_unlock_irqrestore(&dev->event_lock, flags);
51162306a36Sopenharmony_ci	}
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_crtc->irq_lock, flags);
51462306a36Sopenharmony_ci	vc4_crtc->current_dlist = vc4_state->mm.start;
51562306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_crtc->irq_lock, flags);
51662306a36Sopenharmony_ci}
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_civoid vc4_hvs_atomic_begin(struct drm_crtc *crtc,
51962306a36Sopenharmony_ci			  struct drm_atomic_state *state)
52062306a36Sopenharmony_ci{
52162306a36Sopenharmony_ci	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
52262306a36Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
52362306a36Sopenharmony_ci	unsigned long flags;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_crtc->irq_lock, flags);
52662306a36Sopenharmony_ci	vc4_crtc->current_hvs_channel = vc4_state->assigned_channel;
52762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_crtc->irq_lock, flags);
52862306a36Sopenharmony_ci}
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_civoid vc4_hvs_atomic_enable(struct drm_crtc *crtc,
53162306a36Sopenharmony_ci			   struct drm_atomic_state *state)
53262306a36Sopenharmony_ci{
53362306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
53462306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
53562306a36Sopenharmony_ci	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
53662306a36Sopenharmony_ci	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
53762306a36Sopenharmony_ci	bool oneshot = vc4_crtc->feeds_txp;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	vc4_hvs_install_dlist(crtc);
54062306a36Sopenharmony_ci	vc4_hvs_update_dlist(crtc);
54162306a36Sopenharmony_ci	vc4_hvs_init_channel(vc4->hvs, crtc, mode, oneshot);
54262306a36Sopenharmony_ci}
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_civoid vc4_hvs_atomic_disable(struct drm_crtc *crtc,
54562306a36Sopenharmony_ci			    struct drm_atomic_state *state)
54662306a36Sopenharmony_ci{
54762306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
54862306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
54962306a36Sopenharmony_ci	struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, crtc);
55062306a36Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(old_state);
55162306a36Sopenharmony_ci	unsigned int chan = vc4_state->assigned_channel;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	vc4_hvs_stop_channel(vc4->hvs, chan);
55462306a36Sopenharmony_ci}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_civoid vc4_hvs_atomic_flush(struct drm_crtc *crtc,
55762306a36Sopenharmony_ci			  struct drm_atomic_state *state)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
56062306a36Sopenharmony_ci									 crtc);
56162306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
56262306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
56362306a36Sopenharmony_ci	struct vc4_hvs *hvs = vc4->hvs;
56462306a36Sopenharmony_ci	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
56562306a36Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
56662306a36Sopenharmony_ci	unsigned int channel = vc4_state->assigned_channel;
56762306a36Sopenharmony_ci	struct drm_plane *plane;
56862306a36Sopenharmony_ci	struct vc4_plane_state *vc4_plane_state;
56962306a36Sopenharmony_ci	bool debug_dump_regs = false;
57062306a36Sopenharmony_ci	bool enable_bg_fill = false;
57162306a36Sopenharmony_ci	u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start;
57262306a36Sopenharmony_ci	u32 __iomem *dlist_next = dlist_start;
57362306a36Sopenharmony_ci	unsigned int zpos = 0;
57462306a36Sopenharmony_ci	bool found = false;
57562306a36Sopenharmony_ci	int idx;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	if (!drm_dev_enter(dev, &idx)) {
57862306a36Sopenharmony_ci		vc4_crtc_send_vblank(crtc);
57962306a36Sopenharmony_ci		return;
58062306a36Sopenharmony_ci	}
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED)
58362306a36Sopenharmony_ci		return;
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	if (debug_dump_regs) {
58662306a36Sopenharmony_ci		DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc));
58762306a36Sopenharmony_ci		vc4_hvs_dump_state(hvs);
58862306a36Sopenharmony_ci	}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	/* Copy all the active planes' dlist contents to the hardware dlist. */
59162306a36Sopenharmony_ci	do {
59262306a36Sopenharmony_ci		found = false;
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci		drm_atomic_crtc_for_each_plane(plane, crtc) {
59562306a36Sopenharmony_ci			if (plane->state->normalized_zpos != zpos)
59662306a36Sopenharmony_ci				continue;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci			/* Is this the first active plane? */
59962306a36Sopenharmony_ci			if (dlist_next == dlist_start) {
60062306a36Sopenharmony_ci				/* We need to enable background fill when a plane
60162306a36Sopenharmony_ci				 * could be alpha blending from the background, i.e.
60262306a36Sopenharmony_ci				 * where no other plane is underneath. It suffices to
60362306a36Sopenharmony_ci				 * consider the first active plane here since we set
60462306a36Sopenharmony_ci				 * needs_bg_fill such that either the first plane
60562306a36Sopenharmony_ci				 * already needs it or all planes on top blend from
60662306a36Sopenharmony_ci				 * the first or a lower plane.
60762306a36Sopenharmony_ci				 */
60862306a36Sopenharmony_ci				vc4_plane_state = to_vc4_plane_state(plane->state);
60962306a36Sopenharmony_ci				enable_bg_fill = vc4_plane_state->needs_bg_fill;
61062306a36Sopenharmony_ci			}
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci			dlist_next += vc4_plane_write_dlist(plane, dlist_next);
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci			found = true;
61562306a36Sopenharmony_ci		}
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci		zpos++;
61862306a36Sopenharmony_ci	} while (found);
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	writel(SCALER_CTL0_END, dlist_next);
62162306a36Sopenharmony_ci	dlist_next++;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	WARN_ON_ONCE(dlist_next - dlist_start != vc4_state->mm.size);
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	if (enable_bg_fill)
62662306a36Sopenharmony_ci		/* This sets a black background color fill, as is the case
62762306a36Sopenharmony_ci		 * with other DRM drivers.
62862306a36Sopenharmony_ci		 */
62962306a36Sopenharmony_ci		HVS_WRITE(SCALER_DISPBKGNDX(channel),
63062306a36Sopenharmony_ci			  HVS_READ(SCALER_DISPBKGNDX(channel)) |
63162306a36Sopenharmony_ci			  SCALER_DISPBKGND_FILL);
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	/* Only update DISPLIST if the CRTC was already running and is not
63462306a36Sopenharmony_ci	 * being disabled.
63562306a36Sopenharmony_ci	 * vc4_crtc_enable() takes care of updating the dlist just after
63662306a36Sopenharmony_ci	 * re-enabling VBLANK interrupts and before enabling the engine.
63762306a36Sopenharmony_ci	 * If the CRTC is being disabled, there's no point in updating this
63862306a36Sopenharmony_ci	 * information.
63962306a36Sopenharmony_ci	 */
64062306a36Sopenharmony_ci	if (crtc->state->active && old_state->active) {
64162306a36Sopenharmony_ci		vc4_hvs_install_dlist(crtc);
64262306a36Sopenharmony_ci		vc4_hvs_update_dlist(crtc);
64362306a36Sopenharmony_ci	}
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	if (crtc->state->color_mgmt_changed) {
64662306a36Sopenharmony_ci		u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(channel));
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci		if (crtc->state->gamma_lut) {
64962306a36Sopenharmony_ci			vc4_hvs_update_gamma_lut(hvs, vc4_crtc);
65062306a36Sopenharmony_ci			dispbkgndx |= SCALER_DISPBKGND_GAMMA;
65162306a36Sopenharmony_ci		} else {
65262306a36Sopenharmony_ci			/* Unsetting DISPBKGND_GAMMA skips the gamma lut step
65362306a36Sopenharmony_ci			 * in hardware, which is the same as a linear lut that
65462306a36Sopenharmony_ci			 * DRM expects us to use in absence of a user lut.
65562306a36Sopenharmony_ci			 */
65662306a36Sopenharmony_ci			dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
65762306a36Sopenharmony_ci		}
65862306a36Sopenharmony_ci		HVS_WRITE(SCALER_DISPBKGNDX(channel), dispbkgndx);
65962306a36Sopenharmony_ci	}
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	if (debug_dump_regs) {
66262306a36Sopenharmony_ci		DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc));
66362306a36Sopenharmony_ci		vc4_hvs_dump_state(hvs);
66462306a36Sopenharmony_ci	}
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	drm_dev_exit(idx);
66762306a36Sopenharmony_ci}
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_civoid vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel)
67062306a36Sopenharmony_ci{
67162306a36Sopenharmony_ci	struct drm_device *drm = &hvs->vc4->base;
67262306a36Sopenharmony_ci	u32 dispctrl;
67362306a36Sopenharmony_ci	int idx;
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
67662306a36Sopenharmony_ci		return;
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	dispctrl = HVS_READ(SCALER_DISPCTRL);
67962306a36Sopenharmony_ci	dispctrl &= ~(hvs->vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) :
68062306a36Sopenharmony_ci					 SCALER_DISPCTRL_DSPEISLUR(channel));
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRL, dispctrl);
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci	drm_dev_exit(idx);
68562306a36Sopenharmony_ci}
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_civoid vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel)
68862306a36Sopenharmony_ci{
68962306a36Sopenharmony_ci	struct drm_device *drm = &hvs->vc4->base;
69062306a36Sopenharmony_ci	u32 dispctrl;
69162306a36Sopenharmony_ci	int idx;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
69462306a36Sopenharmony_ci		return;
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_ci	dispctrl = HVS_READ(SCALER_DISPCTRL);
69762306a36Sopenharmony_ci	dispctrl |= (hvs->vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) :
69862306a36Sopenharmony_ci					SCALER_DISPCTRL_DSPEISLUR(channel));
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPSTAT,
70162306a36Sopenharmony_ci		  SCALER_DISPSTAT_EUFLOW(channel));
70262306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRL, dispctrl);
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci	drm_dev_exit(idx);
70562306a36Sopenharmony_ci}
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_cistatic void vc4_hvs_report_underrun(struct drm_device *dev)
70862306a36Sopenharmony_ci{
70962306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	atomic_inc(&vc4->underrun);
71262306a36Sopenharmony_ci	DRM_DEV_ERROR(dev->dev, "HVS underrun\n");
71362306a36Sopenharmony_ci}
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_cistatic irqreturn_t vc4_hvs_irq_handler(int irq, void *data)
71662306a36Sopenharmony_ci{
71762306a36Sopenharmony_ci	struct drm_device *dev = data;
71862306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
71962306a36Sopenharmony_ci	struct vc4_hvs *hvs = vc4->hvs;
72062306a36Sopenharmony_ci	irqreturn_t irqret = IRQ_NONE;
72162306a36Sopenharmony_ci	int channel;
72262306a36Sopenharmony_ci	u32 control;
72362306a36Sopenharmony_ci	u32 status;
72462306a36Sopenharmony_ci	u32 dspeislur;
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_ci	/*
72762306a36Sopenharmony_ci	 * NOTE: We don't need to protect the register access using
72862306a36Sopenharmony_ci	 * drm_dev_enter() there because the interrupt handler lifetime
72962306a36Sopenharmony_ci	 * is tied to the device itself, and not to the DRM device.
73062306a36Sopenharmony_ci	 *
73162306a36Sopenharmony_ci	 * So when the device will be gone, one of the first thing we
73262306a36Sopenharmony_ci	 * will be doing will be to unregister the interrupt handler,
73362306a36Sopenharmony_ci	 * and then unregister the DRM device. drm_dev_enter() would
73462306a36Sopenharmony_ci	 * thus always succeed if we are here.
73562306a36Sopenharmony_ci	 */
73662306a36Sopenharmony_ci
73762306a36Sopenharmony_ci	status = HVS_READ(SCALER_DISPSTAT);
73862306a36Sopenharmony_ci	control = HVS_READ(SCALER_DISPCTRL);
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	for (channel = 0; channel < SCALER_CHANNELS_COUNT; channel++) {
74162306a36Sopenharmony_ci		dspeislur = vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) :
74262306a36Sopenharmony_ci					  SCALER_DISPCTRL_DSPEISLUR(channel);
74362306a36Sopenharmony_ci		/* Interrupt masking is not always honored, so check it here. */
74462306a36Sopenharmony_ci		if (status & SCALER_DISPSTAT_EUFLOW(channel) &&
74562306a36Sopenharmony_ci		    control & dspeislur) {
74662306a36Sopenharmony_ci			vc4_hvs_mask_underrun(hvs, channel);
74762306a36Sopenharmony_ci			vc4_hvs_report_underrun(dev);
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_ci			irqret = IRQ_HANDLED;
75062306a36Sopenharmony_ci		}
75162306a36Sopenharmony_ci	}
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	/* Clear every per-channel interrupt flag. */
75462306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_IRQMASK(0) |
75562306a36Sopenharmony_ci				   SCALER_DISPSTAT_IRQMASK(1) |
75662306a36Sopenharmony_ci				   SCALER_DISPSTAT_IRQMASK(2));
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	return irqret;
75962306a36Sopenharmony_ci}
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ciint vc4_hvs_debugfs_init(struct drm_minor *minor)
76262306a36Sopenharmony_ci{
76362306a36Sopenharmony_ci	struct drm_device *drm = minor->dev;
76462306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(drm);
76562306a36Sopenharmony_ci	struct vc4_hvs *hvs = vc4->hvs;
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci	if (!vc4->hvs)
76862306a36Sopenharmony_ci		return -ENODEV;
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci	if (!vc4->is_vc5)
77162306a36Sopenharmony_ci		debugfs_create_bool("hvs_load_tracker", S_IRUGO | S_IWUSR,
77262306a36Sopenharmony_ci				    minor->debugfs_root,
77362306a36Sopenharmony_ci				    &vc4->load_tracker_enabled);
77462306a36Sopenharmony_ci
77562306a36Sopenharmony_ci	drm_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist, NULL);
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	drm_debugfs_add_file(drm, "hvs_underrun", vc4_hvs_debugfs_underrun, NULL);
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	vc4_debugfs_add_regset32(drm, "hvs_regs", &hvs->regset);
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	return 0;
78262306a36Sopenharmony_ci}
78362306a36Sopenharmony_ci
78462306a36Sopenharmony_cistruct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4, struct platform_device *pdev)
78562306a36Sopenharmony_ci{
78662306a36Sopenharmony_ci	struct drm_device *drm = &vc4->base;
78762306a36Sopenharmony_ci	struct vc4_hvs *hvs;
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	hvs = drmm_kzalloc(drm, sizeof(*hvs), GFP_KERNEL);
79062306a36Sopenharmony_ci	if (!hvs)
79162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	hvs->vc4 = vc4;
79462306a36Sopenharmony_ci	hvs->pdev = pdev;
79562306a36Sopenharmony_ci
79662306a36Sopenharmony_ci	spin_lock_init(&hvs->mm_lock);
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	/* Set up the HVS display list memory manager.  We never
79962306a36Sopenharmony_ci	 * overwrite the setup from the bootloader (just 128b out of
80062306a36Sopenharmony_ci	 * our 16K), since we don't want to scramble the screen when
80162306a36Sopenharmony_ci	 * transitioning from the firmware's boot setup to runtime.
80262306a36Sopenharmony_ci	 */
80362306a36Sopenharmony_ci	drm_mm_init(&hvs->dlist_mm,
80462306a36Sopenharmony_ci		    HVS_BOOTLOADER_DLIST_END,
80562306a36Sopenharmony_ci		    (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END);
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_ci	/* Set up the HVS LBM memory manager.  We could have some more
80862306a36Sopenharmony_ci	 * complicated data structure that allowed reuse of LBM areas
80962306a36Sopenharmony_ci	 * between planes when they don't overlap on the screen, but
81062306a36Sopenharmony_ci	 * for now we just allocate globally.
81162306a36Sopenharmony_ci	 */
81262306a36Sopenharmony_ci	if (!vc4->is_vc5)
81362306a36Sopenharmony_ci		/* 48k words of 2x12-bit pixels */
81462306a36Sopenharmony_ci		drm_mm_init(&hvs->lbm_mm, 0, 48 * 1024);
81562306a36Sopenharmony_ci	else
81662306a36Sopenharmony_ci		/* 60k words of 4x12-bit pixels */
81762306a36Sopenharmony_ci		drm_mm_init(&hvs->lbm_mm, 0, 60 * 1024);
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_ci	vc4->hvs = hvs;
82062306a36Sopenharmony_ci
82162306a36Sopenharmony_ci	return hvs;
82262306a36Sopenharmony_ci}
82362306a36Sopenharmony_ci
82462306a36Sopenharmony_cistatic int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
82562306a36Sopenharmony_ci{
82662306a36Sopenharmony_ci	struct platform_device *pdev = to_platform_device(dev);
82762306a36Sopenharmony_ci	struct drm_device *drm = dev_get_drvdata(master);
82862306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(drm);
82962306a36Sopenharmony_ci	struct vc4_hvs *hvs = NULL;
83062306a36Sopenharmony_ci	int ret;
83162306a36Sopenharmony_ci	u32 dispctrl;
83262306a36Sopenharmony_ci	u32 reg, top;
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci	hvs = __vc4_hvs_alloc(vc4, NULL);
83562306a36Sopenharmony_ci	if (IS_ERR(hvs))
83662306a36Sopenharmony_ci		return PTR_ERR(hvs);
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci	hvs->regs = vc4_ioremap_regs(pdev, 0);
83962306a36Sopenharmony_ci	if (IS_ERR(hvs->regs))
84062306a36Sopenharmony_ci		return PTR_ERR(hvs->regs);
84162306a36Sopenharmony_ci
84262306a36Sopenharmony_ci	hvs->regset.base = hvs->regs;
84362306a36Sopenharmony_ci	hvs->regset.regs = hvs_regs;
84462306a36Sopenharmony_ci	hvs->regset.nregs = ARRAY_SIZE(hvs_regs);
84562306a36Sopenharmony_ci
84662306a36Sopenharmony_ci	if (vc4->is_vc5) {
84762306a36Sopenharmony_ci		struct rpi_firmware *firmware;
84862306a36Sopenharmony_ci		struct device_node *node;
84962306a36Sopenharmony_ci		unsigned int max_rate;
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci		node = rpi_firmware_find_node();
85262306a36Sopenharmony_ci		if (!node)
85362306a36Sopenharmony_ci			return -EINVAL;
85462306a36Sopenharmony_ci
85562306a36Sopenharmony_ci		firmware = rpi_firmware_get(node);
85662306a36Sopenharmony_ci		of_node_put(node);
85762306a36Sopenharmony_ci		if (!firmware)
85862306a36Sopenharmony_ci			return -EPROBE_DEFER;
85962306a36Sopenharmony_ci
86062306a36Sopenharmony_ci		hvs->core_clk = devm_clk_get(&pdev->dev, NULL);
86162306a36Sopenharmony_ci		if (IS_ERR(hvs->core_clk)) {
86262306a36Sopenharmony_ci			dev_err(&pdev->dev, "Couldn't get core clock\n");
86362306a36Sopenharmony_ci			return PTR_ERR(hvs->core_clk);
86462306a36Sopenharmony_ci		}
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ci		max_rate = rpi_firmware_clk_get_max_rate(firmware,
86762306a36Sopenharmony_ci							 RPI_FIRMWARE_CORE_CLK_ID);
86862306a36Sopenharmony_ci		rpi_firmware_put(firmware);
86962306a36Sopenharmony_ci		if (max_rate >= 550000000)
87062306a36Sopenharmony_ci			hvs->vc5_hdmi_enable_hdmi_20 = true;
87162306a36Sopenharmony_ci
87262306a36Sopenharmony_ci		if (max_rate >= 600000000)
87362306a36Sopenharmony_ci			hvs->vc5_hdmi_enable_4096by2160 = true;
87462306a36Sopenharmony_ci
87562306a36Sopenharmony_ci		hvs->max_core_rate = max_rate;
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci		ret = clk_prepare_enable(hvs->core_clk);
87862306a36Sopenharmony_ci		if (ret) {
87962306a36Sopenharmony_ci			dev_err(&pdev->dev, "Couldn't enable the core clock\n");
88062306a36Sopenharmony_ci			return ret;
88162306a36Sopenharmony_ci		}
88262306a36Sopenharmony_ci	}
88362306a36Sopenharmony_ci
88462306a36Sopenharmony_ci	if (!vc4->is_vc5)
88562306a36Sopenharmony_ci		hvs->dlist = hvs->regs + SCALER_DLIST_START;
88662306a36Sopenharmony_ci	else
88762306a36Sopenharmony_ci		hvs->dlist = hvs->regs + SCALER5_DLIST_START;
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_ci	/* Upload filter kernels.  We only have the one for now, so we
89062306a36Sopenharmony_ci	 * keep it around for the lifetime of the driver.
89162306a36Sopenharmony_ci	 */
89262306a36Sopenharmony_ci	ret = vc4_hvs_upload_linear_kernel(hvs,
89362306a36Sopenharmony_ci					   &hvs->mitchell_netravali_filter,
89462306a36Sopenharmony_ci					   mitchell_netravali_1_3_1_3_kernel);
89562306a36Sopenharmony_ci	if (ret)
89662306a36Sopenharmony_ci		return ret;
89762306a36Sopenharmony_ci
89862306a36Sopenharmony_ci	reg = HVS_READ(SCALER_DISPECTRL);
89962306a36Sopenharmony_ci	reg &= ~SCALER_DISPECTRL_DSP2_MUX_MASK;
90062306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPECTRL,
90162306a36Sopenharmony_ci		  reg | VC4_SET_FIELD(0, SCALER_DISPECTRL_DSP2_MUX));
90262306a36Sopenharmony_ci
90362306a36Sopenharmony_ci	reg = HVS_READ(SCALER_DISPCTRL);
90462306a36Sopenharmony_ci	reg &= ~SCALER_DISPCTRL_DSP3_MUX_MASK;
90562306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRL,
90662306a36Sopenharmony_ci		  reg | VC4_SET_FIELD(3, SCALER_DISPCTRL_DSP3_MUX));
90762306a36Sopenharmony_ci
90862306a36Sopenharmony_ci	reg = HVS_READ(SCALER_DISPEOLN);
90962306a36Sopenharmony_ci	reg &= ~SCALER_DISPEOLN_DSP4_MUX_MASK;
91062306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPEOLN,
91162306a36Sopenharmony_ci		  reg | VC4_SET_FIELD(3, SCALER_DISPEOLN_DSP4_MUX));
91262306a36Sopenharmony_ci
91362306a36Sopenharmony_ci	reg = HVS_READ(SCALER_DISPDITHER);
91462306a36Sopenharmony_ci	reg &= ~SCALER_DISPDITHER_DSP5_MUX_MASK;
91562306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPDITHER,
91662306a36Sopenharmony_ci		  reg | VC4_SET_FIELD(3, SCALER_DISPDITHER_DSP5_MUX));
91762306a36Sopenharmony_ci
91862306a36Sopenharmony_ci	dispctrl = HVS_READ(SCALER_DISPCTRL);
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci	dispctrl |= SCALER_DISPCTRL_ENABLE;
92162306a36Sopenharmony_ci	dispctrl |= SCALER_DISPCTRL_DISPEIRQ(0) |
92262306a36Sopenharmony_ci		    SCALER_DISPCTRL_DISPEIRQ(1) |
92362306a36Sopenharmony_ci		    SCALER_DISPCTRL_DISPEIRQ(2);
92462306a36Sopenharmony_ci
92562306a36Sopenharmony_ci	if (!vc4->is_vc5)
92662306a36Sopenharmony_ci		dispctrl &= ~(SCALER_DISPCTRL_DMAEIRQ |
92762306a36Sopenharmony_ci			      SCALER_DISPCTRL_SLVWREIRQ |
92862306a36Sopenharmony_ci			      SCALER_DISPCTRL_SLVRDEIRQ |
92962306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEIEOF(0) |
93062306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEIEOF(1) |
93162306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEIEOF(2) |
93262306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEIEOLN(0) |
93362306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEIEOLN(1) |
93462306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEIEOLN(2) |
93562306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEISLUR(0) |
93662306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEISLUR(1) |
93762306a36Sopenharmony_ci			      SCALER_DISPCTRL_DSPEISLUR(2) |
93862306a36Sopenharmony_ci			      SCALER_DISPCTRL_SCLEIRQ);
93962306a36Sopenharmony_ci	else
94062306a36Sopenharmony_ci		dispctrl &= ~(SCALER_DISPCTRL_DMAEIRQ |
94162306a36Sopenharmony_ci			      SCALER5_DISPCTRL_SLVEIRQ |
94262306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEIEOF(0) |
94362306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEIEOF(1) |
94462306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEIEOF(2) |
94562306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEIEOLN(0) |
94662306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEIEOLN(1) |
94762306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEIEOLN(2) |
94862306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEISLUR(0) |
94962306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEISLUR(1) |
95062306a36Sopenharmony_ci			      SCALER5_DISPCTRL_DSPEISLUR(2) |
95162306a36Sopenharmony_ci			      SCALER_DISPCTRL_SCLEIRQ);
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_ci
95462306a36Sopenharmony_ci	/* Set AXI panic mode.
95562306a36Sopenharmony_ci	 * VC4 panics when < 2 lines in FIFO.
95662306a36Sopenharmony_ci	 * VC5 panics when less than 1 line in the FIFO.
95762306a36Sopenharmony_ci	 */
95862306a36Sopenharmony_ci	dispctrl &= ~(SCALER_DISPCTRL_PANIC0_MASK |
95962306a36Sopenharmony_ci		      SCALER_DISPCTRL_PANIC1_MASK |
96062306a36Sopenharmony_ci		      SCALER_DISPCTRL_PANIC2_MASK);
96162306a36Sopenharmony_ci	dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC0);
96262306a36Sopenharmony_ci	dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC1);
96362306a36Sopenharmony_ci	dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC2);
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRL, dispctrl);
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_ci	/* Recompute Composite Output Buffer (COB) allocations for the displays
96862306a36Sopenharmony_ci	 */
96962306a36Sopenharmony_ci	if (!vc4->is_vc5) {
97062306a36Sopenharmony_ci		/* The COB is 20736 pixels, or just over 10 lines at 2048 wide.
97162306a36Sopenharmony_ci		 * The bottom 2048 pixels are full 32bpp RGBA (intended for the
97262306a36Sopenharmony_ci		 * TXP composing RGBA to memory), whilst the remainder are only
97362306a36Sopenharmony_ci		 * 24bpp RGB.
97462306a36Sopenharmony_ci		 *
97562306a36Sopenharmony_ci		 * Assign 3 lines to channels 1 & 2, and just over 4 lines to
97662306a36Sopenharmony_ci		 * channel 0.
97762306a36Sopenharmony_ci		 */
97862306a36Sopenharmony_ci		#define VC4_COB_SIZE		20736
97962306a36Sopenharmony_ci		#define VC4_COB_LINE_WIDTH	2048
98062306a36Sopenharmony_ci		#define VC4_COB_NUM_LINES	3
98162306a36Sopenharmony_ci		reg = 0;
98262306a36Sopenharmony_ci		top = VC4_COB_LINE_WIDTH * VC4_COB_NUM_LINES;
98362306a36Sopenharmony_ci		reg |= (top - 1) << 16;
98462306a36Sopenharmony_ci		HVS_WRITE(SCALER_DISPBASE2, reg);
98562306a36Sopenharmony_ci		reg = top;
98662306a36Sopenharmony_ci		top += VC4_COB_LINE_WIDTH * VC4_COB_NUM_LINES;
98762306a36Sopenharmony_ci		reg |= (top - 1) << 16;
98862306a36Sopenharmony_ci		HVS_WRITE(SCALER_DISPBASE1, reg);
98962306a36Sopenharmony_ci		reg = top;
99062306a36Sopenharmony_ci		top = VC4_COB_SIZE;
99162306a36Sopenharmony_ci		reg |= (top - 1) << 16;
99262306a36Sopenharmony_ci		HVS_WRITE(SCALER_DISPBASE0, reg);
99362306a36Sopenharmony_ci	} else {
99462306a36Sopenharmony_ci		/* The COB is 44416 pixels, or 10.8 lines at 4096 wide.
99562306a36Sopenharmony_ci		 * The bottom 4096 pixels are full RGBA (intended for the TXP
99662306a36Sopenharmony_ci		 * composing RGBA to memory), whilst the remainder are only
99762306a36Sopenharmony_ci		 * RGB. Addressing is always pixel wide.
99862306a36Sopenharmony_ci		 *
99962306a36Sopenharmony_ci		 * Assign 3 lines of 4096 to channels 1 & 2, and just over 4
100062306a36Sopenharmony_ci		 * lines. to channel 0.
100162306a36Sopenharmony_ci		 */
100262306a36Sopenharmony_ci		#define VC5_COB_SIZE		44416
100362306a36Sopenharmony_ci		#define VC5_COB_LINE_WIDTH	4096
100462306a36Sopenharmony_ci		#define VC5_COB_NUM_LINES	3
100562306a36Sopenharmony_ci		reg = 0;
100662306a36Sopenharmony_ci		top = VC5_COB_LINE_WIDTH * VC5_COB_NUM_LINES;
100762306a36Sopenharmony_ci		reg |= top << 16;
100862306a36Sopenharmony_ci		HVS_WRITE(SCALER_DISPBASE2, reg);
100962306a36Sopenharmony_ci		top += 16;
101062306a36Sopenharmony_ci		reg = top;
101162306a36Sopenharmony_ci		top += VC5_COB_LINE_WIDTH * VC5_COB_NUM_LINES;
101262306a36Sopenharmony_ci		reg |= top << 16;
101362306a36Sopenharmony_ci		HVS_WRITE(SCALER_DISPBASE1, reg);
101462306a36Sopenharmony_ci		top += 16;
101562306a36Sopenharmony_ci		reg = top;
101662306a36Sopenharmony_ci		top = VC5_COB_SIZE;
101762306a36Sopenharmony_ci		reg |= top << 16;
101862306a36Sopenharmony_ci		HVS_WRITE(SCALER_DISPBASE0, reg);
101962306a36Sopenharmony_ci	}
102062306a36Sopenharmony_ci
102162306a36Sopenharmony_ci	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
102262306a36Sopenharmony_ci			       vc4_hvs_irq_handler, 0, "vc4 hvs", drm);
102362306a36Sopenharmony_ci	if (ret)
102462306a36Sopenharmony_ci		return ret;
102562306a36Sopenharmony_ci
102662306a36Sopenharmony_ci	return 0;
102762306a36Sopenharmony_ci}
102862306a36Sopenharmony_ci
102962306a36Sopenharmony_cistatic void vc4_hvs_unbind(struct device *dev, struct device *master,
103062306a36Sopenharmony_ci			   void *data)
103162306a36Sopenharmony_ci{
103262306a36Sopenharmony_ci	struct drm_device *drm = dev_get_drvdata(master);
103362306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(drm);
103462306a36Sopenharmony_ci	struct vc4_hvs *hvs = vc4->hvs;
103562306a36Sopenharmony_ci	struct drm_mm_node *node, *next;
103662306a36Sopenharmony_ci
103762306a36Sopenharmony_ci	if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter))
103862306a36Sopenharmony_ci		drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter);
103962306a36Sopenharmony_ci
104062306a36Sopenharmony_ci	drm_mm_for_each_node_safe(node, next, &vc4->hvs->dlist_mm)
104162306a36Sopenharmony_ci		drm_mm_remove_node(node);
104262306a36Sopenharmony_ci
104362306a36Sopenharmony_ci	drm_mm_takedown(&vc4->hvs->dlist_mm);
104462306a36Sopenharmony_ci
104562306a36Sopenharmony_ci	drm_mm_for_each_node_safe(node, next, &vc4->hvs->lbm_mm)
104662306a36Sopenharmony_ci		drm_mm_remove_node(node);
104762306a36Sopenharmony_ci	drm_mm_takedown(&vc4->hvs->lbm_mm);
104862306a36Sopenharmony_ci
104962306a36Sopenharmony_ci	clk_disable_unprepare(hvs->core_clk);
105062306a36Sopenharmony_ci
105162306a36Sopenharmony_ci	vc4->hvs = NULL;
105262306a36Sopenharmony_ci}
105362306a36Sopenharmony_ci
105462306a36Sopenharmony_cistatic const struct component_ops vc4_hvs_ops = {
105562306a36Sopenharmony_ci	.bind   = vc4_hvs_bind,
105662306a36Sopenharmony_ci	.unbind = vc4_hvs_unbind,
105762306a36Sopenharmony_ci};
105862306a36Sopenharmony_ci
105962306a36Sopenharmony_cistatic int vc4_hvs_dev_probe(struct platform_device *pdev)
106062306a36Sopenharmony_ci{
106162306a36Sopenharmony_ci	return component_add(&pdev->dev, &vc4_hvs_ops);
106262306a36Sopenharmony_ci}
106362306a36Sopenharmony_ci
106462306a36Sopenharmony_cistatic void vc4_hvs_dev_remove(struct platform_device *pdev)
106562306a36Sopenharmony_ci{
106662306a36Sopenharmony_ci	component_del(&pdev->dev, &vc4_hvs_ops);
106762306a36Sopenharmony_ci}
106862306a36Sopenharmony_ci
106962306a36Sopenharmony_cistatic const struct of_device_id vc4_hvs_dt_match[] = {
107062306a36Sopenharmony_ci	{ .compatible = "brcm,bcm2711-hvs" },
107162306a36Sopenharmony_ci	{ .compatible = "brcm,bcm2835-hvs" },
107262306a36Sopenharmony_ci	{}
107362306a36Sopenharmony_ci};
107462306a36Sopenharmony_ci
107562306a36Sopenharmony_cistruct platform_driver vc4_hvs_driver = {
107662306a36Sopenharmony_ci	.probe = vc4_hvs_dev_probe,
107762306a36Sopenharmony_ci	.remove_new = vc4_hvs_dev_remove,
107862306a36Sopenharmony_ci	.driver = {
107962306a36Sopenharmony_ci		.name = "vc4_hvs",
108062306a36Sopenharmony_ci		.of_match_table = vc4_hvs_dt_match,
108162306a36Sopenharmony_ci	},
108262306a36Sopenharmony_ci};
1083