18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2015 Broadcom
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci/**
78c2ecf20Sopenharmony_ci * DOC: VC4 HVS module.
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * The Hardware Video Scaler (HVS) is the piece of hardware that does
108c2ecf20Sopenharmony_ci * translation, scaling, colorspace conversion, and compositing of
118c2ecf20Sopenharmony_ci * pixels stored in framebuffers into a FIFO of pixels going out to
128c2ecf20Sopenharmony_ci * the Pixel Valve (CRTC).  It operates at the system clock rate (the
138c2ecf20Sopenharmony_ci * system audio clock gate, specifically), which is much higher than
148c2ecf20Sopenharmony_ci * the pixel clock rate.
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci * There is a single global HVS, with multiple output FIFOs that can
178c2ecf20Sopenharmony_ci * be consumed by the PVs.  This file just manages the resources for
188c2ecf20Sopenharmony_ci * the HVS, while the vc4_crtc.c code actually drives HVS setup for
198c2ecf20Sopenharmony_ci * each CRTC.
208c2ecf20Sopenharmony_ci */
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#include <linux/bitfield.h>
238c2ecf20Sopenharmony_ci#include <linux/clk.h>
248c2ecf20Sopenharmony_ci#include <linux/component.h>
258c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h>
288c2ecf20Sopenharmony_ci#include <drm/drm_vblank.h>
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#include "vc4_drv.h"
318c2ecf20Sopenharmony_ci#include "vc4_regs.h"
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic const struct debugfs_reg32 hvs_regs[] = {
348c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPCTRL),
358c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPSTAT),
368c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPID),
378c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPECTRL),
388c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPPROF),
398c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPDITHER),
408c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPEOLN),
418c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPLIST0),
428c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPLIST1),
438c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPLIST2),
448c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPLSTAT),
458c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPLACT0),
468c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPLACT1),
478c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPLACT2),
488c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPCTRL0),
498c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPBKGND0),
508c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPSTAT0),
518c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPBASE0),
528c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPCTRL1),
538c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPBKGND1),
548c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPSTAT1),
558c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPBASE1),
568c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPCTRL2),
578c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPBKGND2),
588c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPSTAT2),
598c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPBASE2),
608c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_DISPALPHA2),
618c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_OLEDOFFS),
628c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_OLEDCOEF0),
638c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_OLEDCOEF1),
648c2ecf20Sopenharmony_ci	VC4_REG32(SCALER_OLEDCOEF2),
658c2ecf20Sopenharmony_ci};
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_civoid vc4_hvs_dump_state(struct drm_device *dev)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
708c2ecf20Sopenharmony_ci	struct drm_printer p = drm_info_printer(&vc4->hvs->pdev->dev);
718c2ecf20Sopenharmony_ci	int i;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	drm_print_regset32(&p, &vc4->hvs->regset);
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	DRM_INFO("HVS ctx:\n");
768c2ecf20Sopenharmony_ci	for (i = 0; i < 64; i += 4) {
778c2ecf20Sopenharmony_ci		DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n",
788c2ecf20Sopenharmony_ci			 i * 4, i < HVS_BOOTLOADER_DLIST_END ? "B" : "D",
798c2ecf20Sopenharmony_ci			 readl((u32 __iomem *)vc4->hvs->dlist + i + 0),
808c2ecf20Sopenharmony_ci			 readl((u32 __iomem *)vc4->hvs->dlist + i + 1),
818c2ecf20Sopenharmony_ci			 readl((u32 __iomem *)vc4->hvs->dlist + i + 2),
828c2ecf20Sopenharmony_ci			 readl((u32 __iomem *)vc4->hvs->dlist + i + 3));
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_cistatic int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	struct drm_info_node *node = m->private;
898c2ecf20Sopenharmony_ci	struct drm_device *dev = node->minor->dev;
908c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
918c2ecf20Sopenharmony_ci	struct drm_printer p = drm_seq_file_printer(m);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	drm_printf(&p, "%d\n", atomic_read(&vc4->underrun));
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	return 0;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci/* The filter kernel is composed of dwords each containing 3 9-bit
998c2ecf20Sopenharmony_ci * signed integers packed next to each other.
1008c2ecf20Sopenharmony_ci */
1018c2ecf20Sopenharmony_ci#define VC4_INT_TO_COEFF(coeff) (coeff & 0x1ff)
1028c2ecf20Sopenharmony_ci#define VC4_PPF_FILTER_WORD(c0, c1, c2)				\
1038c2ecf20Sopenharmony_ci	((((c0) & 0x1ff) << 0) |				\
1048c2ecf20Sopenharmony_ci	 (((c1) & 0x1ff) << 9) |				\
1058c2ecf20Sopenharmony_ci	 (((c2) & 0x1ff) << 18))
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci/* The whole filter kernel is arranged as the coefficients 0-16 going
1088c2ecf20Sopenharmony_ci * up, then a pad, then 17-31 going down and reversed within the
1098c2ecf20Sopenharmony_ci * dwords.  This means that a linear phase kernel (where it's
1108c2ecf20Sopenharmony_ci * symmetrical at the boundary between 15 and 16) has the last 5
1118c2ecf20Sopenharmony_ci * dwords matching the first 5, but reversed.
1128c2ecf20Sopenharmony_ci */
1138c2ecf20Sopenharmony_ci#define VC4_LINEAR_PHASE_KERNEL(c0, c1, c2, c3, c4, c5, c6, c7, c8,	\
1148c2ecf20Sopenharmony_ci				c9, c10, c11, c12, c13, c14, c15)	\
1158c2ecf20Sopenharmony_ci	{VC4_PPF_FILTER_WORD(c0, c1, c2),				\
1168c2ecf20Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c3, c4, c5),				\
1178c2ecf20Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c6, c7, c8),				\
1188c2ecf20Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c9, c10, c11),				\
1198c2ecf20Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c12, c13, c14),				\
1208c2ecf20Sopenharmony_ci	 VC4_PPF_FILTER_WORD(c15, c15, 0)}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci#define VC4_LINEAR_PHASE_KERNEL_DWORDS 6
1238c2ecf20Sopenharmony_ci#define VC4_KERNEL_DWORDS (VC4_LINEAR_PHASE_KERNEL_DWORDS * 2 - 1)
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci/* Recommended B=1/3, C=1/3 filter choice from Mitchell/Netravali.
1268c2ecf20Sopenharmony_ci * http://www.cs.utexas.edu/~fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf
1278c2ecf20Sopenharmony_ci */
1288c2ecf20Sopenharmony_cistatic const u32 mitchell_netravali_1_3_1_3_kernel[] =
1298c2ecf20Sopenharmony_ci	VC4_LINEAR_PHASE_KERNEL(0, -2, -6, -8, -10, -8, -3, 2, 18,
1308c2ecf20Sopenharmony_ci				50, 82, 119, 155, 187, 213, 227);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs,
1338c2ecf20Sopenharmony_ci					struct drm_mm_node *space,
1348c2ecf20Sopenharmony_ci					const u32 *kernel)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	int ret, i;
1378c2ecf20Sopenharmony_ci	u32 __iomem *dst_kernel;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS);
1408c2ecf20Sopenharmony_ci	if (ret) {
1418c2ecf20Sopenharmony_ci		DRM_ERROR("Failed to allocate space for filter kernel: %d\n",
1428c2ecf20Sopenharmony_ci			  ret);
1438c2ecf20Sopenharmony_ci		return ret;
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	dst_kernel = hvs->dlist + space->start;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	for (i = 0; i < VC4_KERNEL_DWORDS; i++) {
1498c2ecf20Sopenharmony_ci		if (i < VC4_LINEAR_PHASE_KERNEL_DWORDS)
1508c2ecf20Sopenharmony_ci			writel(kernel[i], &dst_kernel[i]);
1518c2ecf20Sopenharmony_ci		else {
1528c2ecf20Sopenharmony_ci			writel(kernel[VC4_KERNEL_DWORDS - i - 1],
1538c2ecf20Sopenharmony_ci			       &dst_kernel[i]);
1548c2ecf20Sopenharmony_ci		}
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	return 0;
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic void vc4_hvs_lut_load(struct drm_crtc *crtc)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	struct drm_device *dev = crtc->dev;
1638c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
1648c2ecf20Sopenharmony_ci	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
1658c2ecf20Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
1668c2ecf20Sopenharmony_ci	u32 i;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	/* The LUT memory is laid out with each HVS channel in order,
1698c2ecf20Sopenharmony_ci	 * each of which takes 256 writes for R, 256 for G, then 256
1708c2ecf20Sopenharmony_ci	 * for B.
1718c2ecf20Sopenharmony_ci	 */
1728c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_GAMADDR,
1738c2ecf20Sopenharmony_ci		  SCALER_GAMADDR_AUTOINC |
1748c2ecf20Sopenharmony_ci		  (vc4_state->assigned_channel * 3 * crtc->gamma_size));
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	for (i = 0; i < crtc->gamma_size; i++)
1778c2ecf20Sopenharmony_ci		HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_r[i]);
1788c2ecf20Sopenharmony_ci	for (i = 0; i < crtc->gamma_size; i++)
1798c2ecf20Sopenharmony_ci		HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_g[i]);
1808c2ecf20Sopenharmony_ci	for (i = 0; i < crtc->gamma_size; i++)
1818c2ecf20Sopenharmony_ci		HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]);
1828c2ecf20Sopenharmony_ci}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cistatic void vc4_hvs_update_gamma_lut(struct drm_crtc *crtc)
1858c2ecf20Sopenharmony_ci{
1868c2ecf20Sopenharmony_ci	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
1878c2ecf20Sopenharmony_ci	struct drm_color_lut *lut = crtc->state->gamma_lut->data;
1888c2ecf20Sopenharmony_ci	u32 length = drm_color_lut_size(crtc->state->gamma_lut);
1898c2ecf20Sopenharmony_ci	u32 i;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	for (i = 0; i < length; i++) {
1928c2ecf20Sopenharmony_ci		vc4_crtc->lut_r[i] = drm_color_lut_extract(lut[i].red, 8);
1938c2ecf20Sopenharmony_ci		vc4_crtc->lut_g[i] = drm_color_lut_extract(lut[i].green, 8);
1948c2ecf20Sopenharmony_ci		vc4_crtc->lut_b[i] = drm_color_lut_extract(lut[i].blue, 8);
1958c2ecf20Sopenharmony_ci	}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	vc4_hvs_lut_load(crtc);
1988c2ecf20Sopenharmony_ci}
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ciint vc4_hvs_get_fifo_from_output(struct drm_device *dev, unsigned int output)
2018c2ecf20Sopenharmony_ci{
2028c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
2038c2ecf20Sopenharmony_ci	u32 reg;
2048c2ecf20Sopenharmony_ci	int ret;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	if (!vc4->hvs->hvs5)
2078c2ecf20Sopenharmony_ci		return output;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	switch (output) {
2108c2ecf20Sopenharmony_ci	case 0:
2118c2ecf20Sopenharmony_ci		return 0;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	case 1:
2148c2ecf20Sopenharmony_ci		return 1;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	case 2:
2178c2ecf20Sopenharmony_ci		reg = HVS_READ(SCALER_DISPECTRL);
2188c2ecf20Sopenharmony_ci		ret = FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg);
2198c2ecf20Sopenharmony_ci		if (ret == 0)
2208c2ecf20Sopenharmony_ci			return 2;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci		return 0;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	case 3:
2258c2ecf20Sopenharmony_ci		reg = HVS_READ(SCALER_DISPCTRL);
2268c2ecf20Sopenharmony_ci		ret = FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg);
2278c2ecf20Sopenharmony_ci		if (ret == 3)
2288c2ecf20Sopenharmony_ci			return -EPIPE;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci		return ret;
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	case 4:
2338c2ecf20Sopenharmony_ci		reg = HVS_READ(SCALER_DISPEOLN);
2348c2ecf20Sopenharmony_ci		ret = FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg);
2358c2ecf20Sopenharmony_ci		if (ret == 3)
2368c2ecf20Sopenharmony_ci			return -EPIPE;
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci		return ret;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	case 5:
2418c2ecf20Sopenharmony_ci		reg = HVS_READ(SCALER_DISPDITHER);
2428c2ecf20Sopenharmony_ci		ret = FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg);
2438c2ecf20Sopenharmony_ci		if (ret == 3)
2448c2ecf20Sopenharmony_ci			return -EPIPE;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci		return ret;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	default:
2498c2ecf20Sopenharmony_ci		return -EPIPE;
2508c2ecf20Sopenharmony_ci	}
2518c2ecf20Sopenharmony_ci}
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_cistatic int vc4_hvs_init_channel(struct vc4_dev *vc4, struct drm_crtc *crtc,
2548c2ecf20Sopenharmony_ci				struct drm_display_mode *mode, bool oneshot)
2558c2ecf20Sopenharmony_ci{
2568c2ecf20Sopenharmony_ci	struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state);
2578c2ecf20Sopenharmony_ci	unsigned int chan = vc4_crtc_state->assigned_channel;
2588c2ecf20Sopenharmony_ci	bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
2598c2ecf20Sopenharmony_ci	u32 dispbkgndx;
2608c2ecf20Sopenharmony_ci	u32 dispctrl;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan), 0);
2638c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET);
2648c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan), 0);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	/* Turn on the scaler, which will wait for vstart to start
2678c2ecf20Sopenharmony_ci	 * compositing.
2688c2ecf20Sopenharmony_ci	 * When feeding the transposer, we should operate in oneshot
2698c2ecf20Sopenharmony_ci	 * mode.
2708c2ecf20Sopenharmony_ci	 */
2718c2ecf20Sopenharmony_ci	dispctrl = SCALER_DISPCTRLX_ENABLE;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	if (!vc4->hvs->hvs5)
2748c2ecf20Sopenharmony_ci		dispctrl |= VC4_SET_FIELD(mode->hdisplay,
2758c2ecf20Sopenharmony_ci					  SCALER_DISPCTRLX_WIDTH) |
2768c2ecf20Sopenharmony_ci			    VC4_SET_FIELD(mode->vdisplay,
2778c2ecf20Sopenharmony_ci					  SCALER_DISPCTRLX_HEIGHT) |
2788c2ecf20Sopenharmony_ci			    (oneshot ? SCALER_DISPCTRLX_ONESHOT : 0);
2798c2ecf20Sopenharmony_ci	else
2808c2ecf20Sopenharmony_ci		dispctrl |= VC4_SET_FIELD(mode->hdisplay,
2818c2ecf20Sopenharmony_ci					  SCALER5_DISPCTRLX_WIDTH) |
2828c2ecf20Sopenharmony_ci			    VC4_SET_FIELD(mode->vdisplay,
2838c2ecf20Sopenharmony_ci					  SCALER5_DISPCTRLX_HEIGHT) |
2848c2ecf20Sopenharmony_ci			    (oneshot ? SCALER5_DISPCTRLX_ONESHOT : 0);
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan), dispctrl);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(chan));
2898c2ecf20Sopenharmony_ci	dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
2908c2ecf20Sopenharmony_ci	dispbkgndx &= ~SCALER_DISPBKGND_INTERLACE;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPBKGNDX(chan), dispbkgndx |
2938c2ecf20Sopenharmony_ci		  SCALER_DISPBKGND_AUTOHS |
2948c2ecf20Sopenharmony_ci		  ((!vc4->hvs->hvs5) ? SCALER_DISPBKGND_GAMMA : 0) |
2958c2ecf20Sopenharmony_ci		  (interlace ? SCALER_DISPBKGND_INTERLACE : 0));
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	/* Reload the LUT, since the SRAMs would have been disabled if
2988c2ecf20Sopenharmony_ci	 * all CRTCs had SCALER_DISPBKGND_GAMMA unset at once.
2998c2ecf20Sopenharmony_ci	 */
3008c2ecf20Sopenharmony_ci	vc4_hvs_lut_load(crtc);
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	return 0;
3038c2ecf20Sopenharmony_ci}
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_civoid vc4_hvs_stop_channel(struct drm_device *dev, unsigned int chan)
3068c2ecf20Sopenharmony_ci{
3078c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE)
3108c2ecf20Sopenharmony_ci		return;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan),
3138c2ecf20Sopenharmony_ci		  HVS_READ(SCALER_DISPCTRLX(chan)) | SCALER_DISPCTRLX_RESET);
3148c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRLX(chan),
3158c2ecf20Sopenharmony_ci		  HVS_READ(SCALER_DISPCTRLX(chan)) & ~SCALER_DISPCTRLX_ENABLE);
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	/* Once we leave, the scaler should be disabled and its fifo empty. */
3188c2ecf20Sopenharmony_ci	WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET);
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)),
3218c2ecf20Sopenharmony_ci				   SCALER_DISPSTATX_MODE) !=
3228c2ecf20Sopenharmony_ci		     SCALER_DISPSTATX_MODE_DISABLED);
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci	WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) &
3258c2ecf20Sopenharmony_ci		      (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) !=
3268c2ecf20Sopenharmony_ci		     SCALER_DISPSTATX_EMPTY);
3278c2ecf20Sopenharmony_ci}
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ciint vc4_hvs_atomic_check(struct drm_crtc *crtc,
3308c2ecf20Sopenharmony_ci			 struct drm_crtc_state *state)
3318c2ecf20Sopenharmony_ci{
3328c2ecf20Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
3338c2ecf20Sopenharmony_ci	struct drm_device *dev = crtc->dev;
3348c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
3358c2ecf20Sopenharmony_ci	struct drm_plane *plane;
3368c2ecf20Sopenharmony_ci	unsigned long flags;
3378c2ecf20Sopenharmony_ci	const struct drm_plane_state *plane_state;
3388c2ecf20Sopenharmony_ci	u32 dlist_count = 0;
3398c2ecf20Sopenharmony_ci	int ret;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	/* The pixelvalve can only feed one encoder (and encoders are
3428c2ecf20Sopenharmony_ci	 * 1:1 with connectors.)
3438c2ecf20Sopenharmony_ci	 */
3448c2ecf20Sopenharmony_ci	if (hweight32(state->connector_mask) > 1)
3458c2ecf20Sopenharmony_ci		return -EINVAL;
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, state)
3488c2ecf20Sopenharmony_ci		dlist_count += vc4_plane_dlist_size(plane_state);
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	dlist_count++; /* Account for SCALER_CTL0_END. */
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vc4->hvs->mm_lock, flags);
3538c2ecf20Sopenharmony_ci	ret = drm_mm_insert_node(&vc4->hvs->dlist_mm, &vc4_state->mm,
3548c2ecf20Sopenharmony_ci				 dlist_count);
3558c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags);
3568c2ecf20Sopenharmony_ci	if (ret)
3578c2ecf20Sopenharmony_ci		return ret;
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	return 0;
3608c2ecf20Sopenharmony_ci}
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_cistatic void vc4_hvs_update_dlist(struct drm_crtc *crtc)
3638c2ecf20Sopenharmony_ci{
3648c2ecf20Sopenharmony_ci	struct drm_device *dev = crtc->dev;
3658c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
3668c2ecf20Sopenharmony_ci	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
3678c2ecf20Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	if (crtc->state->event) {
3708c2ecf20Sopenharmony_ci		unsigned long flags;
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci		crtc->state->event->pipe = drm_crtc_index(crtc);
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci		spin_lock_irqsave(&dev->event_lock, flags);
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci		if (!vc4_state->feed_txp || vc4_state->txp_armed) {
3798c2ecf20Sopenharmony_ci			vc4_crtc->event = crtc->state->event;
3808c2ecf20Sopenharmony_ci			crtc->state->event = NULL;
3818c2ecf20Sopenharmony_ci		}
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci		HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel),
3848c2ecf20Sopenharmony_ci			  vc4_state->mm.start);
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->event_lock, flags);
3878c2ecf20Sopenharmony_ci	} else {
3888c2ecf20Sopenharmony_ci		HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel),
3898c2ecf20Sopenharmony_ci			  vc4_state->mm.start);
3908c2ecf20Sopenharmony_ci	}
3918c2ecf20Sopenharmony_ci}
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_civoid vc4_hvs_atomic_enable(struct drm_crtc *crtc,
3948c2ecf20Sopenharmony_ci			   struct drm_crtc_state *old_state)
3958c2ecf20Sopenharmony_ci{
3968c2ecf20Sopenharmony_ci	struct drm_device *dev = crtc->dev;
3978c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
3988c2ecf20Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
3998c2ecf20Sopenharmony_ci	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
4008c2ecf20Sopenharmony_ci	bool oneshot = vc4_state->feed_txp;
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	vc4_hvs_update_dlist(crtc);
4038c2ecf20Sopenharmony_ci	vc4_hvs_init_channel(vc4, crtc, mode, oneshot);
4048c2ecf20Sopenharmony_ci}
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_civoid vc4_hvs_atomic_disable(struct drm_crtc *crtc,
4078c2ecf20Sopenharmony_ci			    struct drm_crtc_state *old_state)
4088c2ecf20Sopenharmony_ci{
4098c2ecf20Sopenharmony_ci	struct drm_device *dev = crtc->dev;
4108c2ecf20Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(old_state);
4118c2ecf20Sopenharmony_ci	unsigned int chan = vc4_state->assigned_channel;
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	vc4_hvs_stop_channel(dev, chan);
4148c2ecf20Sopenharmony_ci}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_civoid vc4_hvs_atomic_flush(struct drm_crtc *crtc,
4178c2ecf20Sopenharmony_ci			  struct drm_crtc_state *old_state)
4188c2ecf20Sopenharmony_ci{
4198c2ecf20Sopenharmony_ci	struct drm_device *dev = crtc->dev;
4208c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
4218c2ecf20Sopenharmony_ci	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
4228c2ecf20Sopenharmony_ci	struct drm_plane *plane;
4238c2ecf20Sopenharmony_ci	struct vc4_plane_state *vc4_plane_state;
4248c2ecf20Sopenharmony_ci	bool debug_dump_regs = false;
4258c2ecf20Sopenharmony_ci	bool enable_bg_fill = false;
4268c2ecf20Sopenharmony_ci	u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start;
4278c2ecf20Sopenharmony_ci	u32 __iomem *dlist_next = dlist_start;
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	if (debug_dump_regs) {
4308c2ecf20Sopenharmony_ci		DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc));
4318c2ecf20Sopenharmony_ci		vc4_hvs_dump_state(dev);
4328c2ecf20Sopenharmony_ci	}
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	/* Copy all the active planes' dlist contents to the hardware dlist. */
4358c2ecf20Sopenharmony_ci	drm_atomic_crtc_for_each_plane(plane, crtc) {
4368c2ecf20Sopenharmony_ci		/* Is this the first active plane? */
4378c2ecf20Sopenharmony_ci		if (dlist_next == dlist_start) {
4388c2ecf20Sopenharmony_ci			/* We need to enable background fill when a plane
4398c2ecf20Sopenharmony_ci			 * could be alpha blending from the background, i.e.
4408c2ecf20Sopenharmony_ci			 * where no other plane is underneath. It suffices to
4418c2ecf20Sopenharmony_ci			 * consider the first active plane here since we set
4428c2ecf20Sopenharmony_ci			 * needs_bg_fill such that either the first plane
4438c2ecf20Sopenharmony_ci			 * already needs it or all planes on top blend from
4448c2ecf20Sopenharmony_ci			 * the first or a lower plane.
4458c2ecf20Sopenharmony_ci			 */
4468c2ecf20Sopenharmony_ci			vc4_plane_state = to_vc4_plane_state(plane->state);
4478c2ecf20Sopenharmony_ci			enable_bg_fill = vc4_plane_state->needs_bg_fill;
4488c2ecf20Sopenharmony_ci		}
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci		dlist_next += vc4_plane_write_dlist(plane, dlist_next);
4518c2ecf20Sopenharmony_ci	}
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	writel(SCALER_CTL0_END, dlist_next);
4548c2ecf20Sopenharmony_ci	dlist_next++;
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	WARN_ON_ONCE(dlist_next - dlist_start != vc4_state->mm.size);
4578c2ecf20Sopenharmony_ci
4588c2ecf20Sopenharmony_ci	if (enable_bg_fill)
4598c2ecf20Sopenharmony_ci		/* This sets a black background color fill, as is the case
4608c2ecf20Sopenharmony_ci		 * with other DRM drivers.
4618c2ecf20Sopenharmony_ci		 */
4628c2ecf20Sopenharmony_ci		HVS_WRITE(SCALER_DISPBKGNDX(vc4_state->assigned_channel),
4638c2ecf20Sopenharmony_ci			  HVS_READ(SCALER_DISPBKGNDX(vc4_state->assigned_channel)) |
4648c2ecf20Sopenharmony_ci			  SCALER_DISPBKGND_FILL);
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ci	/* Only update DISPLIST if the CRTC was already running and is not
4678c2ecf20Sopenharmony_ci	 * being disabled.
4688c2ecf20Sopenharmony_ci	 * vc4_crtc_enable() takes care of updating the dlist just after
4698c2ecf20Sopenharmony_ci	 * re-enabling VBLANK interrupts and before enabling the engine.
4708c2ecf20Sopenharmony_ci	 * If the CRTC is being disabled, there's no point in updating this
4718c2ecf20Sopenharmony_ci	 * information.
4728c2ecf20Sopenharmony_ci	 */
4738c2ecf20Sopenharmony_ci	if (crtc->state->active && old_state->active)
4748c2ecf20Sopenharmony_ci		vc4_hvs_update_dlist(crtc);
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	if (crtc->state->color_mgmt_changed) {
4778c2ecf20Sopenharmony_ci		u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(vc4_state->assigned_channel));
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci		if (crtc->state->gamma_lut) {
4808c2ecf20Sopenharmony_ci			vc4_hvs_update_gamma_lut(crtc);
4818c2ecf20Sopenharmony_ci			dispbkgndx |= SCALER_DISPBKGND_GAMMA;
4828c2ecf20Sopenharmony_ci		} else {
4838c2ecf20Sopenharmony_ci			/* Unsetting DISPBKGND_GAMMA skips the gamma lut step
4848c2ecf20Sopenharmony_ci			 * in hardware, which is the same as a linear lut that
4858c2ecf20Sopenharmony_ci			 * DRM expects us to use in absence of a user lut.
4868c2ecf20Sopenharmony_ci			 */
4878c2ecf20Sopenharmony_ci			dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
4888c2ecf20Sopenharmony_ci		}
4898c2ecf20Sopenharmony_ci		HVS_WRITE(SCALER_DISPBKGNDX(vc4_state->assigned_channel), dispbkgndx);
4908c2ecf20Sopenharmony_ci	}
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_ci	if (debug_dump_regs) {
4938c2ecf20Sopenharmony_ci		DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc));
4948c2ecf20Sopenharmony_ci		vc4_hvs_dump_state(dev);
4958c2ecf20Sopenharmony_ci	}
4968c2ecf20Sopenharmony_ci}
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_civoid vc4_hvs_mask_underrun(struct drm_device *dev, int channel)
4998c2ecf20Sopenharmony_ci{
5008c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
5018c2ecf20Sopenharmony_ci	u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci	dispctrl &= ~SCALER_DISPCTRL_DSPEISLUR(channel);
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRL, dispctrl);
5068c2ecf20Sopenharmony_ci}
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_civoid vc4_hvs_unmask_underrun(struct drm_device *dev, int channel)
5098c2ecf20Sopenharmony_ci{
5108c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
5118c2ecf20Sopenharmony_ci	u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_ci	dispctrl |= SCALER_DISPCTRL_DSPEISLUR(channel);
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPSTAT,
5168c2ecf20Sopenharmony_ci		  SCALER_DISPSTAT_EUFLOW(channel));
5178c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRL, dispctrl);
5188c2ecf20Sopenharmony_ci}
5198c2ecf20Sopenharmony_ci
5208c2ecf20Sopenharmony_cistatic void vc4_hvs_report_underrun(struct drm_device *dev)
5218c2ecf20Sopenharmony_ci{
5228c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
5238c2ecf20Sopenharmony_ci
5248c2ecf20Sopenharmony_ci	atomic_inc(&vc4->underrun);
5258c2ecf20Sopenharmony_ci	DRM_DEV_ERROR(dev->dev, "HVS underrun\n");
5268c2ecf20Sopenharmony_ci}
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_cistatic irqreturn_t vc4_hvs_irq_handler(int irq, void *data)
5298c2ecf20Sopenharmony_ci{
5308c2ecf20Sopenharmony_ci	struct drm_device *dev = data;
5318c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(dev);
5328c2ecf20Sopenharmony_ci	irqreturn_t irqret = IRQ_NONE;
5338c2ecf20Sopenharmony_ci	int channel;
5348c2ecf20Sopenharmony_ci	u32 control;
5358c2ecf20Sopenharmony_ci	u32 status;
5368c2ecf20Sopenharmony_ci
5378c2ecf20Sopenharmony_ci	status = HVS_READ(SCALER_DISPSTAT);
5388c2ecf20Sopenharmony_ci	control = HVS_READ(SCALER_DISPCTRL);
5398c2ecf20Sopenharmony_ci
5408c2ecf20Sopenharmony_ci	for (channel = 0; channel < SCALER_CHANNELS_COUNT; channel++) {
5418c2ecf20Sopenharmony_ci		/* Interrupt masking is not always honored, so check it here. */
5428c2ecf20Sopenharmony_ci		if (status & SCALER_DISPSTAT_EUFLOW(channel) &&
5438c2ecf20Sopenharmony_ci		    control & SCALER_DISPCTRL_DSPEISLUR(channel)) {
5448c2ecf20Sopenharmony_ci			vc4_hvs_mask_underrun(dev, channel);
5458c2ecf20Sopenharmony_ci			vc4_hvs_report_underrun(dev);
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_ci			irqret = IRQ_HANDLED;
5488c2ecf20Sopenharmony_ci		}
5498c2ecf20Sopenharmony_ci	}
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_ci	/* Clear every per-channel interrupt flag. */
5528c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_IRQMASK(0) |
5538c2ecf20Sopenharmony_ci				   SCALER_DISPSTAT_IRQMASK(1) |
5548c2ecf20Sopenharmony_ci				   SCALER_DISPSTAT_IRQMASK(2));
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	return irqret;
5578c2ecf20Sopenharmony_ci}
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_cistatic int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
5608c2ecf20Sopenharmony_ci{
5618c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(dev);
5628c2ecf20Sopenharmony_ci	struct drm_device *drm = dev_get_drvdata(master);
5638c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(drm);
5648c2ecf20Sopenharmony_ci	struct vc4_hvs *hvs = NULL;
5658c2ecf20Sopenharmony_ci	int ret;
5668c2ecf20Sopenharmony_ci	u32 dispctrl;
5678c2ecf20Sopenharmony_ci	u32 reg;
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	hvs = devm_kzalloc(&pdev->dev, sizeof(*hvs), GFP_KERNEL);
5708c2ecf20Sopenharmony_ci	if (!hvs)
5718c2ecf20Sopenharmony_ci		return -ENOMEM;
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_ci	hvs->pdev = pdev;
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_ci	if (of_device_is_compatible(pdev->dev.of_node, "brcm,bcm2711-hvs"))
5768c2ecf20Sopenharmony_ci		hvs->hvs5 = true;
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_ci	hvs->regs = vc4_ioremap_regs(pdev, 0);
5798c2ecf20Sopenharmony_ci	if (IS_ERR(hvs->regs))
5808c2ecf20Sopenharmony_ci		return PTR_ERR(hvs->regs);
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_ci	hvs->regset.base = hvs->regs;
5838c2ecf20Sopenharmony_ci	hvs->regset.regs = hvs_regs;
5848c2ecf20Sopenharmony_ci	hvs->regset.nregs = ARRAY_SIZE(hvs_regs);
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_ci	if (hvs->hvs5) {
5878c2ecf20Sopenharmony_ci		hvs->core_clk = devm_clk_get(&pdev->dev, NULL);
5888c2ecf20Sopenharmony_ci		if (IS_ERR(hvs->core_clk)) {
5898c2ecf20Sopenharmony_ci			dev_err(&pdev->dev, "Couldn't get core clock\n");
5908c2ecf20Sopenharmony_ci			return PTR_ERR(hvs->core_clk);
5918c2ecf20Sopenharmony_ci		}
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci		ret = clk_prepare_enable(hvs->core_clk);
5948c2ecf20Sopenharmony_ci		if (ret) {
5958c2ecf20Sopenharmony_ci			dev_err(&pdev->dev, "Couldn't enable the core clock\n");
5968c2ecf20Sopenharmony_ci			return ret;
5978c2ecf20Sopenharmony_ci		}
5988c2ecf20Sopenharmony_ci	}
5998c2ecf20Sopenharmony_ci
6008c2ecf20Sopenharmony_ci	if (!hvs->hvs5)
6018c2ecf20Sopenharmony_ci		hvs->dlist = hvs->regs + SCALER_DLIST_START;
6028c2ecf20Sopenharmony_ci	else
6038c2ecf20Sopenharmony_ci		hvs->dlist = hvs->regs + SCALER5_DLIST_START;
6048c2ecf20Sopenharmony_ci
6058c2ecf20Sopenharmony_ci	spin_lock_init(&hvs->mm_lock);
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	/* Set up the HVS display list memory manager.  We never
6088c2ecf20Sopenharmony_ci	 * overwrite the setup from the bootloader (just 128b out of
6098c2ecf20Sopenharmony_ci	 * our 16K), since we don't want to scramble the screen when
6108c2ecf20Sopenharmony_ci	 * transitioning from the firmware's boot setup to runtime.
6118c2ecf20Sopenharmony_ci	 */
6128c2ecf20Sopenharmony_ci	drm_mm_init(&hvs->dlist_mm,
6138c2ecf20Sopenharmony_ci		    HVS_BOOTLOADER_DLIST_END,
6148c2ecf20Sopenharmony_ci		    (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END);
6158c2ecf20Sopenharmony_ci
6168c2ecf20Sopenharmony_ci	/* Set up the HVS LBM memory manager.  We could have some more
6178c2ecf20Sopenharmony_ci	 * complicated data structure that allowed reuse of LBM areas
6188c2ecf20Sopenharmony_ci	 * between planes when they don't overlap on the screen, but
6198c2ecf20Sopenharmony_ci	 * for now we just allocate globally.
6208c2ecf20Sopenharmony_ci	 */
6218c2ecf20Sopenharmony_ci	if (!hvs->hvs5)
6228c2ecf20Sopenharmony_ci		/* 48k words of 2x12-bit pixels */
6238c2ecf20Sopenharmony_ci		drm_mm_init(&hvs->lbm_mm, 0, 48 * 1024);
6248c2ecf20Sopenharmony_ci	else
6258c2ecf20Sopenharmony_ci		/* 60k words of 4x12-bit pixels */
6268c2ecf20Sopenharmony_ci		drm_mm_init(&hvs->lbm_mm, 0, 60 * 1024);
6278c2ecf20Sopenharmony_ci
6288c2ecf20Sopenharmony_ci	/* Upload filter kernels.  We only have the one for now, so we
6298c2ecf20Sopenharmony_ci	 * keep it around for the lifetime of the driver.
6308c2ecf20Sopenharmony_ci	 */
6318c2ecf20Sopenharmony_ci	ret = vc4_hvs_upload_linear_kernel(hvs,
6328c2ecf20Sopenharmony_ci					   &hvs->mitchell_netravali_filter,
6338c2ecf20Sopenharmony_ci					   mitchell_netravali_1_3_1_3_kernel);
6348c2ecf20Sopenharmony_ci	if (ret)
6358c2ecf20Sopenharmony_ci		return ret;
6368c2ecf20Sopenharmony_ci
6378c2ecf20Sopenharmony_ci	vc4->hvs = hvs;
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci	reg = HVS_READ(SCALER_DISPECTRL);
6408c2ecf20Sopenharmony_ci	reg &= ~SCALER_DISPECTRL_DSP2_MUX_MASK;
6418c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPECTRL,
6428c2ecf20Sopenharmony_ci		  reg | VC4_SET_FIELD(0, SCALER_DISPECTRL_DSP2_MUX));
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_ci	reg = HVS_READ(SCALER_DISPCTRL);
6458c2ecf20Sopenharmony_ci	reg &= ~SCALER_DISPCTRL_DSP3_MUX_MASK;
6468c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRL,
6478c2ecf20Sopenharmony_ci		  reg | VC4_SET_FIELD(3, SCALER_DISPCTRL_DSP3_MUX));
6488c2ecf20Sopenharmony_ci
6498c2ecf20Sopenharmony_ci	reg = HVS_READ(SCALER_DISPEOLN);
6508c2ecf20Sopenharmony_ci	reg &= ~SCALER_DISPEOLN_DSP4_MUX_MASK;
6518c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPEOLN,
6528c2ecf20Sopenharmony_ci		  reg | VC4_SET_FIELD(3, SCALER_DISPEOLN_DSP4_MUX));
6538c2ecf20Sopenharmony_ci
6548c2ecf20Sopenharmony_ci	reg = HVS_READ(SCALER_DISPDITHER);
6558c2ecf20Sopenharmony_ci	reg &= ~SCALER_DISPDITHER_DSP5_MUX_MASK;
6568c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPDITHER,
6578c2ecf20Sopenharmony_ci		  reg | VC4_SET_FIELD(3, SCALER_DISPDITHER_DSP5_MUX));
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci	dispctrl = HVS_READ(SCALER_DISPCTRL);
6608c2ecf20Sopenharmony_ci
6618c2ecf20Sopenharmony_ci	dispctrl |= SCALER_DISPCTRL_ENABLE;
6628c2ecf20Sopenharmony_ci	dispctrl |= SCALER_DISPCTRL_DISPEIRQ(0) |
6638c2ecf20Sopenharmony_ci		    SCALER_DISPCTRL_DISPEIRQ(1) |
6648c2ecf20Sopenharmony_ci		    SCALER_DISPCTRL_DISPEIRQ(2);
6658c2ecf20Sopenharmony_ci
6668c2ecf20Sopenharmony_ci	dispctrl &= ~(SCALER_DISPCTRL_DMAEIRQ |
6678c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_SLVWREIRQ |
6688c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_SLVRDEIRQ |
6698c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEIEOF(0) |
6708c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEIEOF(1) |
6718c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEIEOF(2) |
6728c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEIEOLN(0) |
6738c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEIEOLN(1) |
6748c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEIEOLN(2) |
6758c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEISLUR(0) |
6768c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEISLUR(1) |
6778c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_DSPEISLUR(2) |
6788c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_SCLEIRQ);
6798c2ecf20Sopenharmony_ci
6808c2ecf20Sopenharmony_ci	/* Set AXI panic mode.
6818c2ecf20Sopenharmony_ci	 * VC4 panics when < 2 lines in FIFO.
6828c2ecf20Sopenharmony_ci	 * VC5 panics when less than 1 line in the FIFO.
6838c2ecf20Sopenharmony_ci	 */
6848c2ecf20Sopenharmony_ci	dispctrl &= ~(SCALER_DISPCTRL_PANIC0_MASK |
6858c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_PANIC1_MASK |
6868c2ecf20Sopenharmony_ci		      SCALER_DISPCTRL_PANIC2_MASK);
6878c2ecf20Sopenharmony_ci	dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC0);
6888c2ecf20Sopenharmony_ci	dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC1);
6898c2ecf20Sopenharmony_ci	dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC2);
6908c2ecf20Sopenharmony_ci
6918c2ecf20Sopenharmony_ci	HVS_WRITE(SCALER_DISPCTRL, dispctrl);
6928c2ecf20Sopenharmony_ci
6938c2ecf20Sopenharmony_ci	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
6948c2ecf20Sopenharmony_ci			       vc4_hvs_irq_handler, 0, "vc4 hvs", drm);
6958c2ecf20Sopenharmony_ci	if (ret)
6968c2ecf20Sopenharmony_ci		return ret;
6978c2ecf20Sopenharmony_ci
6988c2ecf20Sopenharmony_ci	vc4_debugfs_add_regset32(drm, "hvs_regs", &hvs->regset);
6998c2ecf20Sopenharmony_ci	vc4_debugfs_add_file(drm, "hvs_underrun", vc4_hvs_debugfs_underrun,
7008c2ecf20Sopenharmony_ci			     NULL);
7018c2ecf20Sopenharmony_ci
7028c2ecf20Sopenharmony_ci	return 0;
7038c2ecf20Sopenharmony_ci}
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_cistatic void vc4_hvs_unbind(struct device *dev, struct device *master,
7068c2ecf20Sopenharmony_ci			   void *data)
7078c2ecf20Sopenharmony_ci{
7088c2ecf20Sopenharmony_ci	struct drm_device *drm = dev_get_drvdata(master);
7098c2ecf20Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(drm);
7108c2ecf20Sopenharmony_ci	struct vc4_hvs *hvs = vc4->hvs;
7118c2ecf20Sopenharmony_ci
7128c2ecf20Sopenharmony_ci	if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter))
7138c2ecf20Sopenharmony_ci		drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter);
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_ci	drm_mm_takedown(&vc4->hvs->dlist_mm);
7168c2ecf20Sopenharmony_ci	drm_mm_takedown(&vc4->hvs->lbm_mm);
7178c2ecf20Sopenharmony_ci
7188c2ecf20Sopenharmony_ci	clk_disable_unprepare(hvs->core_clk);
7198c2ecf20Sopenharmony_ci
7208c2ecf20Sopenharmony_ci	vc4->hvs = NULL;
7218c2ecf20Sopenharmony_ci}
7228c2ecf20Sopenharmony_ci
7238c2ecf20Sopenharmony_cistatic const struct component_ops vc4_hvs_ops = {
7248c2ecf20Sopenharmony_ci	.bind   = vc4_hvs_bind,
7258c2ecf20Sopenharmony_ci	.unbind = vc4_hvs_unbind,
7268c2ecf20Sopenharmony_ci};
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_cistatic int vc4_hvs_dev_probe(struct platform_device *pdev)
7298c2ecf20Sopenharmony_ci{
7308c2ecf20Sopenharmony_ci	return component_add(&pdev->dev, &vc4_hvs_ops);
7318c2ecf20Sopenharmony_ci}
7328c2ecf20Sopenharmony_ci
7338c2ecf20Sopenharmony_cistatic int vc4_hvs_dev_remove(struct platform_device *pdev)
7348c2ecf20Sopenharmony_ci{
7358c2ecf20Sopenharmony_ci	component_del(&pdev->dev, &vc4_hvs_ops);
7368c2ecf20Sopenharmony_ci	return 0;
7378c2ecf20Sopenharmony_ci}
7388c2ecf20Sopenharmony_ci
7398c2ecf20Sopenharmony_cistatic const struct of_device_id vc4_hvs_dt_match[] = {
7408c2ecf20Sopenharmony_ci	{ .compatible = "brcm,bcm2711-hvs" },
7418c2ecf20Sopenharmony_ci	{ .compatible = "brcm,bcm2835-hvs" },
7428c2ecf20Sopenharmony_ci	{}
7438c2ecf20Sopenharmony_ci};
7448c2ecf20Sopenharmony_ci
7458c2ecf20Sopenharmony_cistruct platform_driver vc4_hvs_driver = {
7468c2ecf20Sopenharmony_ci	.probe = vc4_hvs_dev_probe,
7478c2ecf20Sopenharmony_ci	.remove = vc4_hvs_dev_remove,
7488c2ecf20Sopenharmony_ci	.driver = {
7498c2ecf20Sopenharmony_ci		.name = "vc4_hvs",
7508c2ecf20Sopenharmony_ci		.of_match_table = vc4_hvs_dt_match,
7518c2ecf20Sopenharmony_ci	},
7528c2ecf20Sopenharmony_ci};
753