162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2015 Broadcom
462306a36Sopenharmony_ci * Copyright (c) 2014 The Linux Foundation. All rights reserved.
562306a36Sopenharmony_ci * Copyright (C) 2013 Red Hat
662306a36Sopenharmony_ci * Author: Rob Clark <robdclark@gmail.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci/**
1062306a36Sopenharmony_ci * DOC: VC4 Falcon HDMI module
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * The HDMI core has a state machine and a PHY.  On BCM2835, most of
1362306a36Sopenharmony_ci * the unit operates off of the HSM clock from CPRMAN.  It also
1462306a36Sopenharmony_ci * internally uses the PLLH_PIX clock for the PHY.
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * HDMI infoframes are kept within a small packet ram, where each
1762306a36Sopenharmony_ci * packet can be individually enabled for including in a frame.
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * HDMI audio is implemented entirely within the HDMI IP block.  A
2062306a36Sopenharmony_ci * register in the HDMI encoder takes SPDIF frames from the DMA engine
2162306a36Sopenharmony_ci * and transfers them over an internal MAI (multi-channel audio
2262306a36Sopenharmony_ci * interconnect) bus to the encoder side for insertion into the video
2362306a36Sopenharmony_ci * blank regions.
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * The driver's HDMI encoder does not yet support power management.
2662306a36Sopenharmony_ci * The HDMI encoder's power domain and the HSM/pixel clocks are kept
2762306a36Sopenharmony_ci * continuously running, and only the HDMI logic and packet ram are
2862306a36Sopenharmony_ci * powered off/on at disable/enable time.
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * The driver does not yet support CEC control, though the HDMI
3162306a36Sopenharmony_ci * encoder block has CEC support.
3262306a36Sopenharmony_ci */
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#include <drm/display/drm_hdmi_helper.h>
3562306a36Sopenharmony_ci#include <drm/display/drm_scdc_helper.h>
3662306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
3762306a36Sopenharmony_ci#include <drm/drm_drv.h>
3862306a36Sopenharmony_ci#include <drm/drm_probe_helper.h>
3962306a36Sopenharmony_ci#include <drm/drm_simple_kms_helper.h>
4062306a36Sopenharmony_ci#include <linux/clk.h>
4162306a36Sopenharmony_ci#include <linux/component.h>
4262306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
4362306a36Sopenharmony_ci#include <linux/i2c.h>
4462306a36Sopenharmony_ci#include <linux/of.h>
4562306a36Sopenharmony_ci#include <linux/of_address.h>
4662306a36Sopenharmony_ci#include <linux/pm_runtime.h>
4762306a36Sopenharmony_ci#include <linux/rational.h>
4862306a36Sopenharmony_ci#include <linux/reset.h>
4962306a36Sopenharmony_ci#include <sound/dmaengine_pcm.h>
5062306a36Sopenharmony_ci#include <sound/hdmi-codec.h>
5162306a36Sopenharmony_ci#include <sound/pcm_drm_eld.h>
5262306a36Sopenharmony_ci#include <sound/pcm_params.h>
5362306a36Sopenharmony_ci#include <sound/soc.h>
5462306a36Sopenharmony_ci#include "media/cec.h"
5562306a36Sopenharmony_ci#include "vc4_drv.h"
5662306a36Sopenharmony_ci#include "vc4_hdmi.h"
5762306a36Sopenharmony_ci#include "vc4_hdmi_regs.h"
5862306a36Sopenharmony_ci#include "vc4_regs.h"
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci#define VC5_HDMI_HORZA_HFP_SHIFT		16
6162306a36Sopenharmony_ci#define VC5_HDMI_HORZA_HFP_MASK			VC4_MASK(28, 16)
6262306a36Sopenharmony_ci#define VC5_HDMI_HORZA_VPOS			BIT(15)
6362306a36Sopenharmony_ci#define VC5_HDMI_HORZA_HPOS			BIT(14)
6462306a36Sopenharmony_ci#define VC5_HDMI_HORZA_HAP_SHIFT		0
6562306a36Sopenharmony_ci#define VC5_HDMI_HORZA_HAP_MASK			VC4_MASK(13, 0)
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci#define VC5_HDMI_HORZB_HBP_SHIFT		16
6862306a36Sopenharmony_ci#define VC5_HDMI_HORZB_HBP_MASK			VC4_MASK(26, 16)
6962306a36Sopenharmony_ci#define VC5_HDMI_HORZB_HSP_SHIFT		0
7062306a36Sopenharmony_ci#define VC5_HDMI_HORZB_HSP_MASK			VC4_MASK(10, 0)
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci#define VC5_HDMI_VERTA_VSP_SHIFT		24
7362306a36Sopenharmony_ci#define VC5_HDMI_VERTA_VSP_MASK			VC4_MASK(28, 24)
7462306a36Sopenharmony_ci#define VC5_HDMI_VERTA_VFP_SHIFT		16
7562306a36Sopenharmony_ci#define VC5_HDMI_VERTA_VFP_MASK			VC4_MASK(22, 16)
7662306a36Sopenharmony_ci#define VC5_HDMI_VERTA_VAL_SHIFT		0
7762306a36Sopenharmony_ci#define VC5_HDMI_VERTA_VAL_MASK			VC4_MASK(12, 0)
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci#define VC5_HDMI_VERTB_VSPO_SHIFT		16
8062306a36Sopenharmony_ci#define VC5_HDMI_VERTB_VSPO_MASK		VC4_MASK(29, 16)
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci#define VC4_HDMI_MISC_CONTROL_PIXEL_REP_SHIFT	0
8362306a36Sopenharmony_ci#define VC4_HDMI_MISC_CONTROL_PIXEL_REP_MASK	VC4_MASK(3, 0)
8462306a36Sopenharmony_ci#define VC5_HDMI_MISC_CONTROL_PIXEL_REP_SHIFT	0
8562306a36Sopenharmony_ci#define VC5_HDMI_MISC_CONTROL_PIXEL_REP_MASK	VC4_MASK(3, 0)
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci#define VC5_HDMI_SCRAMBLER_CTL_ENABLE		BIT(0)
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci#define VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_SHIFT	8
9062306a36Sopenharmony_ci#define VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_MASK	VC4_MASK(10, 8)
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci#define VC5_HDMI_DEEP_COLOR_CONFIG_1_COLOR_DEPTH_SHIFT		0
9362306a36Sopenharmony_ci#define VC5_HDMI_DEEP_COLOR_CONFIG_1_COLOR_DEPTH_MASK		VC4_MASK(3, 0)
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci#define VC5_HDMI_GCP_CONFIG_GCP_ENABLE		BIT(31)
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci#define VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_1_SHIFT	8
9862306a36Sopenharmony_ci#define VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_1_MASK	VC4_MASK(15, 8)
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci#define VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_0_MASK	VC4_MASK(7, 0)
10162306a36Sopenharmony_ci#define VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_0_SET_AVMUTE	BIT(0)
10262306a36Sopenharmony_ci#define VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_0_CLEAR_AVMUTE	BIT(4)
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci# define VC4_HD_M_SW_RST			BIT(2)
10562306a36Sopenharmony_ci# define VC4_HD_M_ENABLE			BIT(0)
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci#define HSM_MIN_CLOCK_FREQ	120000000
10862306a36Sopenharmony_ci#define CEC_CLOCK_FREQ 40000
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci#define HDMI_14_MAX_TMDS_CLK   (340 * 1000 * 1000)
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic const char * const output_format_str[] = {
11362306a36Sopenharmony_ci	[VC4_HDMI_OUTPUT_RGB]		= "RGB",
11462306a36Sopenharmony_ci	[VC4_HDMI_OUTPUT_YUV420]	= "YUV 4:2:0",
11562306a36Sopenharmony_ci	[VC4_HDMI_OUTPUT_YUV422]	= "YUV 4:2:2",
11662306a36Sopenharmony_ci	[VC4_HDMI_OUTPUT_YUV444]	= "YUV 4:4:4",
11762306a36Sopenharmony_ci};
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic const char *vc4_hdmi_output_fmt_str(enum vc4_hdmi_output_format fmt)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	if (fmt >= ARRAY_SIZE(output_format_str))
12262306a36Sopenharmony_ci		return "invalid";
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return output_format_str[fmt];
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic unsigned long long
12862306a36Sopenharmony_civc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode,
12962306a36Sopenharmony_ci				    unsigned int bpc, enum vc4_hdmi_output_format fmt);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic bool vc4_hdmi_supports_scrambling(struct vc4_hdmi *vc4_hdmi)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	struct drm_display_info *display = &vc4_hdmi->connector.display_info;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	if (!display->is_hdmi)
13862306a36Sopenharmony_ci		return false;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (!display->hdmi.scdc.supported ||
14162306a36Sopenharmony_ci	    !display->hdmi.scdc.scrambling.supported)
14262306a36Sopenharmony_ci		return false;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	return true;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode,
14862306a36Sopenharmony_ci					   unsigned int bpc,
14962306a36Sopenharmony_ci					   enum vc4_hdmi_output_format fmt)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	unsigned long long clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	return clock > HDMI_14_MAX_TMDS_CLK;
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic bool vc4_hdmi_is_full_range(struct vc4_hdmi *vc4_hdmi,
15762306a36Sopenharmony_ci				   struct vc4_hdmi_connector_state *vc4_state)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
16062306a36Sopenharmony_ci	struct drm_display_info *display = &vc4_hdmi->connector.display_info;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if (vc4_state->broadcast_rgb == VC4_HDMI_BROADCAST_RGB_LIMITED)
16362306a36Sopenharmony_ci		return false;
16462306a36Sopenharmony_ci	else if (vc4_state->broadcast_rgb == VC4_HDMI_BROADCAST_RGB_FULL)
16562306a36Sopenharmony_ci		return true;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return !display->is_hdmi ||
16862306a36Sopenharmony_ci		drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_FULL;
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cistatic int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	struct drm_debugfs_entry *entry = m->private;
17462306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = entry->file.data;
17562306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
17662306a36Sopenharmony_ci	struct drm_printer p = drm_seq_file_printer(m);
17762306a36Sopenharmony_ci	int idx;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
18062306a36Sopenharmony_ci		return -ENODEV;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	drm_print_regset32(&p, &vc4_hdmi->hdmi_regset);
18362306a36Sopenharmony_ci	drm_print_regset32(&p, &vc4_hdmi->hd_regset);
18462306a36Sopenharmony_ci	drm_print_regset32(&p, &vc4_hdmi->cec_regset);
18562306a36Sopenharmony_ci	drm_print_regset32(&p, &vc4_hdmi->csc_regset);
18662306a36Sopenharmony_ci	drm_print_regset32(&p, &vc4_hdmi->dvp_regset);
18762306a36Sopenharmony_ci	drm_print_regset32(&p, &vc4_hdmi->phy_regset);
18862306a36Sopenharmony_ci	drm_print_regset32(&p, &vc4_hdmi->ram_regset);
18962306a36Sopenharmony_ci	drm_print_regset32(&p, &vc4_hdmi->rm_regset);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	drm_dev_exit(idx);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	return 0;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
19962306a36Sopenharmony_ci	unsigned long flags;
20062306a36Sopenharmony_ci	int idx;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	/*
20362306a36Sopenharmony_ci	 * We can be called by our bind callback, when the
20462306a36Sopenharmony_ci	 * connector->dev pointer might not be initialised yet.
20562306a36Sopenharmony_ci	 */
20662306a36Sopenharmony_ci	if (drm && !drm_dev_enter(drm, &idx))
20762306a36Sopenharmony_ci		return;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_M_CTL, VC4_HD_M_SW_RST);
21262306a36Sopenharmony_ci	udelay(1);
21362306a36Sopenharmony_ci	HDMI_WRITE(HDMI_M_CTL, 0);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_M_CTL, VC4_HD_M_ENABLE);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_SW_RESET_CONTROL,
21862306a36Sopenharmony_ci		   VC4_HDMI_SW_RESET_HDMI |
21962306a36Sopenharmony_ci		   VC4_HDMI_SW_RESET_FORMAT_DETECT);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_SW_RESET_CONTROL, 0);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (drm)
22662306a36Sopenharmony_ci		drm_dev_exit(idx);
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
23262306a36Sopenharmony_ci	unsigned long flags;
23362306a36Sopenharmony_ci	int idx;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	/*
23662306a36Sopenharmony_ci	 * We can be called by our bind callback, when the
23762306a36Sopenharmony_ci	 * connector->dev pointer might not be initialised yet.
23862306a36Sopenharmony_ci	 */
23962306a36Sopenharmony_ci	if (drm && !drm_dev_enter(drm, &idx))
24062306a36Sopenharmony_ci		return;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	reset_control_reset(vc4_hdmi->reset);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_DVP_CTL, 0);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CLOCK_STOP,
24962306a36Sopenharmony_ci		   HDMI_READ(HDMI_CLOCK_STOP) | VC4_DVP_HT_CLOCK_STOP_PIXEL);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	if (drm)
25462306a36Sopenharmony_ci		drm_dev_exit(idx);
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci#ifdef CONFIG_DRM_VC4_HDMI_CEC
25862306a36Sopenharmony_cistatic void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
26162306a36Sopenharmony_ci	unsigned long cec_rate;
26262306a36Sopenharmony_ci	unsigned long flags;
26362306a36Sopenharmony_ci	u16 clk_cnt;
26462306a36Sopenharmony_ci	u32 value;
26562306a36Sopenharmony_ci	int idx;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	/*
26862306a36Sopenharmony_ci	 * This function is called by our runtime_resume implementation
26962306a36Sopenharmony_ci	 * and thus at bind time, when we haven't registered our
27062306a36Sopenharmony_ci	 * connector yet and thus don't have a pointer to the DRM
27162306a36Sopenharmony_ci	 * device.
27262306a36Sopenharmony_ci	 */
27362306a36Sopenharmony_ci	if (drm && !drm_dev_enter(drm, &idx))
27462306a36Sopenharmony_ci		return;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	cec_rate = clk_get_rate(vc4_hdmi->cec_clock);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	value = HDMI_READ(HDMI_CEC_CNTRL_1);
28162306a36Sopenharmony_ci	value &= ~VC4_HDMI_CEC_DIV_CLK_CNT_MASK;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	/*
28462306a36Sopenharmony_ci	 * Set the clock divider: the hsm_clock rate and this divider
28562306a36Sopenharmony_ci	 * setting will give a 40 kHz CEC clock.
28662306a36Sopenharmony_ci	 */
28762306a36Sopenharmony_ci	clk_cnt = cec_rate / CEC_CLOCK_FREQ;
28862306a36Sopenharmony_ci	value |= clk_cnt << VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT;
28962306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_1, value);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	if (drm)
29462306a36Sopenharmony_ci		drm_dev_exit(idx);
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci#else
29762306a36Sopenharmony_cistatic void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) {}
29862306a36Sopenharmony_ci#endif
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_cistatic int reset_pipe(struct drm_crtc *crtc,
30162306a36Sopenharmony_ci			struct drm_modeset_acquire_ctx *ctx)
30262306a36Sopenharmony_ci{
30362306a36Sopenharmony_ci	struct drm_atomic_state *state;
30462306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state;
30562306a36Sopenharmony_ci	int ret;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	state = drm_atomic_state_alloc(crtc->dev);
30862306a36Sopenharmony_ci	if (!state)
30962306a36Sopenharmony_ci		return -ENOMEM;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	state->acquire_ctx = ctx;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	crtc_state = drm_atomic_get_crtc_state(state, crtc);
31462306a36Sopenharmony_ci	if (IS_ERR(crtc_state)) {
31562306a36Sopenharmony_ci		ret = PTR_ERR(crtc_state);
31662306a36Sopenharmony_ci		goto out;
31762306a36Sopenharmony_ci	}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	crtc_state->connectors_changed = true;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	ret = drm_atomic_commit(state);
32262306a36Sopenharmony_ciout:
32362306a36Sopenharmony_ci	drm_atomic_state_put(state);
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	return ret;
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_cistatic int vc4_hdmi_reset_link(struct drm_connector *connector,
32962306a36Sopenharmony_ci			       struct drm_modeset_acquire_ctx *ctx)
33062306a36Sopenharmony_ci{
33162306a36Sopenharmony_ci	struct drm_device *drm;
33262306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi;
33362306a36Sopenharmony_ci	struct drm_connector_state *conn_state;
33462306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state;
33562306a36Sopenharmony_ci	struct drm_crtc *crtc;
33662306a36Sopenharmony_ci	bool scrambling_needed;
33762306a36Sopenharmony_ci	u8 config;
33862306a36Sopenharmony_ci	int ret;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	if (!connector)
34162306a36Sopenharmony_ci		return 0;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	drm = connector->dev;
34462306a36Sopenharmony_ci	ret = drm_modeset_lock(&drm->mode_config.connection_mutex, ctx);
34562306a36Sopenharmony_ci	if (ret)
34662306a36Sopenharmony_ci		return ret;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	conn_state = connector->state;
34962306a36Sopenharmony_ci	crtc = conn_state->crtc;
35062306a36Sopenharmony_ci	if (!crtc)
35162306a36Sopenharmony_ci		return 0;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	ret = drm_modeset_lock(&crtc->mutex, ctx);
35462306a36Sopenharmony_ci	if (ret)
35562306a36Sopenharmony_ci		return ret;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	crtc_state = crtc->state;
35862306a36Sopenharmony_ci	if (!crtc_state->active)
35962306a36Sopenharmony_ci		return 0;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	vc4_hdmi = connector_to_vc4_hdmi(connector);
36262306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	if (!vc4_hdmi_supports_scrambling(vc4_hdmi)) {
36562306a36Sopenharmony_ci		mutex_unlock(&vc4_hdmi->mutex);
36662306a36Sopenharmony_ci		return 0;
36762306a36Sopenharmony_ci	}
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	scrambling_needed = vc4_hdmi_mode_needs_scrambling(&vc4_hdmi->saved_adjusted_mode,
37062306a36Sopenharmony_ci							   vc4_hdmi->output_bpc,
37162306a36Sopenharmony_ci							   vc4_hdmi->output_format);
37262306a36Sopenharmony_ci	if (!scrambling_needed) {
37362306a36Sopenharmony_ci		mutex_unlock(&vc4_hdmi->mutex);
37462306a36Sopenharmony_ci		return 0;
37562306a36Sopenharmony_ci	}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	if (conn_state->commit &&
37862306a36Sopenharmony_ci	    !try_wait_for_completion(&conn_state->commit->hw_done)) {
37962306a36Sopenharmony_ci		mutex_unlock(&vc4_hdmi->mutex);
38062306a36Sopenharmony_ci		return 0;
38162306a36Sopenharmony_ci	}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	ret = drm_scdc_readb(connector->ddc, SCDC_TMDS_CONFIG, &config);
38462306a36Sopenharmony_ci	if (ret < 0) {
38562306a36Sopenharmony_ci		drm_err(drm, "Failed to read TMDS config: %d\n", ret);
38662306a36Sopenharmony_ci		mutex_unlock(&vc4_hdmi->mutex);
38762306a36Sopenharmony_ci		return 0;
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	if (!!(config & SCDC_SCRAMBLING_ENABLE) == scrambling_needed) {
39162306a36Sopenharmony_ci		mutex_unlock(&vc4_hdmi->mutex);
39262306a36Sopenharmony_ci		return 0;
39362306a36Sopenharmony_ci	}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	/*
39862306a36Sopenharmony_ci	 * HDMI 2.0 says that one should not send scrambled data
39962306a36Sopenharmony_ci	 * prior to configuring the sink scrambling, and that
40062306a36Sopenharmony_ci	 * TMDS clock/data transmission should be suspended when
40162306a36Sopenharmony_ci	 * changing the TMDS clock rate in the sink. So let's
40262306a36Sopenharmony_ci	 * just do a full modeset here, even though some sinks
40362306a36Sopenharmony_ci	 * would be perfectly happy if were to just reconfigure
40462306a36Sopenharmony_ci	 * the SCDC settings on the fly.
40562306a36Sopenharmony_ci	 */
40662306a36Sopenharmony_ci	return reset_pipe(crtc, ctx);
40762306a36Sopenharmony_ci}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_cistatic void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi,
41062306a36Sopenharmony_ci				    struct drm_modeset_acquire_ctx *ctx,
41162306a36Sopenharmony_ci				    enum drm_connector_status status)
41262306a36Sopenharmony_ci{
41362306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
41462306a36Sopenharmony_ci	struct edid *edid;
41562306a36Sopenharmony_ci	int ret;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	/*
41862306a36Sopenharmony_ci	 * NOTE: This function should really be called with
41962306a36Sopenharmony_ci	 * vc4_hdmi->mutex held, but doing so results in reentrancy
42062306a36Sopenharmony_ci	 * issues since cec_s_phys_addr_from_edid might call
42162306a36Sopenharmony_ci	 * .adap_enable, which leads to that funtion being called with
42262306a36Sopenharmony_ci	 * our mutex held.
42362306a36Sopenharmony_ci	 *
42462306a36Sopenharmony_ci	 * A similar situation occurs with vc4_hdmi_reset_link() that
42562306a36Sopenharmony_ci	 * will call into our KMS hooks if the scrambling was enabled.
42662306a36Sopenharmony_ci	 *
42762306a36Sopenharmony_ci	 * Concurrency isn't an issue at the moment since we don't share
42862306a36Sopenharmony_ci	 * any state with any of the other frameworks so we can ignore
42962306a36Sopenharmony_ci	 * the lock for now.
43062306a36Sopenharmony_ci	 */
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	if (status == connector_status_disconnected) {
43362306a36Sopenharmony_ci		cec_phys_addr_invalidate(vc4_hdmi->cec_adap);
43462306a36Sopenharmony_ci		return;
43562306a36Sopenharmony_ci	}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	edid = drm_get_edid(connector, vc4_hdmi->ddc);
43862306a36Sopenharmony_ci	if (!edid)
43962306a36Sopenharmony_ci		return;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	cec_s_phys_addr_from_edid(vc4_hdmi->cec_adap, edid);
44262306a36Sopenharmony_ci	kfree(edid);
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	for (;;) {
44562306a36Sopenharmony_ci		ret = vc4_hdmi_reset_link(connector, ctx);
44662306a36Sopenharmony_ci		if (ret == -EDEADLK) {
44762306a36Sopenharmony_ci			drm_modeset_backoff(ctx);
44862306a36Sopenharmony_ci			continue;
44962306a36Sopenharmony_ci		}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci		break;
45262306a36Sopenharmony_ci	}
45362306a36Sopenharmony_ci}
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_cistatic int vc4_hdmi_connector_detect_ctx(struct drm_connector *connector,
45662306a36Sopenharmony_ci					 struct drm_modeset_acquire_ctx *ctx,
45762306a36Sopenharmony_ci					 bool force)
45862306a36Sopenharmony_ci{
45962306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
46062306a36Sopenharmony_ci	enum drm_connector_status status = connector_status_disconnected;
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	/*
46362306a36Sopenharmony_ci	 * NOTE: This function should really take vc4_hdmi->mutex, but
46462306a36Sopenharmony_ci	 * doing so results in reentrancy issues since
46562306a36Sopenharmony_ci	 * vc4_hdmi_handle_hotplug() can call into other functions that
46662306a36Sopenharmony_ci	 * would take the mutex while it's held here.
46762306a36Sopenharmony_ci	 *
46862306a36Sopenharmony_ci	 * Concurrency isn't an issue at the moment since we don't share
46962306a36Sopenharmony_ci	 * any state with any of the other frameworks so we can ignore
47062306a36Sopenharmony_ci	 * the lock for now.
47162306a36Sopenharmony_ci	 */
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	WARN_ON(pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev));
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	if (vc4_hdmi->hpd_gpio) {
47662306a36Sopenharmony_ci		if (gpiod_get_value_cansleep(vc4_hdmi->hpd_gpio))
47762306a36Sopenharmony_ci			status = connector_status_connected;
47862306a36Sopenharmony_ci	} else {
47962306a36Sopenharmony_ci		if (vc4_hdmi->variant->hp_detect &&
48062306a36Sopenharmony_ci		    vc4_hdmi->variant->hp_detect(vc4_hdmi))
48162306a36Sopenharmony_ci			status = connector_status_connected;
48262306a36Sopenharmony_ci	}
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	vc4_hdmi_handle_hotplug(vc4_hdmi, ctx, status);
48562306a36Sopenharmony_ci	pm_runtime_put(&vc4_hdmi->pdev->dev);
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	return status;
48862306a36Sopenharmony_ci}
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_cistatic int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
49162306a36Sopenharmony_ci{
49262306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
49362306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(connector->dev);
49462306a36Sopenharmony_ci	int ret = 0;
49562306a36Sopenharmony_ci	struct edid *edid;
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	/*
49862306a36Sopenharmony_ci	 * NOTE: This function should really take vc4_hdmi->mutex, but
49962306a36Sopenharmony_ci	 * doing so results in reentrancy issues since
50062306a36Sopenharmony_ci	 * cec_s_phys_addr_from_edid might call .adap_enable, which
50162306a36Sopenharmony_ci	 * leads to that funtion being called with our mutex held.
50262306a36Sopenharmony_ci	 *
50362306a36Sopenharmony_ci	 * Concurrency isn't an issue at the moment since we don't share
50462306a36Sopenharmony_ci	 * any state with any of the other frameworks so we can ignore
50562306a36Sopenharmony_ci	 * the lock for now.
50662306a36Sopenharmony_ci	 */
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	edid = drm_get_edid(connector, vc4_hdmi->ddc);
50962306a36Sopenharmony_ci	cec_s_phys_addr_from_edid(vc4_hdmi->cec_adap, edid);
51062306a36Sopenharmony_ci	if (!edid)
51162306a36Sopenharmony_ci		return -ENODEV;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	drm_connector_update_edid_property(connector, edid);
51462306a36Sopenharmony_ci	ret = drm_add_edid_modes(connector, edid);
51562306a36Sopenharmony_ci	kfree(edid);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	if (!vc4->hvs->vc5_hdmi_enable_hdmi_20) {
51862306a36Sopenharmony_ci		struct drm_device *drm = connector->dev;
51962306a36Sopenharmony_ci		const struct drm_display_mode *mode;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci		list_for_each_entry(mode, &connector->probed_modes, head) {
52262306a36Sopenharmony_ci			if (vc4_hdmi_mode_needs_scrambling(mode, 8, VC4_HDMI_OUTPUT_RGB)) {
52362306a36Sopenharmony_ci				drm_warn_once(drm, "The core clock cannot reach frequencies high enough to support 4k @ 60Hz.");
52462306a36Sopenharmony_ci				drm_warn_once(drm, "Please change your config.txt file to add hdmi_enable_4kp60.");
52562306a36Sopenharmony_ci			}
52662306a36Sopenharmony_ci		}
52762306a36Sopenharmony_ci	}
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	return ret;
53062306a36Sopenharmony_ci}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_cistatic int vc4_hdmi_connector_atomic_check(struct drm_connector *connector,
53362306a36Sopenharmony_ci					   struct drm_atomic_state *state)
53462306a36Sopenharmony_ci{
53562306a36Sopenharmony_ci	struct drm_connector_state *old_state =
53662306a36Sopenharmony_ci		drm_atomic_get_old_connector_state(state, connector);
53762306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *old_vc4_state =
53862306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(old_state);
53962306a36Sopenharmony_ci	struct drm_connector_state *new_state =
54062306a36Sopenharmony_ci		drm_atomic_get_new_connector_state(state, connector);
54162306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *new_vc4_state =
54262306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(new_state);
54362306a36Sopenharmony_ci	struct drm_crtc *crtc = new_state->crtc;
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	if (!crtc)
54662306a36Sopenharmony_ci		return 0;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	if (old_state->tv.margins.left != new_state->tv.margins.left ||
54962306a36Sopenharmony_ci	    old_state->tv.margins.right != new_state->tv.margins.right ||
55062306a36Sopenharmony_ci	    old_state->tv.margins.top != new_state->tv.margins.top ||
55162306a36Sopenharmony_ci	    old_state->tv.margins.bottom != new_state->tv.margins.bottom) {
55262306a36Sopenharmony_ci		struct drm_crtc_state *crtc_state;
55362306a36Sopenharmony_ci		int ret;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci		crtc_state = drm_atomic_get_crtc_state(state, crtc);
55662306a36Sopenharmony_ci		if (IS_ERR(crtc_state))
55762306a36Sopenharmony_ci			return PTR_ERR(crtc_state);
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci		/*
56062306a36Sopenharmony_ci		 * Strictly speaking, we should be calling
56162306a36Sopenharmony_ci		 * drm_atomic_helper_check_planes() after our call to
56262306a36Sopenharmony_ci		 * drm_atomic_add_affected_planes(). However, the
56362306a36Sopenharmony_ci		 * connector atomic_check is called as part of
56462306a36Sopenharmony_ci		 * drm_atomic_helper_check_modeset() that already
56562306a36Sopenharmony_ci		 * happens before a call to
56662306a36Sopenharmony_ci		 * drm_atomic_helper_check_planes() in
56762306a36Sopenharmony_ci		 * drm_atomic_helper_check().
56862306a36Sopenharmony_ci		 */
56962306a36Sopenharmony_ci		ret = drm_atomic_add_affected_planes(state, crtc);
57062306a36Sopenharmony_ci		if (ret)
57162306a36Sopenharmony_ci			return ret;
57262306a36Sopenharmony_ci	}
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	if (old_state->colorspace != new_state->colorspace ||
57562306a36Sopenharmony_ci	    old_vc4_state->broadcast_rgb != new_vc4_state->broadcast_rgb ||
57662306a36Sopenharmony_ci	    !drm_connector_atomic_hdr_metadata_equal(old_state, new_state)) {
57762306a36Sopenharmony_ci		struct drm_crtc_state *crtc_state;
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci		crtc_state = drm_atomic_get_crtc_state(state, crtc);
58062306a36Sopenharmony_ci		if (IS_ERR(crtc_state))
58162306a36Sopenharmony_ci			return PTR_ERR(crtc_state);
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci		crtc_state->mode_changed = true;
58462306a36Sopenharmony_ci	}
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	return 0;
58762306a36Sopenharmony_ci}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_cistatic int vc4_hdmi_connector_get_property(struct drm_connector *connector,
59062306a36Sopenharmony_ci					   const struct drm_connector_state *state,
59162306a36Sopenharmony_ci					   struct drm_property *property,
59262306a36Sopenharmony_ci					   uint64_t *val)
59362306a36Sopenharmony_ci{
59462306a36Sopenharmony_ci	struct drm_device *drm = connector->dev;
59562306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi =
59662306a36Sopenharmony_ci		connector_to_vc4_hdmi(connector);
59762306a36Sopenharmony_ci	const struct vc4_hdmi_connector_state *vc4_conn_state =
59862306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(state);
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	if (property == vc4_hdmi->broadcast_rgb_property) {
60162306a36Sopenharmony_ci		*val = vc4_conn_state->broadcast_rgb;
60262306a36Sopenharmony_ci	} else {
60362306a36Sopenharmony_ci		drm_dbg(drm, "Unknown property [PROP:%d:%s]\n",
60462306a36Sopenharmony_ci			property->base.id, property->name);
60562306a36Sopenharmony_ci		return -EINVAL;
60662306a36Sopenharmony_ci	}
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	return 0;
60962306a36Sopenharmony_ci}
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_cistatic int vc4_hdmi_connector_set_property(struct drm_connector *connector,
61262306a36Sopenharmony_ci					   struct drm_connector_state *state,
61362306a36Sopenharmony_ci					   struct drm_property *property,
61462306a36Sopenharmony_ci					   uint64_t val)
61562306a36Sopenharmony_ci{
61662306a36Sopenharmony_ci	struct drm_device *drm = connector->dev;
61762306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi =
61862306a36Sopenharmony_ci		connector_to_vc4_hdmi(connector);
61962306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *vc4_conn_state =
62062306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(state);
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	if (property == vc4_hdmi->broadcast_rgb_property) {
62362306a36Sopenharmony_ci		vc4_conn_state->broadcast_rgb = val;
62462306a36Sopenharmony_ci		return 0;
62562306a36Sopenharmony_ci	}
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	drm_dbg(drm, "Unknown property [PROP:%d:%s]\n",
62862306a36Sopenharmony_ci		property->base.id, property->name);
62962306a36Sopenharmony_ci	return -EINVAL;
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic void vc4_hdmi_connector_reset(struct drm_connector *connector)
63362306a36Sopenharmony_ci{
63462306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *old_state =
63562306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(connector->state);
63662306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *new_state =
63762306a36Sopenharmony_ci		kzalloc(sizeof(*new_state), GFP_KERNEL);
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	if (connector->state)
64062306a36Sopenharmony_ci		__drm_atomic_helper_connector_destroy_state(connector->state);
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	kfree(old_state);
64362306a36Sopenharmony_ci	__drm_atomic_helper_connector_reset(connector, &new_state->base);
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	if (!new_state)
64662306a36Sopenharmony_ci		return;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	new_state->base.max_bpc = 8;
64962306a36Sopenharmony_ci	new_state->base.max_requested_bpc = 8;
65062306a36Sopenharmony_ci	new_state->output_format = VC4_HDMI_OUTPUT_RGB;
65162306a36Sopenharmony_ci	new_state->broadcast_rgb = VC4_HDMI_BROADCAST_RGB_AUTO;
65262306a36Sopenharmony_ci	drm_atomic_helper_connector_tv_margins_reset(connector);
65362306a36Sopenharmony_ci}
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_cistatic struct drm_connector_state *
65662306a36Sopenharmony_civc4_hdmi_connector_duplicate_state(struct drm_connector *connector)
65762306a36Sopenharmony_ci{
65862306a36Sopenharmony_ci	struct drm_connector_state *conn_state = connector->state;
65962306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(conn_state);
66062306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *new_state;
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	new_state = kzalloc(sizeof(*new_state), GFP_KERNEL);
66362306a36Sopenharmony_ci	if (!new_state)
66462306a36Sopenharmony_ci		return NULL;
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	new_state->tmds_char_rate = vc4_state->tmds_char_rate;
66762306a36Sopenharmony_ci	new_state->output_bpc = vc4_state->output_bpc;
66862306a36Sopenharmony_ci	new_state->output_format = vc4_state->output_format;
66962306a36Sopenharmony_ci	new_state->broadcast_rgb = vc4_state->broadcast_rgb;
67062306a36Sopenharmony_ci	__drm_atomic_helper_connector_duplicate_state(connector, &new_state->base);
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	return &new_state->base;
67362306a36Sopenharmony_ci}
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_cistatic const struct drm_connector_funcs vc4_hdmi_connector_funcs = {
67662306a36Sopenharmony_ci	.fill_modes = drm_helper_probe_single_connector_modes,
67762306a36Sopenharmony_ci	.reset = vc4_hdmi_connector_reset,
67862306a36Sopenharmony_ci	.atomic_duplicate_state = vc4_hdmi_connector_duplicate_state,
67962306a36Sopenharmony_ci	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
68062306a36Sopenharmony_ci	.atomic_get_property = vc4_hdmi_connector_get_property,
68162306a36Sopenharmony_ci	.atomic_set_property = vc4_hdmi_connector_set_property,
68262306a36Sopenharmony_ci};
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_cistatic const struct drm_connector_helper_funcs vc4_hdmi_connector_helper_funcs = {
68562306a36Sopenharmony_ci	.detect_ctx = vc4_hdmi_connector_detect_ctx,
68662306a36Sopenharmony_ci	.get_modes = vc4_hdmi_connector_get_modes,
68762306a36Sopenharmony_ci	.atomic_check = vc4_hdmi_connector_atomic_check,
68862306a36Sopenharmony_ci};
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_cistatic const struct drm_prop_enum_list broadcast_rgb_names[] = {
69162306a36Sopenharmony_ci	{ VC4_HDMI_BROADCAST_RGB_AUTO, "Automatic" },
69262306a36Sopenharmony_ci	{ VC4_HDMI_BROADCAST_RGB_FULL, "Full" },
69362306a36Sopenharmony_ci	{ VC4_HDMI_BROADCAST_RGB_LIMITED, "Limited 16:235" },
69462306a36Sopenharmony_ci};
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_cistatic void
69762306a36Sopenharmony_civc4_hdmi_attach_broadcast_rgb_property(struct drm_device *dev,
69862306a36Sopenharmony_ci				       struct vc4_hdmi *vc4_hdmi)
69962306a36Sopenharmony_ci{
70062306a36Sopenharmony_ci	struct drm_property *prop = vc4_hdmi->broadcast_rgb_property;
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	if (!prop) {
70362306a36Sopenharmony_ci		prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM,
70462306a36Sopenharmony_ci						"Broadcast RGB",
70562306a36Sopenharmony_ci						broadcast_rgb_names,
70662306a36Sopenharmony_ci						ARRAY_SIZE(broadcast_rgb_names));
70762306a36Sopenharmony_ci		if (!prop)
70862306a36Sopenharmony_ci			return;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci		vc4_hdmi->broadcast_rgb_property = prop;
71162306a36Sopenharmony_ci	}
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_ci	drm_object_attach_property(&vc4_hdmi->connector.base, prop,
71462306a36Sopenharmony_ci				   VC4_HDMI_BROADCAST_RGB_AUTO);
71562306a36Sopenharmony_ci}
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_cistatic int vc4_hdmi_connector_init(struct drm_device *dev,
71862306a36Sopenharmony_ci				   struct vc4_hdmi *vc4_hdmi)
71962306a36Sopenharmony_ci{
72062306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
72162306a36Sopenharmony_ci	struct drm_encoder *encoder = &vc4_hdmi->encoder.base;
72262306a36Sopenharmony_ci	int ret;
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_ci	ret = drmm_connector_init(dev, connector,
72562306a36Sopenharmony_ci				  &vc4_hdmi_connector_funcs,
72662306a36Sopenharmony_ci				  DRM_MODE_CONNECTOR_HDMIA,
72762306a36Sopenharmony_ci				  vc4_hdmi->ddc);
72862306a36Sopenharmony_ci	if (ret)
72962306a36Sopenharmony_ci		return ret;
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci	drm_connector_helper_add(connector, &vc4_hdmi_connector_helper_funcs);
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	/*
73462306a36Sopenharmony_ci	 * Some of the properties below require access to state, like bpc.
73562306a36Sopenharmony_ci	 * Allocate some default initial connector state with our reset helper.
73662306a36Sopenharmony_ci	 */
73762306a36Sopenharmony_ci	if (connector->funcs->reset)
73862306a36Sopenharmony_ci		connector->funcs->reset(connector);
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	/* Create and attach TV margin props to this connector. */
74162306a36Sopenharmony_ci	ret = drm_mode_create_tv_margin_properties(dev);
74262306a36Sopenharmony_ci	if (ret)
74362306a36Sopenharmony_ci		return ret;
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	ret = drm_mode_create_hdmi_colorspace_property(connector, 0);
74662306a36Sopenharmony_ci	if (ret)
74762306a36Sopenharmony_ci		return ret;
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_ci	drm_connector_attach_colorspace_property(connector);
75062306a36Sopenharmony_ci	drm_connector_attach_tv_margin_properties(connector);
75162306a36Sopenharmony_ci	drm_connector_attach_max_bpc_property(connector, 8, 12);
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	connector->polled = (DRM_CONNECTOR_POLL_CONNECT |
75462306a36Sopenharmony_ci			     DRM_CONNECTOR_POLL_DISCONNECT);
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci	connector->interlace_allowed = 1;
75762306a36Sopenharmony_ci	connector->doublescan_allowed = 0;
75862306a36Sopenharmony_ci	connector->stereo_allowed = 1;
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	if (vc4_hdmi->variant->supports_hdr)
76162306a36Sopenharmony_ci		drm_connector_attach_hdr_output_metadata_property(connector);
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	vc4_hdmi_attach_broadcast_rgb_property(dev, vc4_hdmi);
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	drm_connector_attach_encoder(connector, encoder);
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci	return 0;
76862306a36Sopenharmony_ci}
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_cistatic int vc4_hdmi_stop_packet(struct drm_encoder *encoder,
77162306a36Sopenharmony_ci				enum hdmi_infoframe_type type,
77262306a36Sopenharmony_ci				bool poll)
77362306a36Sopenharmony_ci{
77462306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
77562306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
77662306a36Sopenharmony_ci	u32 packet_id = type - 0x80;
77762306a36Sopenharmony_ci	unsigned long flags;
77862306a36Sopenharmony_ci	int ret = 0;
77962306a36Sopenharmony_ci	int idx;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
78262306a36Sopenharmony_ci		return -ENODEV;
78362306a36Sopenharmony_ci
78462306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
78562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
78662306a36Sopenharmony_ci		   HDMI_READ(HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id));
78762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	if (poll) {
79062306a36Sopenharmony_ci		ret = wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) &
79162306a36Sopenharmony_ci				 BIT(packet_id)), 100);
79262306a36Sopenharmony_ci	}
79362306a36Sopenharmony_ci
79462306a36Sopenharmony_ci	drm_dev_exit(idx);
79562306a36Sopenharmony_ci	return ret;
79662306a36Sopenharmony_ci}
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_cistatic void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
79962306a36Sopenharmony_ci				     union hdmi_infoframe *frame)
80062306a36Sopenharmony_ci{
80162306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
80262306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
80362306a36Sopenharmony_ci	u32 packet_id = frame->any.type - 0x80;
80462306a36Sopenharmony_ci	const struct vc4_hdmi_register *ram_packet_start =
80562306a36Sopenharmony_ci		&vc4_hdmi->variant->registers[HDMI_RAM_PACKET_START];
80662306a36Sopenharmony_ci	u32 packet_reg = ram_packet_start->offset + VC4_HDMI_PACKET_STRIDE * packet_id;
80762306a36Sopenharmony_ci	u32 packet_reg_next = ram_packet_start->offset +
80862306a36Sopenharmony_ci		VC4_HDMI_PACKET_STRIDE * (packet_id + 1);
80962306a36Sopenharmony_ci	void __iomem *base = __vc4_hdmi_get_field_base(vc4_hdmi,
81062306a36Sopenharmony_ci						       ram_packet_start->reg);
81162306a36Sopenharmony_ci	uint8_t buffer[VC4_HDMI_PACKET_STRIDE] = {};
81262306a36Sopenharmony_ci	unsigned long flags;
81362306a36Sopenharmony_ci	ssize_t len, i;
81462306a36Sopenharmony_ci	int ret;
81562306a36Sopenharmony_ci	int idx;
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
81862306a36Sopenharmony_ci		return;
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_ci	WARN_ONCE(!(HDMI_READ(HDMI_RAM_PACKET_CONFIG) &
82162306a36Sopenharmony_ci		    VC4_HDMI_RAM_PACKET_ENABLE),
82262306a36Sopenharmony_ci		  "Packet RAM has to be on to store the packet.");
82362306a36Sopenharmony_ci
82462306a36Sopenharmony_ci	len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer));
82562306a36Sopenharmony_ci	if (len < 0)
82662306a36Sopenharmony_ci		goto out;
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	ret = vc4_hdmi_stop_packet(encoder, frame->any.type, true);
82962306a36Sopenharmony_ci	if (ret) {
83062306a36Sopenharmony_ci		DRM_ERROR("Failed to wait for infoframe to go idle: %d\n", ret);
83162306a36Sopenharmony_ci		goto out;
83262306a36Sopenharmony_ci	}
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci	for (i = 0; i < len; i += 7) {
83762306a36Sopenharmony_ci		writel(buffer[i + 0] << 0 |
83862306a36Sopenharmony_ci		       buffer[i + 1] << 8 |
83962306a36Sopenharmony_ci		       buffer[i + 2] << 16,
84062306a36Sopenharmony_ci		       base + packet_reg);
84162306a36Sopenharmony_ci		packet_reg += 4;
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci		writel(buffer[i + 3] << 0 |
84462306a36Sopenharmony_ci		       buffer[i + 4] << 8 |
84562306a36Sopenharmony_ci		       buffer[i + 5] << 16 |
84662306a36Sopenharmony_ci		       buffer[i + 6] << 24,
84762306a36Sopenharmony_ci		       base + packet_reg);
84862306a36Sopenharmony_ci		packet_reg += 4;
84962306a36Sopenharmony_ci	}
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	/*
85262306a36Sopenharmony_ci	 * clear remainder of packet ram as it's included in the
85362306a36Sopenharmony_ci	 * infoframe and triggers a checksum error on hdmi analyser
85462306a36Sopenharmony_ci	 */
85562306a36Sopenharmony_ci	for (; packet_reg < packet_reg_next; packet_reg += 4)
85662306a36Sopenharmony_ci		writel(0, base + packet_reg);
85762306a36Sopenharmony_ci
85862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
85962306a36Sopenharmony_ci		   HDMI_READ(HDMI_RAM_PACKET_CONFIG) | BIT(packet_id));
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
86262306a36Sopenharmony_ci
86362306a36Sopenharmony_ci	ret = wait_for((HDMI_READ(HDMI_RAM_PACKET_STATUS) &
86462306a36Sopenharmony_ci			BIT(packet_id)), 100);
86562306a36Sopenharmony_ci	if (ret)
86662306a36Sopenharmony_ci		DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret);
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ciout:
86962306a36Sopenharmony_ci	drm_dev_exit(idx);
87062306a36Sopenharmony_ci}
87162306a36Sopenharmony_ci
87262306a36Sopenharmony_cistatic void vc4_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame,
87362306a36Sopenharmony_ci					      enum vc4_hdmi_output_format fmt)
87462306a36Sopenharmony_ci{
87562306a36Sopenharmony_ci	switch (fmt) {
87662306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_RGB:
87762306a36Sopenharmony_ci		frame->colorspace = HDMI_COLORSPACE_RGB;
87862306a36Sopenharmony_ci		break;
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_YUV420:
88162306a36Sopenharmony_ci		frame->colorspace = HDMI_COLORSPACE_YUV420;
88262306a36Sopenharmony_ci		break;
88362306a36Sopenharmony_ci
88462306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_YUV422:
88562306a36Sopenharmony_ci		frame->colorspace = HDMI_COLORSPACE_YUV422;
88662306a36Sopenharmony_ci		break;
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_YUV444:
88962306a36Sopenharmony_ci		frame->colorspace = HDMI_COLORSPACE_YUV444;
89062306a36Sopenharmony_ci		break;
89162306a36Sopenharmony_ci
89262306a36Sopenharmony_ci	default:
89362306a36Sopenharmony_ci		break;
89462306a36Sopenharmony_ci	}
89562306a36Sopenharmony_ci}
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_cistatic void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)
89862306a36Sopenharmony_ci{
89962306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
90062306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
90162306a36Sopenharmony_ci	struct drm_connector_state *cstate = connector->state;
90262306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *vc4_state =
90362306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(cstate);
90462306a36Sopenharmony_ci	const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
90562306a36Sopenharmony_ci	union hdmi_infoframe frame;
90662306a36Sopenharmony_ci	int ret;
90762306a36Sopenharmony_ci
90862306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi,
91162306a36Sopenharmony_ci						       connector, mode);
91262306a36Sopenharmony_ci	if (ret < 0) {
91362306a36Sopenharmony_ci		DRM_ERROR("couldn't fill AVI infoframe\n");
91462306a36Sopenharmony_ci		return;
91562306a36Sopenharmony_ci	}
91662306a36Sopenharmony_ci
91762306a36Sopenharmony_ci	drm_hdmi_avi_infoframe_quant_range(&frame.avi,
91862306a36Sopenharmony_ci					   connector, mode,
91962306a36Sopenharmony_ci					   vc4_hdmi_is_full_range(vc4_hdmi, vc4_state) ?
92062306a36Sopenharmony_ci					   HDMI_QUANTIZATION_RANGE_FULL :
92162306a36Sopenharmony_ci					   HDMI_QUANTIZATION_RANGE_LIMITED);
92262306a36Sopenharmony_ci	drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate);
92362306a36Sopenharmony_ci	vc4_hdmi_avi_infoframe_colorspace(&frame.avi, vc4_state->output_format);
92462306a36Sopenharmony_ci	drm_hdmi_avi_infoframe_bars(&frame.avi, cstate);
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	vc4_hdmi_write_infoframe(encoder, &frame);
92762306a36Sopenharmony_ci}
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_cistatic void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder)
93062306a36Sopenharmony_ci{
93162306a36Sopenharmony_ci	union hdmi_infoframe frame;
93262306a36Sopenharmony_ci	int ret;
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci	ret = hdmi_spd_infoframe_init(&frame.spd, "Broadcom", "Videocore");
93562306a36Sopenharmony_ci	if (ret < 0) {
93662306a36Sopenharmony_ci		DRM_ERROR("couldn't fill SPD infoframe\n");
93762306a36Sopenharmony_ci		return;
93862306a36Sopenharmony_ci	}
93962306a36Sopenharmony_ci
94062306a36Sopenharmony_ci	frame.spd.sdi = HDMI_SPD_SDI_PC;
94162306a36Sopenharmony_ci
94262306a36Sopenharmony_ci	vc4_hdmi_write_infoframe(encoder, &frame);
94362306a36Sopenharmony_ci}
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_cistatic void vc4_hdmi_set_audio_infoframe(struct drm_encoder *encoder)
94662306a36Sopenharmony_ci{
94762306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
94862306a36Sopenharmony_ci	struct hdmi_audio_infoframe *audio = &vc4_hdmi->audio.infoframe;
94962306a36Sopenharmony_ci	union hdmi_infoframe frame;
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_ci	memcpy(&frame.audio, audio, sizeof(*audio));
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_ci	if (vc4_hdmi->packet_ram_enabled)
95462306a36Sopenharmony_ci		vc4_hdmi_write_infoframe(encoder, &frame);
95562306a36Sopenharmony_ci}
95662306a36Sopenharmony_ci
95762306a36Sopenharmony_cistatic void vc4_hdmi_set_hdr_infoframe(struct drm_encoder *encoder)
95862306a36Sopenharmony_ci{
95962306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
96062306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
96162306a36Sopenharmony_ci	struct drm_connector_state *conn_state = connector->state;
96262306a36Sopenharmony_ci	union hdmi_infoframe frame;
96362306a36Sopenharmony_ci
96462306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
96562306a36Sopenharmony_ci
96662306a36Sopenharmony_ci	if (!vc4_hdmi->variant->supports_hdr)
96762306a36Sopenharmony_ci		return;
96862306a36Sopenharmony_ci
96962306a36Sopenharmony_ci	if (!conn_state->hdr_output_metadata)
97062306a36Sopenharmony_ci		return;
97162306a36Sopenharmony_ci
97262306a36Sopenharmony_ci	if (drm_hdmi_infoframe_set_hdr_metadata(&frame.drm, conn_state))
97362306a36Sopenharmony_ci		return;
97462306a36Sopenharmony_ci
97562306a36Sopenharmony_ci	vc4_hdmi_write_infoframe(encoder, &frame);
97662306a36Sopenharmony_ci}
97762306a36Sopenharmony_ci
97862306a36Sopenharmony_cistatic void vc4_hdmi_set_infoframes(struct drm_encoder *encoder)
97962306a36Sopenharmony_ci{
98062306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
98162306a36Sopenharmony_ci
98262306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_ci	vc4_hdmi_set_avi_infoframe(encoder);
98562306a36Sopenharmony_ci	vc4_hdmi_set_spd_infoframe(encoder);
98662306a36Sopenharmony_ci	/*
98762306a36Sopenharmony_ci	 * If audio was streaming, then we need to reenabled the audio
98862306a36Sopenharmony_ci	 * infoframe here during encoder_enable.
98962306a36Sopenharmony_ci	 */
99062306a36Sopenharmony_ci	if (vc4_hdmi->audio.streaming)
99162306a36Sopenharmony_ci		vc4_hdmi_set_audio_infoframe(encoder);
99262306a36Sopenharmony_ci
99362306a36Sopenharmony_ci	vc4_hdmi_set_hdr_infoframe(encoder);
99462306a36Sopenharmony_ci}
99562306a36Sopenharmony_ci
99662306a36Sopenharmony_ci#define SCRAMBLING_POLLING_DELAY_MS	1000
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_cistatic void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder)
99962306a36Sopenharmony_ci{
100062306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
100162306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
100262306a36Sopenharmony_ci	struct drm_device *drm = connector->dev;
100362306a36Sopenharmony_ci	const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
100462306a36Sopenharmony_ci	unsigned long flags;
100562306a36Sopenharmony_ci	int idx;
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci	if (!vc4_hdmi_supports_scrambling(vc4_hdmi))
101062306a36Sopenharmony_ci		return;
101162306a36Sopenharmony_ci
101262306a36Sopenharmony_ci	if (!vc4_hdmi_mode_needs_scrambling(mode,
101362306a36Sopenharmony_ci					    vc4_hdmi->output_bpc,
101462306a36Sopenharmony_ci					    vc4_hdmi->output_format))
101562306a36Sopenharmony_ci		return;
101662306a36Sopenharmony_ci
101762306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
101862306a36Sopenharmony_ci		return;
101962306a36Sopenharmony_ci
102062306a36Sopenharmony_ci	drm_scdc_set_high_tmds_clock_ratio(connector, true);
102162306a36Sopenharmony_ci	drm_scdc_set_scrambling(connector, true);
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
102462306a36Sopenharmony_ci	HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) |
102562306a36Sopenharmony_ci		   VC5_HDMI_SCRAMBLER_CTL_ENABLE);
102662306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
102762306a36Sopenharmony_ci
102862306a36Sopenharmony_ci	drm_dev_exit(idx);
102962306a36Sopenharmony_ci
103062306a36Sopenharmony_ci	vc4_hdmi->scdc_enabled = true;
103162306a36Sopenharmony_ci
103262306a36Sopenharmony_ci	queue_delayed_work(system_wq, &vc4_hdmi->scrambling_work,
103362306a36Sopenharmony_ci			   msecs_to_jiffies(SCRAMBLING_POLLING_DELAY_MS));
103462306a36Sopenharmony_ci}
103562306a36Sopenharmony_ci
103662306a36Sopenharmony_cistatic void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder)
103762306a36Sopenharmony_ci{
103862306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
103962306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
104062306a36Sopenharmony_ci	struct drm_device *drm = connector->dev;
104162306a36Sopenharmony_ci	unsigned long flags;
104262306a36Sopenharmony_ci	int idx;
104362306a36Sopenharmony_ci
104462306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_ci	if (!vc4_hdmi->scdc_enabled)
104762306a36Sopenharmony_ci		return;
104862306a36Sopenharmony_ci
104962306a36Sopenharmony_ci	vc4_hdmi->scdc_enabled = false;
105062306a36Sopenharmony_ci
105162306a36Sopenharmony_ci	if (delayed_work_pending(&vc4_hdmi->scrambling_work))
105262306a36Sopenharmony_ci		cancel_delayed_work_sync(&vc4_hdmi->scrambling_work);
105362306a36Sopenharmony_ci
105462306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
105562306a36Sopenharmony_ci		return;
105662306a36Sopenharmony_ci
105762306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
105862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) &
105962306a36Sopenharmony_ci		   ~VC5_HDMI_SCRAMBLER_CTL_ENABLE);
106062306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci	drm_scdc_set_scrambling(connector, false);
106362306a36Sopenharmony_ci	drm_scdc_set_high_tmds_clock_ratio(connector, false);
106462306a36Sopenharmony_ci
106562306a36Sopenharmony_ci	drm_dev_exit(idx);
106662306a36Sopenharmony_ci}
106762306a36Sopenharmony_ci
106862306a36Sopenharmony_cistatic void vc4_hdmi_scrambling_wq(struct work_struct *work)
106962306a36Sopenharmony_ci{
107062306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = container_of(to_delayed_work(work),
107162306a36Sopenharmony_ci						 struct vc4_hdmi,
107262306a36Sopenharmony_ci						 scrambling_work);
107362306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
107462306a36Sopenharmony_ci
107562306a36Sopenharmony_ci	if (drm_scdc_get_scrambling_status(connector))
107662306a36Sopenharmony_ci		return;
107762306a36Sopenharmony_ci
107862306a36Sopenharmony_ci	drm_scdc_set_high_tmds_clock_ratio(connector, true);
107962306a36Sopenharmony_ci	drm_scdc_set_scrambling(connector, true);
108062306a36Sopenharmony_ci
108162306a36Sopenharmony_ci	queue_delayed_work(system_wq, &vc4_hdmi->scrambling_work,
108262306a36Sopenharmony_ci			   msecs_to_jiffies(SCRAMBLING_POLLING_DELAY_MS));
108362306a36Sopenharmony_ci}
108462306a36Sopenharmony_ci
108562306a36Sopenharmony_cistatic void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder,
108662306a36Sopenharmony_ci					       struct drm_atomic_state *state)
108762306a36Sopenharmony_ci{
108862306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
108962306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
109062306a36Sopenharmony_ci	unsigned long flags;
109162306a36Sopenharmony_ci	int idx;
109262306a36Sopenharmony_ci
109362306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
109462306a36Sopenharmony_ci
109562306a36Sopenharmony_ci	vc4_hdmi->packet_ram_enabled = false;
109662306a36Sopenharmony_ci
109762306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
109862306a36Sopenharmony_ci		goto out;
109962306a36Sopenharmony_ci
110062306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
110162306a36Sopenharmony_ci
110262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 0);
110362306a36Sopenharmony_ci
110462306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_CLRRGB);
110562306a36Sopenharmony_ci
110662306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
110762306a36Sopenharmony_ci
110862306a36Sopenharmony_ci	mdelay(1);
110962306a36Sopenharmony_ci
111062306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
111162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VID_CTL,
111262306a36Sopenharmony_ci		   HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE);
111362306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
111462306a36Sopenharmony_ci
111562306a36Sopenharmony_ci	vc4_hdmi_disable_scrambling(encoder);
111662306a36Sopenharmony_ci
111762306a36Sopenharmony_ci	drm_dev_exit(idx);
111862306a36Sopenharmony_ci
111962306a36Sopenharmony_ciout:
112062306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
112162306a36Sopenharmony_ci}
112262306a36Sopenharmony_ci
112362306a36Sopenharmony_cistatic void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder,
112462306a36Sopenharmony_ci						 struct drm_atomic_state *state)
112562306a36Sopenharmony_ci{
112662306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
112762306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
112862306a36Sopenharmony_ci	unsigned long flags;
112962306a36Sopenharmony_ci	int ret;
113062306a36Sopenharmony_ci	int idx;
113162306a36Sopenharmony_ci
113262306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
113362306a36Sopenharmony_ci
113462306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
113562306a36Sopenharmony_ci		goto out;
113662306a36Sopenharmony_ci
113762306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
113862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VID_CTL,
113962306a36Sopenharmony_ci		   HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_BLANKPIX);
114062306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	if (vc4_hdmi->variant->phy_disable)
114362306a36Sopenharmony_ci		vc4_hdmi->variant->phy_disable(vc4_hdmi);
114462306a36Sopenharmony_ci
114562306a36Sopenharmony_ci	clk_disable_unprepare(vc4_hdmi->pixel_bvb_clock);
114662306a36Sopenharmony_ci	clk_disable_unprepare(vc4_hdmi->pixel_clock);
114762306a36Sopenharmony_ci
114862306a36Sopenharmony_ci	ret = pm_runtime_put(&vc4_hdmi->pdev->dev);
114962306a36Sopenharmony_ci	if (ret < 0)
115062306a36Sopenharmony_ci		DRM_ERROR("Failed to release power domain: %d\n", ret);
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_ci	drm_dev_exit(idx);
115362306a36Sopenharmony_ci
115462306a36Sopenharmony_ciout:
115562306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
115662306a36Sopenharmony_ci}
115762306a36Sopenharmony_ci
115862306a36Sopenharmony_cistatic void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
115962306a36Sopenharmony_ci			       struct drm_connector_state *state,
116062306a36Sopenharmony_ci			       const struct drm_display_mode *mode)
116162306a36Sopenharmony_ci{
116262306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *vc4_state =
116362306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(state);
116462306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
116562306a36Sopenharmony_ci	unsigned long flags;
116662306a36Sopenharmony_ci	u32 csc_ctl;
116762306a36Sopenharmony_ci	int idx;
116862306a36Sopenharmony_ci
116962306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
117062306a36Sopenharmony_ci		return;
117162306a36Sopenharmony_ci
117262306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
117362306a36Sopenharmony_ci
117462306a36Sopenharmony_ci	csc_ctl = VC4_SET_FIELD(VC4_HD_CSC_CTL_ORDER_BGR,
117562306a36Sopenharmony_ci				VC4_HD_CSC_CTL_ORDER);
117662306a36Sopenharmony_ci
117762306a36Sopenharmony_ci	if (!vc4_hdmi_is_full_range(vc4_hdmi, vc4_state)) {
117862306a36Sopenharmony_ci		/* CEA VICs other than #1 requre limited range RGB
117962306a36Sopenharmony_ci		 * output unless overridden by an AVI infoframe.
118062306a36Sopenharmony_ci		 * Apply a colorspace conversion to squash 0-255 down
118162306a36Sopenharmony_ci		 * to 16-235.  The matrix here is:
118262306a36Sopenharmony_ci		 *
118362306a36Sopenharmony_ci		 * [ 0      0      0.8594 16]
118462306a36Sopenharmony_ci		 * [ 0      0.8594 0      16]
118562306a36Sopenharmony_ci		 * [ 0.8594 0      0      16]
118662306a36Sopenharmony_ci		 * [ 0      0      0       1]
118762306a36Sopenharmony_ci		 */
118862306a36Sopenharmony_ci		csc_ctl |= VC4_HD_CSC_CTL_ENABLE;
118962306a36Sopenharmony_ci		csc_ctl |= VC4_HD_CSC_CTL_RGB2YCC;
119062306a36Sopenharmony_ci		csc_ctl |= VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM,
119162306a36Sopenharmony_ci					 VC4_HD_CSC_CTL_MODE);
119262306a36Sopenharmony_ci
119362306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CSC_12_11, (0x000 << 16) | 0x000);
119462306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CSC_14_13, (0x100 << 16) | 0x6e0);
119562306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CSC_22_21, (0x6e0 << 16) | 0x000);
119662306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CSC_24_23, (0x100 << 16) | 0x000);
119762306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CSC_32_31, (0x000 << 16) | 0x6e0);
119862306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CSC_34_33, (0x100 << 16) | 0x000);
119962306a36Sopenharmony_ci	}
120062306a36Sopenharmony_ci
120162306a36Sopenharmony_ci	/* The RGB order applies even when CSC is disabled. */
120262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
120362306a36Sopenharmony_ci
120462306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
120562306a36Sopenharmony_ci
120662306a36Sopenharmony_ci	drm_dev_exit(idx);
120762306a36Sopenharmony_ci}
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci/*
121062306a36Sopenharmony_ci * Matrices for (internal) RGB to RGB output.
121162306a36Sopenharmony_ci *
121262306a36Sopenharmony_ci * Matrices are signed 2p13 fixed point, with signed 9p6 offsets
121362306a36Sopenharmony_ci */
121462306a36Sopenharmony_cistatic const u16 vc5_hdmi_csc_full_rgb_to_rgb[2][3][4] = {
121562306a36Sopenharmony_ci	{
121662306a36Sopenharmony_ci		/*
121762306a36Sopenharmony_ci		 * Full range - unity
121862306a36Sopenharmony_ci		 *
121962306a36Sopenharmony_ci		 * [ 1      0      0      0]
122062306a36Sopenharmony_ci		 * [ 0      1      0      0]
122162306a36Sopenharmony_ci		 * [ 0      0      1      0]
122262306a36Sopenharmony_ci		 */
122362306a36Sopenharmony_ci		{ 0x2000, 0x0000, 0x0000, 0x0000 },
122462306a36Sopenharmony_ci		{ 0x0000, 0x2000, 0x0000, 0x0000 },
122562306a36Sopenharmony_ci		{ 0x0000, 0x0000, 0x2000, 0x0000 },
122662306a36Sopenharmony_ci	},
122762306a36Sopenharmony_ci	{
122862306a36Sopenharmony_ci		/*
122962306a36Sopenharmony_ci		 * Limited range
123062306a36Sopenharmony_ci		 *
123162306a36Sopenharmony_ci		 * CEA VICs other than #1 require limited range RGB
123262306a36Sopenharmony_ci		 * output unless overridden by an AVI infoframe. Apply a
123362306a36Sopenharmony_ci		 * colorspace conversion to squash 0-255 down to 16-235.
123462306a36Sopenharmony_ci		 * The matrix here is:
123562306a36Sopenharmony_ci		 *
123662306a36Sopenharmony_ci		 * [ 0.8594 0      0      16]
123762306a36Sopenharmony_ci		 * [ 0      0.8594 0      16]
123862306a36Sopenharmony_ci		 * [ 0      0      0.8594 16]
123962306a36Sopenharmony_ci		 */
124062306a36Sopenharmony_ci		{ 0x1b80, 0x0000, 0x0000, 0x0400 },
124162306a36Sopenharmony_ci		{ 0x0000, 0x1b80, 0x0000, 0x0400 },
124262306a36Sopenharmony_ci		{ 0x0000, 0x0000, 0x1b80, 0x0400 },
124362306a36Sopenharmony_ci	},
124462306a36Sopenharmony_ci};
124562306a36Sopenharmony_ci
124662306a36Sopenharmony_ci/*
124762306a36Sopenharmony_ci * Conversion between Full Range RGB and YUV using the BT.601 Colorspace
124862306a36Sopenharmony_ci *
124962306a36Sopenharmony_ci * Matrices are signed 2p13 fixed point, with signed 9p6 offsets
125062306a36Sopenharmony_ci */
125162306a36Sopenharmony_cistatic const u16 vc5_hdmi_csc_full_rgb_to_yuv_bt601[2][3][4] = {
125262306a36Sopenharmony_ci	{
125362306a36Sopenharmony_ci		/*
125462306a36Sopenharmony_ci		 * Full Range
125562306a36Sopenharmony_ci		 *
125662306a36Sopenharmony_ci		 * [  0.299000  0.587000  0.114000  0   ]
125762306a36Sopenharmony_ci		 * [ -0.168736 -0.331264  0.500000  128 ]
125862306a36Sopenharmony_ci		 * [  0.500000 -0.418688 -0.081312  128 ]
125962306a36Sopenharmony_ci		 */
126062306a36Sopenharmony_ci		{ 0x0991, 0x12c9, 0x03a6, 0x0000 },
126162306a36Sopenharmony_ci		{ 0xfa9b, 0xf567, 0x1000, 0x2000 },
126262306a36Sopenharmony_ci		{ 0x1000, 0xf29b, 0xfd67, 0x2000 },
126362306a36Sopenharmony_ci	},
126462306a36Sopenharmony_ci	{
126562306a36Sopenharmony_ci		/* Limited Range
126662306a36Sopenharmony_ci		 *
126762306a36Sopenharmony_ci		 * [  0.255785  0.502160  0.097523  16  ]
126862306a36Sopenharmony_ci		 * [ -0.147644 -0.289856  0.437500  128 ]
126962306a36Sopenharmony_ci		 * [  0.437500 -0.366352 -0.071148  128 ]
127062306a36Sopenharmony_ci		 */
127162306a36Sopenharmony_ci		{ 0x082f, 0x1012, 0x031f, 0x0400 },
127262306a36Sopenharmony_ci		{ 0xfb48, 0xf6ba, 0x0e00, 0x2000 },
127362306a36Sopenharmony_ci		{ 0x0e00, 0xf448, 0xfdba, 0x2000 },
127462306a36Sopenharmony_ci	},
127562306a36Sopenharmony_ci};
127662306a36Sopenharmony_ci
127762306a36Sopenharmony_ci/*
127862306a36Sopenharmony_ci * Conversion between Full Range RGB and YUV using the BT.709 Colorspace
127962306a36Sopenharmony_ci *
128062306a36Sopenharmony_ci * Matrices are signed 2p13 fixed point, with signed 9p6 offsets
128162306a36Sopenharmony_ci */
128262306a36Sopenharmony_cistatic const u16 vc5_hdmi_csc_full_rgb_to_yuv_bt709[2][3][4] = {
128362306a36Sopenharmony_ci	{
128462306a36Sopenharmony_ci		/*
128562306a36Sopenharmony_ci		 * Full Range
128662306a36Sopenharmony_ci		 *
128762306a36Sopenharmony_ci		 * [  0.212600  0.715200  0.072200  0   ]
128862306a36Sopenharmony_ci		 * [ -0.114572 -0.385428  0.500000  128 ]
128962306a36Sopenharmony_ci		 * [  0.500000 -0.454153 -0.045847  128 ]
129062306a36Sopenharmony_ci		 */
129162306a36Sopenharmony_ci		{ 0x06ce, 0x16e3, 0x024f, 0x0000 },
129262306a36Sopenharmony_ci		{ 0xfc56, 0xf3ac, 0x1000, 0x2000 },
129362306a36Sopenharmony_ci		{ 0x1000, 0xf179, 0xfe89, 0x2000 },
129462306a36Sopenharmony_ci	},
129562306a36Sopenharmony_ci	{
129662306a36Sopenharmony_ci		/*
129762306a36Sopenharmony_ci		 * Limited Range
129862306a36Sopenharmony_ci		 *
129962306a36Sopenharmony_ci		 * [  0.181906  0.611804  0.061758  16  ]
130062306a36Sopenharmony_ci		 * [ -0.100268 -0.337232  0.437500  128 ]
130162306a36Sopenharmony_ci		 * [  0.437500 -0.397386 -0.040114  128 ]
130262306a36Sopenharmony_ci		 */
130362306a36Sopenharmony_ci		{ 0x05d2, 0x1394, 0x01fa, 0x0400 },
130462306a36Sopenharmony_ci		{ 0xfccc, 0xf536, 0x0e00, 0x2000 },
130562306a36Sopenharmony_ci		{ 0x0e00, 0xf34a, 0xfeb8, 0x2000 },
130662306a36Sopenharmony_ci	},
130762306a36Sopenharmony_ci};
130862306a36Sopenharmony_ci
130962306a36Sopenharmony_ci/*
131062306a36Sopenharmony_ci * Conversion between Full Range RGB and YUV using the BT.2020 Colorspace
131162306a36Sopenharmony_ci *
131262306a36Sopenharmony_ci * Matrices are signed 2p13 fixed point, with signed 9p6 offsets
131362306a36Sopenharmony_ci */
131462306a36Sopenharmony_cistatic const u16 vc5_hdmi_csc_full_rgb_to_yuv_bt2020[2][3][4] = {
131562306a36Sopenharmony_ci	{
131662306a36Sopenharmony_ci		/*
131762306a36Sopenharmony_ci		 * Full Range
131862306a36Sopenharmony_ci		 *
131962306a36Sopenharmony_ci		 * [  0.262700  0.678000  0.059300  0   ]
132062306a36Sopenharmony_ci		 * [ -0.139630 -0.360370  0.500000  128 ]
132162306a36Sopenharmony_ci		 * [  0.500000 -0.459786 -0.040214  128 ]
132262306a36Sopenharmony_ci		 */
132362306a36Sopenharmony_ci		{ 0x0868, 0x15b2, 0x01e6, 0x0000 },
132462306a36Sopenharmony_ci		{ 0xfb89, 0xf479, 0x1000, 0x2000 },
132562306a36Sopenharmony_ci		{ 0x1000, 0xf14a, 0xfeb8, 0x2000 },
132662306a36Sopenharmony_ci	},
132762306a36Sopenharmony_ci	{
132862306a36Sopenharmony_ci		/* Limited Range
132962306a36Sopenharmony_ci		 *
133062306a36Sopenharmony_ci		 * [  0.224732  0.580008  0.050729  16  ]
133162306a36Sopenharmony_ci		 * [ -0.122176 -0.315324  0.437500  128 ]
133262306a36Sopenharmony_ci		 * [  0.437500 -0.402312 -0.035188  128 ]
133362306a36Sopenharmony_ci		 */
133462306a36Sopenharmony_ci		{ 0x082f, 0x1012, 0x031f, 0x0400 },
133562306a36Sopenharmony_ci		{ 0xfb48, 0xf6ba, 0x0e00, 0x2000 },
133662306a36Sopenharmony_ci		{ 0x0e00, 0xf448, 0xfdba, 0x2000 },
133762306a36Sopenharmony_ci	},
133862306a36Sopenharmony_ci};
133962306a36Sopenharmony_ci
134062306a36Sopenharmony_cistatic void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi,
134162306a36Sopenharmony_ci				    const u16 coeffs[3][4])
134262306a36Sopenharmony_ci{
134362306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->hw_lock);
134462306a36Sopenharmony_ci
134562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_12_11, (coeffs[0][1] << 16) | coeffs[0][0]);
134662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_14_13, (coeffs[0][3] << 16) | coeffs[0][2]);
134762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_22_21, (coeffs[1][1] << 16) | coeffs[1][0]);
134862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_24_23, (coeffs[1][3] << 16) | coeffs[1][2]);
134962306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_32_31, (coeffs[2][1] << 16) | coeffs[2][0]);
135062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_34_33, (coeffs[2][3] << 16) | coeffs[2][2]);
135162306a36Sopenharmony_ci}
135262306a36Sopenharmony_ci
135362306a36Sopenharmony_cistatic void vc5_hdmi_set_csc_coeffs_swap(struct vc4_hdmi *vc4_hdmi,
135462306a36Sopenharmony_ci					 const u16 coeffs[3][4])
135562306a36Sopenharmony_ci{
135662306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->hw_lock);
135762306a36Sopenharmony_ci
135862306a36Sopenharmony_ci	/* YUV444 needs the CSC matrices using the channels in a different order */
135962306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_12_11, (coeffs[1][1] << 16) | coeffs[1][0]);
136062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_14_13, (coeffs[1][3] << 16) | coeffs[1][2]);
136162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_22_21, (coeffs[2][1] << 16) | coeffs[2][0]);
136262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_24_23, (coeffs[2][3] << 16) | coeffs[2][2]);
136362306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_32_31, (coeffs[0][1] << 16) | coeffs[0][0]);
136462306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_34_33, (coeffs[0][3] << 16) | coeffs[0][2]);
136562306a36Sopenharmony_ci}
136662306a36Sopenharmony_ci
136762306a36Sopenharmony_cistatic const u16
136862306a36Sopenharmony_ci(*vc5_hdmi_find_yuv_csc_coeffs(struct vc4_hdmi *vc4_hdmi, u32 colorspace, bool limited))[4]
136962306a36Sopenharmony_ci{
137062306a36Sopenharmony_ci	switch (colorspace) {
137162306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_SMPTE_170M_YCC:
137262306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_XVYCC_601:
137362306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_SYCC_601:
137462306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_OPYCC_601:
137562306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_BT601_YCC:
137662306a36Sopenharmony_ci		return vc5_hdmi_csc_full_rgb_to_yuv_bt601[limited];
137762306a36Sopenharmony_ci
137862306a36Sopenharmony_ci	default:
137962306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_NO_DATA:
138062306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_BT709_YCC:
138162306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_XVYCC_709:
138262306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED:
138362306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT:
138462306a36Sopenharmony_ci		return vc5_hdmi_csc_full_rgb_to_yuv_bt709[limited];
138562306a36Sopenharmony_ci
138662306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_BT2020_CYCC:
138762306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_BT2020_YCC:
138862306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_BT2020_RGB:
138962306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65:
139062306a36Sopenharmony_ci	case DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER:
139162306a36Sopenharmony_ci		return vc5_hdmi_csc_full_rgb_to_yuv_bt2020[limited];
139262306a36Sopenharmony_ci	}
139362306a36Sopenharmony_ci}
139462306a36Sopenharmony_ci
139562306a36Sopenharmony_cistatic void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
139662306a36Sopenharmony_ci			       struct drm_connector_state *state,
139762306a36Sopenharmony_ci			       const struct drm_display_mode *mode)
139862306a36Sopenharmony_ci{
139962306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
140062306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *vc4_state =
140162306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(state);
140262306a36Sopenharmony_ci	unsigned int lim_range = vc4_hdmi_is_full_range(vc4_hdmi, vc4_state) ? 0 : 1;
140362306a36Sopenharmony_ci	unsigned long flags;
140462306a36Sopenharmony_ci	const u16 (*csc)[4];
140562306a36Sopenharmony_ci	u32 if_cfg = 0;
140662306a36Sopenharmony_ci	u32 if_xbar = 0x543210;
140762306a36Sopenharmony_ci	u32 csc_chan_ctl = 0;
140862306a36Sopenharmony_ci	u32 csc_ctl = VC5_MT_CP_CSC_CTL_ENABLE | VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM,
140962306a36Sopenharmony_ci							       VC5_MT_CP_CSC_CTL_MODE);
141062306a36Sopenharmony_ci	int idx;
141162306a36Sopenharmony_ci
141262306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
141362306a36Sopenharmony_ci		return;
141462306a36Sopenharmony_ci
141562306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
141662306a36Sopenharmony_ci
141762306a36Sopenharmony_ci	switch (vc4_state->output_format) {
141862306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_YUV444:
141962306a36Sopenharmony_ci		csc = vc5_hdmi_find_yuv_csc_coeffs(vc4_hdmi, state->colorspace, !!lim_range);
142062306a36Sopenharmony_ci
142162306a36Sopenharmony_ci		vc5_hdmi_set_csc_coeffs_swap(vc4_hdmi, csc);
142262306a36Sopenharmony_ci		break;
142362306a36Sopenharmony_ci
142462306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_YUV422:
142562306a36Sopenharmony_ci		csc = vc5_hdmi_find_yuv_csc_coeffs(vc4_hdmi, state->colorspace, !!lim_range);
142662306a36Sopenharmony_ci
142762306a36Sopenharmony_ci		csc_ctl |= VC4_SET_FIELD(VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_STANDARD,
142862306a36Sopenharmony_ci					 VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422) |
142962306a36Sopenharmony_ci			VC5_MT_CP_CSC_CTL_USE_444_TO_422 |
143062306a36Sopenharmony_ci			VC5_MT_CP_CSC_CTL_USE_RNG_SUPPRESSION;
143162306a36Sopenharmony_ci
143262306a36Sopenharmony_ci		csc_chan_ctl |= VC4_SET_FIELD(VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP_LEGACY_STYLE,
143362306a36Sopenharmony_ci					      VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP);
143462306a36Sopenharmony_ci
143562306a36Sopenharmony_ci		if_cfg |= VC4_SET_FIELD(VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422_FORMAT_422_LEGACY,
143662306a36Sopenharmony_ci					VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422);
143762306a36Sopenharmony_ci
143862306a36Sopenharmony_ci		vc5_hdmi_set_csc_coeffs(vc4_hdmi, csc);
143962306a36Sopenharmony_ci		break;
144062306a36Sopenharmony_ci
144162306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_RGB:
144262306a36Sopenharmony_ci		if_xbar = 0x354021;
144362306a36Sopenharmony_ci
144462306a36Sopenharmony_ci		vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_rgb[lim_range]);
144562306a36Sopenharmony_ci		break;
144662306a36Sopenharmony_ci
144762306a36Sopenharmony_ci	default:
144862306a36Sopenharmony_ci		break;
144962306a36Sopenharmony_ci	}
145062306a36Sopenharmony_ci
145162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VEC_INTERFACE_CFG, if_cfg);
145262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, if_xbar);
145362306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_CHANNEL_CTL, csc_chan_ctl);
145462306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
145562306a36Sopenharmony_ci
145662306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
145762306a36Sopenharmony_ci
145862306a36Sopenharmony_ci	drm_dev_exit(idx);
145962306a36Sopenharmony_ci}
146062306a36Sopenharmony_ci
146162306a36Sopenharmony_cistatic void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
146262306a36Sopenharmony_ci				 struct drm_connector_state *state,
146362306a36Sopenharmony_ci				 const struct drm_display_mode *mode)
146462306a36Sopenharmony_ci{
146562306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
146662306a36Sopenharmony_ci	bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;
146762306a36Sopenharmony_ci	bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;
146862306a36Sopenharmony_ci	bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE;
146962306a36Sopenharmony_ci	u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1;
147062306a36Sopenharmony_ci	u32 verta = (VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start,
147162306a36Sopenharmony_ci				   VC4_HDMI_VERTA_VSP) |
147262306a36Sopenharmony_ci		     VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay,
147362306a36Sopenharmony_ci				   VC4_HDMI_VERTA_VFP) |
147462306a36Sopenharmony_ci		     VC4_SET_FIELD(mode->crtc_vdisplay, VC4_HDMI_VERTA_VAL));
147562306a36Sopenharmony_ci	u32 vertb = (VC4_SET_FIELD(0, VC4_HDMI_VERTB_VSPO) |
147662306a36Sopenharmony_ci		     VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end +
147762306a36Sopenharmony_ci				   interlaced,
147862306a36Sopenharmony_ci				   VC4_HDMI_VERTB_VBP));
147962306a36Sopenharmony_ci	u32 vertb_even = (VC4_SET_FIELD(0, VC4_HDMI_VERTB_VSPO) |
148062306a36Sopenharmony_ci			  VC4_SET_FIELD(mode->crtc_vtotal -
148162306a36Sopenharmony_ci					mode->crtc_vsync_end,
148262306a36Sopenharmony_ci					VC4_HDMI_VERTB_VBP));
148362306a36Sopenharmony_ci	unsigned long flags;
148462306a36Sopenharmony_ci	u32 reg;
148562306a36Sopenharmony_ci	int idx;
148662306a36Sopenharmony_ci
148762306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
148862306a36Sopenharmony_ci		return;
148962306a36Sopenharmony_ci
149062306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
149162306a36Sopenharmony_ci
149262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_HORZA,
149362306a36Sopenharmony_ci		   (vsync_pos ? VC4_HDMI_HORZA_VPOS : 0) |
149462306a36Sopenharmony_ci		   (hsync_pos ? VC4_HDMI_HORZA_HPOS : 0) |
149562306a36Sopenharmony_ci		   VC4_SET_FIELD(mode->hdisplay * pixel_rep,
149662306a36Sopenharmony_ci				 VC4_HDMI_HORZA_HAP));
149762306a36Sopenharmony_ci
149862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_HORZB,
149962306a36Sopenharmony_ci		   VC4_SET_FIELD((mode->htotal -
150062306a36Sopenharmony_ci				  mode->hsync_end) * pixel_rep,
150162306a36Sopenharmony_ci				 VC4_HDMI_HORZB_HBP) |
150262306a36Sopenharmony_ci		   VC4_SET_FIELD((mode->hsync_end -
150362306a36Sopenharmony_ci				  mode->hsync_start) * pixel_rep,
150462306a36Sopenharmony_ci				 VC4_HDMI_HORZB_HSP) |
150562306a36Sopenharmony_ci		   VC4_SET_FIELD((mode->hsync_start -
150662306a36Sopenharmony_ci				  mode->hdisplay) * pixel_rep,
150762306a36Sopenharmony_ci				 VC4_HDMI_HORZB_HFP));
150862306a36Sopenharmony_ci
150962306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VERTA0, verta);
151062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VERTA1, verta);
151162306a36Sopenharmony_ci
151262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VERTB0, vertb_even);
151362306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VERTB1, vertb);
151462306a36Sopenharmony_ci
151562306a36Sopenharmony_ci	reg = HDMI_READ(HDMI_MISC_CONTROL);
151662306a36Sopenharmony_ci	reg &= ~VC4_HDMI_MISC_CONTROL_PIXEL_REP_MASK;
151762306a36Sopenharmony_ci	reg |= VC4_SET_FIELD(pixel_rep - 1, VC4_HDMI_MISC_CONTROL_PIXEL_REP);
151862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MISC_CONTROL, reg);
151962306a36Sopenharmony_ci
152062306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
152162306a36Sopenharmony_ci
152262306a36Sopenharmony_ci	drm_dev_exit(idx);
152362306a36Sopenharmony_ci}
152462306a36Sopenharmony_ci
152562306a36Sopenharmony_cistatic void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
152662306a36Sopenharmony_ci				 struct drm_connector_state *state,
152762306a36Sopenharmony_ci				 const struct drm_display_mode *mode)
152862306a36Sopenharmony_ci{
152962306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
153062306a36Sopenharmony_ci	const struct vc4_hdmi_connector_state *vc4_state =
153162306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(state);
153262306a36Sopenharmony_ci	bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;
153362306a36Sopenharmony_ci	bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;
153462306a36Sopenharmony_ci	bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE;
153562306a36Sopenharmony_ci	u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1;
153662306a36Sopenharmony_ci	u32 verta = (VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start,
153762306a36Sopenharmony_ci				   VC5_HDMI_VERTA_VSP) |
153862306a36Sopenharmony_ci		     VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay,
153962306a36Sopenharmony_ci				   VC5_HDMI_VERTA_VFP) |
154062306a36Sopenharmony_ci		     VC4_SET_FIELD(mode->crtc_vdisplay, VC5_HDMI_VERTA_VAL));
154162306a36Sopenharmony_ci	u32 vertb = (VC4_SET_FIELD(mode->htotal >> (2 - pixel_rep),
154262306a36Sopenharmony_ci				   VC5_HDMI_VERTB_VSPO) |
154362306a36Sopenharmony_ci		     VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end +
154462306a36Sopenharmony_ci				   interlaced,
154562306a36Sopenharmony_ci				   VC4_HDMI_VERTB_VBP));
154662306a36Sopenharmony_ci	u32 vertb_even = (VC4_SET_FIELD(0, VC5_HDMI_VERTB_VSPO) |
154762306a36Sopenharmony_ci			  VC4_SET_FIELD(mode->crtc_vtotal -
154862306a36Sopenharmony_ci					mode->crtc_vsync_end,
154962306a36Sopenharmony_ci					VC4_HDMI_VERTB_VBP));
155062306a36Sopenharmony_ci	unsigned long flags;
155162306a36Sopenharmony_ci	unsigned char gcp;
155262306a36Sopenharmony_ci	u32 reg;
155362306a36Sopenharmony_ci	int idx;
155462306a36Sopenharmony_ci
155562306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
155662306a36Sopenharmony_ci		return;
155762306a36Sopenharmony_ci
155862306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
155962306a36Sopenharmony_ci
156062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_HORZA,
156162306a36Sopenharmony_ci		   (vsync_pos ? VC5_HDMI_HORZA_VPOS : 0) |
156262306a36Sopenharmony_ci		   (hsync_pos ? VC5_HDMI_HORZA_HPOS : 0) |
156362306a36Sopenharmony_ci		   VC4_SET_FIELD(mode->hdisplay * pixel_rep,
156462306a36Sopenharmony_ci				 VC5_HDMI_HORZA_HAP) |
156562306a36Sopenharmony_ci		   VC4_SET_FIELD((mode->hsync_start -
156662306a36Sopenharmony_ci				  mode->hdisplay) * pixel_rep,
156762306a36Sopenharmony_ci				 VC5_HDMI_HORZA_HFP));
156862306a36Sopenharmony_ci
156962306a36Sopenharmony_ci	HDMI_WRITE(HDMI_HORZB,
157062306a36Sopenharmony_ci		   VC4_SET_FIELD((mode->htotal -
157162306a36Sopenharmony_ci				  mode->hsync_end) * pixel_rep,
157262306a36Sopenharmony_ci				 VC5_HDMI_HORZB_HBP) |
157362306a36Sopenharmony_ci		   VC4_SET_FIELD((mode->hsync_end -
157462306a36Sopenharmony_ci				  mode->hsync_start) * pixel_rep,
157562306a36Sopenharmony_ci				 VC5_HDMI_HORZB_HSP));
157662306a36Sopenharmony_ci
157762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VERTA0, verta);
157862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VERTA1, verta);
157962306a36Sopenharmony_ci
158062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VERTB0, vertb_even);
158162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VERTB1, vertb);
158262306a36Sopenharmony_ci
158362306a36Sopenharmony_ci	switch (vc4_state->output_bpc) {
158462306a36Sopenharmony_ci	case 12:
158562306a36Sopenharmony_ci		gcp = 6;
158662306a36Sopenharmony_ci		break;
158762306a36Sopenharmony_ci	case 10:
158862306a36Sopenharmony_ci		gcp = 5;
158962306a36Sopenharmony_ci		break;
159062306a36Sopenharmony_ci	case 8:
159162306a36Sopenharmony_ci	default:
159262306a36Sopenharmony_ci		gcp = 0;
159362306a36Sopenharmony_ci		break;
159462306a36Sopenharmony_ci	}
159562306a36Sopenharmony_ci
159662306a36Sopenharmony_ci	/*
159762306a36Sopenharmony_ci	 * YCC422 is always 36-bit and not considered deep colour so
159862306a36Sopenharmony_ci	 * doesn't signal in GCP.
159962306a36Sopenharmony_ci	 */
160062306a36Sopenharmony_ci	if (vc4_state->output_format == VC4_HDMI_OUTPUT_YUV422) {
160162306a36Sopenharmony_ci		gcp = 0;
160262306a36Sopenharmony_ci	}
160362306a36Sopenharmony_ci
160462306a36Sopenharmony_ci	reg = HDMI_READ(HDMI_DEEP_COLOR_CONFIG_1);
160562306a36Sopenharmony_ci	reg &= ~(VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_MASK |
160662306a36Sopenharmony_ci		 VC5_HDMI_DEEP_COLOR_CONFIG_1_COLOR_DEPTH_MASK);
160762306a36Sopenharmony_ci	reg |= VC4_SET_FIELD(2, VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE) |
160862306a36Sopenharmony_ci	       VC4_SET_FIELD(gcp, VC5_HDMI_DEEP_COLOR_CONFIG_1_COLOR_DEPTH);
160962306a36Sopenharmony_ci	HDMI_WRITE(HDMI_DEEP_COLOR_CONFIG_1, reg);
161062306a36Sopenharmony_ci
161162306a36Sopenharmony_ci	reg = HDMI_READ(HDMI_GCP_WORD_1);
161262306a36Sopenharmony_ci	reg &= ~VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_1_MASK;
161362306a36Sopenharmony_ci	reg |= VC4_SET_FIELD(gcp, VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_1);
161462306a36Sopenharmony_ci	reg &= ~VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_0_MASK;
161562306a36Sopenharmony_ci	reg |= VC5_HDMI_GCP_WORD_1_GCP_SUBPACKET_BYTE_0_CLEAR_AVMUTE;
161662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_GCP_WORD_1, reg);
161762306a36Sopenharmony_ci
161862306a36Sopenharmony_ci	reg = HDMI_READ(HDMI_GCP_CONFIG);
161962306a36Sopenharmony_ci	reg |= VC5_HDMI_GCP_CONFIG_GCP_ENABLE;
162062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_GCP_CONFIG, reg);
162162306a36Sopenharmony_ci
162262306a36Sopenharmony_ci	reg = HDMI_READ(HDMI_MISC_CONTROL);
162362306a36Sopenharmony_ci	reg &= ~VC5_HDMI_MISC_CONTROL_PIXEL_REP_MASK;
162462306a36Sopenharmony_ci	reg |= VC4_SET_FIELD(pixel_rep - 1, VC5_HDMI_MISC_CONTROL_PIXEL_REP);
162562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MISC_CONTROL, reg);
162662306a36Sopenharmony_ci
162762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CLOCK_STOP, 0);
162862306a36Sopenharmony_ci
162962306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
163062306a36Sopenharmony_ci
163162306a36Sopenharmony_ci	drm_dev_exit(idx);
163262306a36Sopenharmony_ci}
163362306a36Sopenharmony_ci
163462306a36Sopenharmony_cistatic void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi)
163562306a36Sopenharmony_ci{
163662306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
163762306a36Sopenharmony_ci	unsigned long flags;
163862306a36Sopenharmony_ci	u32 drift;
163962306a36Sopenharmony_ci	int ret;
164062306a36Sopenharmony_ci	int idx;
164162306a36Sopenharmony_ci
164262306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
164362306a36Sopenharmony_ci		return;
164462306a36Sopenharmony_ci
164562306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
164662306a36Sopenharmony_ci
164762306a36Sopenharmony_ci	drift = HDMI_READ(HDMI_FIFO_CTL);
164862306a36Sopenharmony_ci	drift &= VC4_HDMI_FIFO_VALID_WRITE_MASK;
164962306a36Sopenharmony_ci
165062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_FIFO_CTL,
165162306a36Sopenharmony_ci		   drift & ~VC4_HDMI_FIFO_CTL_RECENTER);
165262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_FIFO_CTL,
165362306a36Sopenharmony_ci		   drift | VC4_HDMI_FIFO_CTL_RECENTER);
165462306a36Sopenharmony_ci
165562306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
165662306a36Sopenharmony_ci
165762306a36Sopenharmony_ci	usleep_range(1000, 1100);
165862306a36Sopenharmony_ci
165962306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
166062306a36Sopenharmony_ci
166162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_FIFO_CTL,
166262306a36Sopenharmony_ci		   drift & ~VC4_HDMI_FIFO_CTL_RECENTER);
166362306a36Sopenharmony_ci	HDMI_WRITE(HDMI_FIFO_CTL,
166462306a36Sopenharmony_ci		   drift | VC4_HDMI_FIFO_CTL_RECENTER);
166562306a36Sopenharmony_ci
166662306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
166762306a36Sopenharmony_ci
166862306a36Sopenharmony_ci	ret = wait_for(HDMI_READ(HDMI_FIFO_CTL) &
166962306a36Sopenharmony_ci		       VC4_HDMI_FIFO_CTL_RECENTER_DONE, 1);
167062306a36Sopenharmony_ci	WARN_ONCE(ret, "Timeout waiting for "
167162306a36Sopenharmony_ci		  "VC4_HDMI_FIFO_CTL_RECENTER_DONE");
167262306a36Sopenharmony_ci
167362306a36Sopenharmony_ci	drm_dev_exit(idx);
167462306a36Sopenharmony_ci}
167562306a36Sopenharmony_ci
167662306a36Sopenharmony_cistatic void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
167762306a36Sopenharmony_ci						struct drm_atomic_state *state)
167862306a36Sopenharmony_ci{
167962306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
168062306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
168162306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
168262306a36Sopenharmony_ci	struct drm_connector_state *conn_state =
168362306a36Sopenharmony_ci		drm_atomic_get_new_connector_state(state, connector);
168462306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *vc4_conn_state =
168562306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(conn_state);
168662306a36Sopenharmony_ci	const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
168762306a36Sopenharmony_ci	unsigned long tmds_char_rate = vc4_conn_state->tmds_char_rate;
168862306a36Sopenharmony_ci	unsigned long bvb_rate, hsm_rate;
168962306a36Sopenharmony_ci	unsigned long flags;
169062306a36Sopenharmony_ci	int ret;
169162306a36Sopenharmony_ci	int idx;
169262306a36Sopenharmony_ci
169362306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
169462306a36Sopenharmony_ci
169562306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
169662306a36Sopenharmony_ci		goto out;
169762306a36Sopenharmony_ci
169862306a36Sopenharmony_ci	ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev);
169962306a36Sopenharmony_ci	if (ret < 0) {
170062306a36Sopenharmony_ci		DRM_ERROR("Failed to retain power domain: %d\n", ret);
170162306a36Sopenharmony_ci		goto err_dev_exit;
170262306a36Sopenharmony_ci	}
170362306a36Sopenharmony_ci
170462306a36Sopenharmony_ci	/*
170562306a36Sopenharmony_ci	 * As stated in RPi's vc4 firmware "HDMI state machine (HSM) clock must
170662306a36Sopenharmony_ci	 * be faster than pixel clock, infinitesimally faster, tested in
170762306a36Sopenharmony_ci	 * simulation. Otherwise, exact value is unimportant for HDMI
170862306a36Sopenharmony_ci	 * operation." This conflicts with bcm2835's vc4 documentation, which
170962306a36Sopenharmony_ci	 * states HSM's clock has to be at least 108% of the pixel clock.
171062306a36Sopenharmony_ci	 *
171162306a36Sopenharmony_ci	 * Real life tests reveal that vc4's firmware statement holds up, and
171262306a36Sopenharmony_ci	 * users are able to use pixel clocks closer to HSM's, namely for
171362306a36Sopenharmony_ci	 * 1920x1200@60Hz. So it was decided to have leave a 1% margin between
171462306a36Sopenharmony_ci	 * both clocks. Which, for RPi0-3 implies a maximum pixel clock of
171562306a36Sopenharmony_ci	 * 162MHz.
171662306a36Sopenharmony_ci	 *
171762306a36Sopenharmony_ci	 * Additionally, the AXI clock needs to be at least 25% of
171862306a36Sopenharmony_ci	 * pixel clock, but HSM ends up being the limiting factor.
171962306a36Sopenharmony_ci	 */
172062306a36Sopenharmony_ci	hsm_rate = max_t(unsigned long,
172162306a36Sopenharmony_ci			 HSM_MIN_CLOCK_FREQ,
172262306a36Sopenharmony_ci			 (tmds_char_rate / 100) * 101);
172362306a36Sopenharmony_ci	ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate);
172462306a36Sopenharmony_ci	if (ret) {
172562306a36Sopenharmony_ci		DRM_ERROR("Failed to set HSM clock rate: %d\n", ret);
172662306a36Sopenharmony_ci		goto err_put_runtime_pm;
172762306a36Sopenharmony_ci	}
172862306a36Sopenharmony_ci
172962306a36Sopenharmony_ci	ret = clk_set_rate(vc4_hdmi->pixel_clock, tmds_char_rate);
173062306a36Sopenharmony_ci	if (ret) {
173162306a36Sopenharmony_ci		DRM_ERROR("Failed to set pixel clock rate: %d\n", ret);
173262306a36Sopenharmony_ci		goto err_put_runtime_pm;
173362306a36Sopenharmony_ci	}
173462306a36Sopenharmony_ci
173562306a36Sopenharmony_ci	ret = clk_prepare_enable(vc4_hdmi->pixel_clock);
173662306a36Sopenharmony_ci	if (ret) {
173762306a36Sopenharmony_ci		DRM_ERROR("Failed to turn on pixel clock: %d\n", ret);
173862306a36Sopenharmony_ci		goto err_put_runtime_pm;
173962306a36Sopenharmony_ci	}
174062306a36Sopenharmony_ci
174162306a36Sopenharmony_ci
174262306a36Sopenharmony_ci	vc4_hdmi_cec_update_clk_div(vc4_hdmi);
174362306a36Sopenharmony_ci
174462306a36Sopenharmony_ci	if (tmds_char_rate > 297000000)
174562306a36Sopenharmony_ci		bvb_rate = 300000000;
174662306a36Sopenharmony_ci	else if (tmds_char_rate > 148500000)
174762306a36Sopenharmony_ci		bvb_rate = 150000000;
174862306a36Sopenharmony_ci	else
174962306a36Sopenharmony_ci		bvb_rate = 75000000;
175062306a36Sopenharmony_ci
175162306a36Sopenharmony_ci	ret = clk_set_min_rate(vc4_hdmi->pixel_bvb_clock, bvb_rate);
175262306a36Sopenharmony_ci	if (ret) {
175362306a36Sopenharmony_ci		DRM_ERROR("Failed to set pixel bvb clock rate: %d\n", ret);
175462306a36Sopenharmony_ci		goto err_disable_pixel_clock;
175562306a36Sopenharmony_ci	}
175662306a36Sopenharmony_ci
175762306a36Sopenharmony_ci	ret = clk_prepare_enable(vc4_hdmi->pixel_bvb_clock);
175862306a36Sopenharmony_ci	if (ret) {
175962306a36Sopenharmony_ci		DRM_ERROR("Failed to turn on pixel bvb clock: %d\n", ret);
176062306a36Sopenharmony_ci		goto err_disable_pixel_clock;
176162306a36Sopenharmony_ci	}
176262306a36Sopenharmony_ci
176362306a36Sopenharmony_ci	if (vc4_hdmi->variant->phy_init)
176462306a36Sopenharmony_ci		vc4_hdmi->variant->phy_init(vc4_hdmi, vc4_conn_state);
176562306a36Sopenharmony_ci
176662306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
176762306a36Sopenharmony_ci
176862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
176962306a36Sopenharmony_ci		   HDMI_READ(HDMI_SCHEDULER_CONTROL) |
177062306a36Sopenharmony_ci		   VC4_HDMI_SCHEDULER_CONTROL_MANUAL_FORMAT |
177162306a36Sopenharmony_ci		   VC4_HDMI_SCHEDULER_CONTROL_IGNORE_VSYNC_PREDICTS);
177262306a36Sopenharmony_ci
177362306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
177462306a36Sopenharmony_ci
177562306a36Sopenharmony_ci	if (vc4_hdmi->variant->set_timings)
177662306a36Sopenharmony_ci		vc4_hdmi->variant->set_timings(vc4_hdmi, conn_state, mode);
177762306a36Sopenharmony_ci
177862306a36Sopenharmony_ci	drm_dev_exit(idx);
177962306a36Sopenharmony_ci
178062306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
178162306a36Sopenharmony_ci
178262306a36Sopenharmony_ci	return;
178362306a36Sopenharmony_ci
178462306a36Sopenharmony_cierr_disable_pixel_clock:
178562306a36Sopenharmony_ci	clk_disable_unprepare(vc4_hdmi->pixel_clock);
178662306a36Sopenharmony_cierr_put_runtime_pm:
178762306a36Sopenharmony_ci	pm_runtime_put(&vc4_hdmi->pdev->dev);
178862306a36Sopenharmony_cierr_dev_exit:
178962306a36Sopenharmony_ci	drm_dev_exit(idx);
179062306a36Sopenharmony_ciout:
179162306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
179262306a36Sopenharmony_ci	return;
179362306a36Sopenharmony_ci}
179462306a36Sopenharmony_ci
179562306a36Sopenharmony_cistatic void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder,
179662306a36Sopenharmony_ci					     struct drm_atomic_state *state)
179762306a36Sopenharmony_ci{
179862306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
179962306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
180062306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
180162306a36Sopenharmony_ci	const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
180262306a36Sopenharmony_ci	struct drm_connector_state *conn_state =
180362306a36Sopenharmony_ci		drm_atomic_get_new_connector_state(state, connector);
180462306a36Sopenharmony_ci	unsigned long flags;
180562306a36Sopenharmony_ci	int idx;
180662306a36Sopenharmony_ci
180762306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
180862306a36Sopenharmony_ci
180962306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
181062306a36Sopenharmony_ci		goto out;
181162306a36Sopenharmony_ci
181262306a36Sopenharmony_ci	if (vc4_hdmi->variant->csc_setup)
181362306a36Sopenharmony_ci		vc4_hdmi->variant->csc_setup(vc4_hdmi, conn_state, mode);
181462306a36Sopenharmony_ci
181562306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
181662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N);
181762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
181862306a36Sopenharmony_ci
181962306a36Sopenharmony_ci	drm_dev_exit(idx);
182062306a36Sopenharmony_ci
182162306a36Sopenharmony_ciout:
182262306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
182362306a36Sopenharmony_ci}
182462306a36Sopenharmony_ci
182562306a36Sopenharmony_cistatic void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder,
182662306a36Sopenharmony_ci					      struct drm_atomic_state *state)
182762306a36Sopenharmony_ci{
182862306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
182962306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
183062306a36Sopenharmony_ci	const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
183162306a36Sopenharmony_ci	struct drm_display_info *display = &vc4_hdmi->connector.display_info;
183262306a36Sopenharmony_ci	bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;
183362306a36Sopenharmony_ci	bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;
183462306a36Sopenharmony_ci	unsigned long flags;
183562306a36Sopenharmony_ci	int ret;
183662306a36Sopenharmony_ci	int idx;
183762306a36Sopenharmony_ci
183862306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
183962306a36Sopenharmony_ci
184062306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
184162306a36Sopenharmony_ci		goto out;
184262306a36Sopenharmony_ci
184362306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
184462306a36Sopenharmony_ci
184562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VID_CTL,
184662306a36Sopenharmony_ci		   VC4_HD_VID_CTL_ENABLE |
184762306a36Sopenharmony_ci		   VC4_HD_VID_CTL_CLRRGB |
184862306a36Sopenharmony_ci		   VC4_HD_VID_CTL_UNDERFLOW_ENABLE |
184962306a36Sopenharmony_ci		   VC4_HD_VID_CTL_FRAME_COUNTER_RESET |
185062306a36Sopenharmony_ci		   (vsync_pos ? 0 : VC4_HD_VID_CTL_VSYNC_LOW) |
185162306a36Sopenharmony_ci		   (hsync_pos ? 0 : VC4_HD_VID_CTL_HSYNC_LOW));
185262306a36Sopenharmony_ci
185362306a36Sopenharmony_ci	HDMI_WRITE(HDMI_VID_CTL,
185462306a36Sopenharmony_ci		   HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_BLANKPIX);
185562306a36Sopenharmony_ci
185662306a36Sopenharmony_ci	if (display->is_hdmi) {
185762306a36Sopenharmony_ci		HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
185862306a36Sopenharmony_ci			   HDMI_READ(HDMI_SCHEDULER_CONTROL) |
185962306a36Sopenharmony_ci			   VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
186062306a36Sopenharmony_ci
186162306a36Sopenharmony_ci		spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
186262306a36Sopenharmony_ci
186362306a36Sopenharmony_ci		ret = wait_for(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
186462306a36Sopenharmony_ci			       VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE, 1000);
186562306a36Sopenharmony_ci		WARN_ONCE(ret, "Timeout waiting for "
186662306a36Sopenharmony_ci			  "VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
186762306a36Sopenharmony_ci	} else {
186862306a36Sopenharmony_ci		HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
186962306a36Sopenharmony_ci			   HDMI_READ(HDMI_RAM_PACKET_CONFIG) &
187062306a36Sopenharmony_ci			   ~(VC4_HDMI_RAM_PACKET_ENABLE));
187162306a36Sopenharmony_ci		HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
187262306a36Sopenharmony_ci			   HDMI_READ(HDMI_SCHEDULER_CONTROL) &
187362306a36Sopenharmony_ci			   ~VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
187462306a36Sopenharmony_ci
187562306a36Sopenharmony_ci		spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
187662306a36Sopenharmony_ci
187762306a36Sopenharmony_ci		ret = wait_for(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
187862306a36Sopenharmony_ci				 VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE), 1000);
187962306a36Sopenharmony_ci		WARN_ONCE(ret, "Timeout waiting for "
188062306a36Sopenharmony_ci			  "!VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
188162306a36Sopenharmony_ci	}
188262306a36Sopenharmony_ci
188362306a36Sopenharmony_ci	if (display->is_hdmi) {
188462306a36Sopenharmony_ci		spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
188562306a36Sopenharmony_ci
188662306a36Sopenharmony_ci		WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
188762306a36Sopenharmony_ci			  VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE));
188862306a36Sopenharmony_ci
188962306a36Sopenharmony_ci		HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
189062306a36Sopenharmony_ci			   VC4_HDMI_RAM_PACKET_ENABLE);
189162306a36Sopenharmony_ci
189262306a36Sopenharmony_ci		spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
189362306a36Sopenharmony_ci		vc4_hdmi->packet_ram_enabled = true;
189462306a36Sopenharmony_ci
189562306a36Sopenharmony_ci		vc4_hdmi_set_infoframes(encoder);
189662306a36Sopenharmony_ci	}
189762306a36Sopenharmony_ci
189862306a36Sopenharmony_ci	vc4_hdmi_recenter_fifo(vc4_hdmi);
189962306a36Sopenharmony_ci	vc4_hdmi_enable_scrambling(encoder);
190062306a36Sopenharmony_ci
190162306a36Sopenharmony_ci	drm_dev_exit(idx);
190262306a36Sopenharmony_ci
190362306a36Sopenharmony_ciout:
190462306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
190562306a36Sopenharmony_ci}
190662306a36Sopenharmony_ci
190762306a36Sopenharmony_cistatic void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder,
190862306a36Sopenharmony_ci					     struct drm_crtc_state *crtc_state,
190962306a36Sopenharmony_ci					     struct drm_connector_state *conn_state)
191062306a36Sopenharmony_ci{
191162306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
191262306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *vc4_state =
191362306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(conn_state);
191462306a36Sopenharmony_ci
191562306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
191662306a36Sopenharmony_ci	drm_mode_copy(&vc4_hdmi->saved_adjusted_mode,
191762306a36Sopenharmony_ci		      &crtc_state->adjusted_mode);
191862306a36Sopenharmony_ci	vc4_hdmi->output_bpc = vc4_state->output_bpc;
191962306a36Sopenharmony_ci	vc4_hdmi->output_format = vc4_state->output_format;
192062306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
192162306a36Sopenharmony_ci}
192262306a36Sopenharmony_ci
192362306a36Sopenharmony_cistatic bool
192462306a36Sopenharmony_civc4_hdmi_sink_supports_format_bpc(const struct vc4_hdmi *vc4_hdmi,
192562306a36Sopenharmony_ci				  const struct drm_display_info *info,
192662306a36Sopenharmony_ci				  const struct drm_display_mode *mode,
192762306a36Sopenharmony_ci				  unsigned int format, unsigned int bpc)
192862306a36Sopenharmony_ci{
192962306a36Sopenharmony_ci	struct drm_device *dev = vc4_hdmi->connector.dev;
193062306a36Sopenharmony_ci	u8 vic = drm_match_cea_mode(mode);
193162306a36Sopenharmony_ci
193262306a36Sopenharmony_ci	if (vic == 1 && bpc != 8) {
193362306a36Sopenharmony_ci		drm_dbg(dev, "VIC1 requires a bpc of 8, got %u\n", bpc);
193462306a36Sopenharmony_ci		return false;
193562306a36Sopenharmony_ci	}
193662306a36Sopenharmony_ci
193762306a36Sopenharmony_ci	if (!info->is_hdmi &&
193862306a36Sopenharmony_ci	    (format != VC4_HDMI_OUTPUT_RGB || bpc != 8)) {
193962306a36Sopenharmony_ci		drm_dbg(dev, "DVI Monitors require an RGB output at 8 bpc\n");
194062306a36Sopenharmony_ci		return false;
194162306a36Sopenharmony_ci	}
194262306a36Sopenharmony_ci
194362306a36Sopenharmony_ci	switch (format) {
194462306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_RGB:
194562306a36Sopenharmony_ci		drm_dbg(dev, "RGB Format, checking the constraints.\n");
194662306a36Sopenharmony_ci
194762306a36Sopenharmony_ci		if (!(info->color_formats & DRM_COLOR_FORMAT_RGB444))
194862306a36Sopenharmony_ci			return false;
194962306a36Sopenharmony_ci
195062306a36Sopenharmony_ci		if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
195162306a36Sopenharmony_ci			drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
195262306a36Sopenharmony_ci			return false;
195362306a36Sopenharmony_ci		}
195462306a36Sopenharmony_ci
195562306a36Sopenharmony_ci		if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
195662306a36Sopenharmony_ci			drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
195762306a36Sopenharmony_ci			return false;
195862306a36Sopenharmony_ci		}
195962306a36Sopenharmony_ci
196062306a36Sopenharmony_ci		drm_dbg(dev, "RGB format supported in that configuration.\n");
196162306a36Sopenharmony_ci
196262306a36Sopenharmony_ci		return true;
196362306a36Sopenharmony_ci
196462306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_YUV422:
196562306a36Sopenharmony_ci		drm_dbg(dev, "YUV422 format, checking the constraints.\n");
196662306a36Sopenharmony_ci
196762306a36Sopenharmony_ci		if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR422)) {
196862306a36Sopenharmony_ci			drm_dbg(dev, "Sink doesn't support YUV422.\n");
196962306a36Sopenharmony_ci			return false;
197062306a36Sopenharmony_ci		}
197162306a36Sopenharmony_ci
197262306a36Sopenharmony_ci		if (bpc != 12) {
197362306a36Sopenharmony_ci			drm_dbg(dev, "YUV422 only supports 12 bpc.\n");
197462306a36Sopenharmony_ci			return false;
197562306a36Sopenharmony_ci		}
197662306a36Sopenharmony_ci
197762306a36Sopenharmony_ci		drm_dbg(dev, "YUV422 format supported in that configuration.\n");
197862306a36Sopenharmony_ci
197962306a36Sopenharmony_ci		return true;
198062306a36Sopenharmony_ci
198162306a36Sopenharmony_ci	case VC4_HDMI_OUTPUT_YUV444:
198262306a36Sopenharmony_ci		drm_dbg(dev, "YUV444 format, checking the constraints.\n");
198362306a36Sopenharmony_ci
198462306a36Sopenharmony_ci		if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR444)) {
198562306a36Sopenharmony_ci			drm_dbg(dev, "Sink doesn't support YUV444.\n");
198662306a36Sopenharmony_ci			return false;
198762306a36Sopenharmony_ci		}
198862306a36Sopenharmony_ci
198962306a36Sopenharmony_ci		if (bpc == 10 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_30)) {
199062306a36Sopenharmony_ci			drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
199162306a36Sopenharmony_ci			return false;
199262306a36Sopenharmony_ci		}
199362306a36Sopenharmony_ci
199462306a36Sopenharmony_ci		if (bpc == 12 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_36)) {
199562306a36Sopenharmony_ci			drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
199662306a36Sopenharmony_ci			return false;
199762306a36Sopenharmony_ci		}
199862306a36Sopenharmony_ci
199962306a36Sopenharmony_ci		drm_dbg(dev, "YUV444 format supported in that configuration.\n");
200062306a36Sopenharmony_ci
200162306a36Sopenharmony_ci		return true;
200262306a36Sopenharmony_ci	}
200362306a36Sopenharmony_ci
200462306a36Sopenharmony_ci	return false;
200562306a36Sopenharmony_ci}
200662306a36Sopenharmony_ci
200762306a36Sopenharmony_cistatic enum drm_mode_status
200862306a36Sopenharmony_civc4_hdmi_encoder_clock_valid(const struct vc4_hdmi *vc4_hdmi,
200962306a36Sopenharmony_ci			     const struct drm_display_mode *mode,
201062306a36Sopenharmony_ci			     unsigned long long clock)
201162306a36Sopenharmony_ci{
201262306a36Sopenharmony_ci	const struct drm_connector *connector = &vc4_hdmi->connector;
201362306a36Sopenharmony_ci	const struct drm_display_info *info = &connector->display_info;
201462306a36Sopenharmony_ci	struct vc4_dev *vc4 = to_vc4_dev(connector->dev);
201562306a36Sopenharmony_ci
201662306a36Sopenharmony_ci	if (clock > vc4_hdmi->variant->max_pixel_clock)
201762306a36Sopenharmony_ci		return MODE_CLOCK_HIGH;
201862306a36Sopenharmony_ci
201962306a36Sopenharmony_ci	if (!vc4->hvs->vc5_hdmi_enable_hdmi_20 && clock > HDMI_14_MAX_TMDS_CLK)
202062306a36Sopenharmony_ci		return MODE_CLOCK_HIGH;
202162306a36Sopenharmony_ci
202262306a36Sopenharmony_ci	/* 4096x2160@60 is not reliable without overclocking core */
202362306a36Sopenharmony_ci	if (!vc4->hvs->vc5_hdmi_enable_4096by2160 &&
202462306a36Sopenharmony_ci	    mode->hdisplay > 3840 && mode->vdisplay >= 2160 &&
202562306a36Sopenharmony_ci	    drm_mode_vrefresh(mode) >= 50)
202662306a36Sopenharmony_ci		return MODE_CLOCK_HIGH;
202762306a36Sopenharmony_ci
202862306a36Sopenharmony_ci	if (info->max_tmds_clock && clock > (info->max_tmds_clock * 1000))
202962306a36Sopenharmony_ci		return MODE_CLOCK_HIGH;
203062306a36Sopenharmony_ci
203162306a36Sopenharmony_ci	return MODE_OK;
203262306a36Sopenharmony_ci}
203362306a36Sopenharmony_ci
203462306a36Sopenharmony_cistatic unsigned long long
203562306a36Sopenharmony_civc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode,
203662306a36Sopenharmony_ci				    unsigned int bpc,
203762306a36Sopenharmony_ci				    enum vc4_hdmi_output_format fmt)
203862306a36Sopenharmony_ci{
203962306a36Sopenharmony_ci	unsigned long long clock = mode->clock * 1000ULL;
204062306a36Sopenharmony_ci
204162306a36Sopenharmony_ci	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
204262306a36Sopenharmony_ci		clock = clock * 2;
204362306a36Sopenharmony_ci
204462306a36Sopenharmony_ci	if (fmt == VC4_HDMI_OUTPUT_YUV422)
204562306a36Sopenharmony_ci		bpc = 8;
204662306a36Sopenharmony_ci
204762306a36Sopenharmony_ci	clock = clock * bpc;
204862306a36Sopenharmony_ci	do_div(clock, 8);
204962306a36Sopenharmony_ci
205062306a36Sopenharmony_ci	return clock;
205162306a36Sopenharmony_ci}
205262306a36Sopenharmony_ci
205362306a36Sopenharmony_cistatic int
205462306a36Sopenharmony_civc4_hdmi_encoder_compute_clock(const struct vc4_hdmi *vc4_hdmi,
205562306a36Sopenharmony_ci			       struct vc4_hdmi_connector_state *vc4_state,
205662306a36Sopenharmony_ci			       const struct drm_display_mode *mode,
205762306a36Sopenharmony_ci			       unsigned int bpc, unsigned int fmt)
205862306a36Sopenharmony_ci{
205962306a36Sopenharmony_ci	unsigned long long clock;
206062306a36Sopenharmony_ci
206162306a36Sopenharmony_ci	clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt);
206262306a36Sopenharmony_ci	if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, mode, clock) != MODE_OK)
206362306a36Sopenharmony_ci		return -EINVAL;
206462306a36Sopenharmony_ci
206562306a36Sopenharmony_ci	vc4_state->tmds_char_rate = clock;
206662306a36Sopenharmony_ci
206762306a36Sopenharmony_ci	return 0;
206862306a36Sopenharmony_ci}
206962306a36Sopenharmony_ci
207062306a36Sopenharmony_cistatic int
207162306a36Sopenharmony_civc4_hdmi_encoder_compute_format(const struct vc4_hdmi *vc4_hdmi,
207262306a36Sopenharmony_ci				struct vc4_hdmi_connector_state *vc4_state,
207362306a36Sopenharmony_ci				const struct drm_display_mode *mode,
207462306a36Sopenharmony_ci				unsigned int bpc)
207562306a36Sopenharmony_ci{
207662306a36Sopenharmony_ci	struct drm_device *dev = vc4_hdmi->connector.dev;
207762306a36Sopenharmony_ci	const struct drm_connector *connector = &vc4_hdmi->connector;
207862306a36Sopenharmony_ci	const struct drm_display_info *info = &connector->display_info;
207962306a36Sopenharmony_ci	unsigned int format;
208062306a36Sopenharmony_ci
208162306a36Sopenharmony_ci	drm_dbg(dev, "Trying with an RGB output\n");
208262306a36Sopenharmony_ci
208362306a36Sopenharmony_ci	format = VC4_HDMI_OUTPUT_RGB;
208462306a36Sopenharmony_ci	if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) {
208562306a36Sopenharmony_ci		int ret;
208662306a36Sopenharmony_ci
208762306a36Sopenharmony_ci		ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state,
208862306a36Sopenharmony_ci						     mode, bpc, format);
208962306a36Sopenharmony_ci		if (!ret) {
209062306a36Sopenharmony_ci			vc4_state->output_format = format;
209162306a36Sopenharmony_ci			return 0;
209262306a36Sopenharmony_ci		}
209362306a36Sopenharmony_ci	}
209462306a36Sopenharmony_ci
209562306a36Sopenharmony_ci	drm_dbg(dev, "Failed, Trying with an YUV422 output\n");
209662306a36Sopenharmony_ci
209762306a36Sopenharmony_ci	format = VC4_HDMI_OUTPUT_YUV422;
209862306a36Sopenharmony_ci	if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) {
209962306a36Sopenharmony_ci		int ret;
210062306a36Sopenharmony_ci
210162306a36Sopenharmony_ci		ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state,
210262306a36Sopenharmony_ci						     mode, bpc, format);
210362306a36Sopenharmony_ci		if (!ret) {
210462306a36Sopenharmony_ci			vc4_state->output_format = format;
210562306a36Sopenharmony_ci			return 0;
210662306a36Sopenharmony_ci		}
210762306a36Sopenharmony_ci	}
210862306a36Sopenharmony_ci
210962306a36Sopenharmony_ci	drm_dbg(dev, "Failed. No Format Supported for that bpc count.\n");
211062306a36Sopenharmony_ci
211162306a36Sopenharmony_ci	return -EINVAL;
211262306a36Sopenharmony_ci}
211362306a36Sopenharmony_ci
211462306a36Sopenharmony_cistatic int
211562306a36Sopenharmony_civc4_hdmi_encoder_compute_config(const struct vc4_hdmi *vc4_hdmi,
211662306a36Sopenharmony_ci				struct vc4_hdmi_connector_state *vc4_state,
211762306a36Sopenharmony_ci				const struct drm_display_mode *mode)
211862306a36Sopenharmony_ci{
211962306a36Sopenharmony_ci	struct drm_device *dev = vc4_hdmi->connector.dev;
212062306a36Sopenharmony_ci	struct drm_connector_state *conn_state = &vc4_state->base;
212162306a36Sopenharmony_ci	unsigned int max_bpc = clamp_t(unsigned int, conn_state->max_bpc, 8, 12);
212262306a36Sopenharmony_ci	unsigned int bpc;
212362306a36Sopenharmony_ci	int ret;
212462306a36Sopenharmony_ci
212562306a36Sopenharmony_ci	for (bpc = max_bpc; bpc >= 8; bpc -= 2) {
212662306a36Sopenharmony_ci		drm_dbg(dev, "Trying with a %d bpc output\n", bpc);
212762306a36Sopenharmony_ci
212862306a36Sopenharmony_ci		ret = vc4_hdmi_encoder_compute_format(vc4_hdmi, vc4_state,
212962306a36Sopenharmony_ci						      mode, bpc);
213062306a36Sopenharmony_ci		if (ret)
213162306a36Sopenharmony_ci			continue;
213262306a36Sopenharmony_ci
213362306a36Sopenharmony_ci		vc4_state->output_bpc = bpc;
213462306a36Sopenharmony_ci
213562306a36Sopenharmony_ci		drm_dbg(dev,
213662306a36Sopenharmony_ci			"Mode %ux%u @ %uHz: Found configuration: bpc: %u, fmt: %s, clock: %llu\n",
213762306a36Sopenharmony_ci			mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
213862306a36Sopenharmony_ci			vc4_state->output_bpc,
213962306a36Sopenharmony_ci			vc4_hdmi_output_fmt_str(vc4_state->output_format),
214062306a36Sopenharmony_ci			vc4_state->tmds_char_rate);
214162306a36Sopenharmony_ci
214262306a36Sopenharmony_ci		break;
214362306a36Sopenharmony_ci	}
214462306a36Sopenharmony_ci
214562306a36Sopenharmony_ci	return ret;
214662306a36Sopenharmony_ci}
214762306a36Sopenharmony_ci
214862306a36Sopenharmony_ci#define WIFI_2_4GHz_CH1_MIN_FREQ	2400000000ULL
214962306a36Sopenharmony_ci#define WIFI_2_4GHz_CH1_MAX_FREQ	2422000000ULL
215062306a36Sopenharmony_ci
215162306a36Sopenharmony_cistatic int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
215262306a36Sopenharmony_ci					 struct drm_crtc_state *crtc_state,
215362306a36Sopenharmony_ci					 struct drm_connector_state *conn_state)
215462306a36Sopenharmony_ci{
215562306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
215662306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
215762306a36Sopenharmony_ci	struct drm_connector_state *old_conn_state =
215862306a36Sopenharmony_ci		drm_atomic_get_old_connector_state(conn_state->state, connector);
215962306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *old_vc4_state =
216062306a36Sopenharmony_ci		conn_state_to_vc4_hdmi_conn_state(old_conn_state);
216162306a36Sopenharmony_ci	struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(conn_state);
216262306a36Sopenharmony_ci	struct drm_display_mode *mode = &crtc_state->adjusted_mode;
216362306a36Sopenharmony_ci	unsigned long long tmds_char_rate = mode->clock * 1000;
216462306a36Sopenharmony_ci	unsigned long long tmds_bit_rate;
216562306a36Sopenharmony_ci	int ret;
216662306a36Sopenharmony_ci
216762306a36Sopenharmony_ci	if (vc4_hdmi->variant->unsupported_odd_h_timings) {
216862306a36Sopenharmony_ci		if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
216962306a36Sopenharmony_ci			/* Only try to fixup DBLCLK modes to get 480i and 576i
217062306a36Sopenharmony_ci			 * working.
217162306a36Sopenharmony_ci			 * A generic solution for all modes with odd horizontal
217262306a36Sopenharmony_ci			 * timing values seems impossible based on trying to
217362306a36Sopenharmony_ci			 * solve it for 1366x768 monitors.
217462306a36Sopenharmony_ci			 */
217562306a36Sopenharmony_ci			if ((mode->hsync_start - mode->hdisplay) & 1)
217662306a36Sopenharmony_ci				mode->hsync_start--;
217762306a36Sopenharmony_ci			if ((mode->hsync_end - mode->hsync_start) & 1)
217862306a36Sopenharmony_ci				mode->hsync_end--;
217962306a36Sopenharmony_ci		}
218062306a36Sopenharmony_ci
218162306a36Sopenharmony_ci		/* Now check whether we still have odd values remaining */
218262306a36Sopenharmony_ci		if ((mode->hdisplay % 2) || (mode->hsync_start % 2) ||
218362306a36Sopenharmony_ci		    (mode->hsync_end % 2) || (mode->htotal % 2))
218462306a36Sopenharmony_ci			return -EINVAL;
218562306a36Sopenharmony_ci	}
218662306a36Sopenharmony_ci
218762306a36Sopenharmony_ci	/*
218862306a36Sopenharmony_ci	 * The 1440p@60 pixel rate is in the same range than the first
218962306a36Sopenharmony_ci	 * WiFi channel (between 2.4GHz and 2.422GHz with 22MHz
219062306a36Sopenharmony_ci	 * bandwidth). Slightly lower the frequency to bring it out of
219162306a36Sopenharmony_ci	 * the WiFi range.
219262306a36Sopenharmony_ci	 */
219362306a36Sopenharmony_ci	tmds_bit_rate = tmds_char_rate * 10;
219462306a36Sopenharmony_ci	if (vc4_hdmi->disable_wifi_frequencies &&
219562306a36Sopenharmony_ci	    (tmds_bit_rate >= WIFI_2_4GHz_CH1_MIN_FREQ &&
219662306a36Sopenharmony_ci	     tmds_bit_rate <= WIFI_2_4GHz_CH1_MAX_FREQ)) {
219762306a36Sopenharmony_ci		mode->clock = 238560;
219862306a36Sopenharmony_ci		tmds_char_rate = mode->clock * 1000;
219962306a36Sopenharmony_ci	}
220062306a36Sopenharmony_ci
220162306a36Sopenharmony_ci	ret = vc4_hdmi_encoder_compute_config(vc4_hdmi, vc4_state, mode);
220262306a36Sopenharmony_ci	if (ret)
220362306a36Sopenharmony_ci		return ret;
220462306a36Sopenharmony_ci
220562306a36Sopenharmony_ci	/* vc4_hdmi_encoder_compute_config may have changed output_bpc and/or output_format */
220662306a36Sopenharmony_ci	if (vc4_state->output_bpc != old_vc4_state->output_bpc ||
220762306a36Sopenharmony_ci	    vc4_state->output_format != old_vc4_state->output_format)
220862306a36Sopenharmony_ci		crtc_state->mode_changed = true;
220962306a36Sopenharmony_ci
221062306a36Sopenharmony_ci	return 0;
221162306a36Sopenharmony_ci}
221262306a36Sopenharmony_ci
221362306a36Sopenharmony_cistatic enum drm_mode_status
221462306a36Sopenharmony_civc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder,
221562306a36Sopenharmony_ci			    const struct drm_display_mode *mode)
221662306a36Sopenharmony_ci{
221762306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
221862306a36Sopenharmony_ci
221962306a36Sopenharmony_ci	if (vc4_hdmi->variant->unsupported_odd_h_timings &&
222062306a36Sopenharmony_ci	    !(mode->flags & DRM_MODE_FLAG_DBLCLK) &&
222162306a36Sopenharmony_ci	    ((mode->hdisplay % 2) || (mode->hsync_start % 2) ||
222262306a36Sopenharmony_ci	     (mode->hsync_end % 2) || (mode->htotal % 2)))
222362306a36Sopenharmony_ci		return MODE_H_ILLEGAL;
222462306a36Sopenharmony_ci
222562306a36Sopenharmony_ci	return vc4_hdmi_encoder_clock_valid(vc4_hdmi, mode, mode->clock * 1000);
222662306a36Sopenharmony_ci}
222762306a36Sopenharmony_ci
222862306a36Sopenharmony_cistatic const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = {
222962306a36Sopenharmony_ci	.atomic_check = vc4_hdmi_encoder_atomic_check,
223062306a36Sopenharmony_ci	.atomic_mode_set = vc4_hdmi_encoder_atomic_mode_set,
223162306a36Sopenharmony_ci	.mode_valid = vc4_hdmi_encoder_mode_valid,
223262306a36Sopenharmony_ci};
223362306a36Sopenharmony_ci
223462306a36Sopenharmony_cistatic int vc4_hdmi_late_register(struct drm_encoder *encoder)
223562306a36Sopenharmony_ci{
223662306a36Sopenharmony_ci	struct drm_device *drm = encoder->dev;
223762306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
223862306a36Sopenharmony_ci	const struct vc4_hdmi_variant *variant = vc4_hdmi->variant;
223962306a36Sopenharmony_ci
224062306a36Sopenharmony_ci	drm_debugfs_add_file(drm, variant->debugfs_name,
224162306a36Sopenharmony_ci			     vc4_hdmi_debugfs_regs, vc4_hdmi);
224262306a36Sopenharmony_ci
224362306a36Sopenharmony_ci	return 0;
224462306a36Sopenharmony_ci}
224562306a36Sopenharmony_ci
224662306a36Sopenharmony_cistatic const struct drm_encoder_funcs vc4_hdmi_encoder_funcs = {
224762306a36Sopenharmony_ci	.late_register = vc4_hdmi_late_register,
224862306a36Sopenharmony_ci};
224962306a36Sopenharmony_ci
225062306a36Sopenharmony_cistatic u32 vc4_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask)
225162306a36Sopenharmony_ci{
225262306a36Sopenharmony_ci	int i;
225362306a36Sopenharmony_ci	u32 channel_map = 0;
225462306a36Sopenharmony_ci
225562306a36Sopenharmony_ci	for (i = 0; i < 8; i++) {
225662306a36Sopenharmony_ci		if (channel_mask & BIT(i))
225762306a36Sopenharmony_ci			channel_map |= i << (3 * i);
225862306a36Sopenharmony_ci	}
225962306a36Sopenharmony_ci	return channel_map;
226062306a36Sopenharmony_ci}
226162306a36Sopenharmony_ci
226262306a36Sopenharmony_cistatic u32 vc5_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask)
226362306a36Sopenharmony_ci{
226462306a36Sopenharmony_ci	int i;
226562306a36Sopenharmony_ci	u32 channel_map = 0;
226662306a36Sopenharmony_ci
226762306a36Sopenharmony_ci	for (i = 0; i < 8; i++) {
226862306a36Sopenharmony_ci		if (channel_mask & BIT(i))
226962306a36Sopenharmony_ci			channel_map |= i << (4 * i);
227062306a36Sopenharmony_ci	}
227162306a36Sopenharmony_ci	return channel_map;
227262306a36Sopenharmony_ci}
227362306a36Sopenharmony_ci
227462306a36Sopenharmony_cistatic bool vc5_hdmi_hp_detect(struct vc4_hdmi *vc4_hdmi)
227562306a36Sopenharmony_ci{
227662306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
227762306a36Sopenharmony_ci	unsigned long flags;
227862306a36Sopenharmony_ci	u32 hotplug;
227962306a36Sopenharmony_ci	int idx;
228062306a36Sopenharmony_ci
228162306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
228262306a36Sopenharmony_ci		return false;
228362306a36Sopenharmony_ci
228462306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
228562306a36Sopenharmony_ci	hotplug = HDMI_READ(HDMI_HOTPLUG);
228662306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
228762306a36Sopenharmony_ci
228862306a36Sopenharmony_ci	drm_dev_exit(idx);
228962306a36Sopenharmony_ci
229062306a36Sopenharmony_ci	return !!(hotplug & VC4_HDMI_HOTPLUG_CONNECTED);
229162306a36Sopenharmony_ci}
229262306a36Sopenharmony_ci
229362306a36Sopenharmony_ci/* HDMI audio codec callbacks */
229462306a36Sopenharmony_cistatic void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi,
229562306a36Sopenharmony_ci					 unsigned int samplerate)
229662306a36Sopenharmony_ci{
229762306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
229862306a36Sopenharmony_ci	u32 hsm_clock;
229962306a36Sopenharmony_ci	unsigned long flags;
230062306a36Sopenharmony_ci	unsigned long n, m;
230162306a36Sopenharmony_ci	int idx;
230262306a36Sopenharmony_ci
230362306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
230462306a36Sopenharmony_ci		return;
230562306a36Sopenharmony_ci
230662306a36Sopenharmony_ci	hsm_clock = clk_get_rate(vc4_hdmi->audio_clock);
230762306a36Sopenharmony_ci	rational_best_approximation(hsm_clock, samplerate,
230862306a36Sopenharmony_ci				    VC4_HD_MAI_SMP_N_MASK >>
230962306a36Sopenharmony_ci				    VC4_HD_MAI_SMP_N_SHIFT,
231062306a36Sopenharmony_ci				    (VC4_HD_MAI_SMP_M_MASK >>
231162306a36Sopenharmony_ci				     VC4_HD_MAI_SMP_M_SHIFT) + 1,
231262306a36Sopenharmony_ci				    &n, &m);
231362306a36Sopenharmony_ci
231462306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
231562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_SMP,
231662306a36Sopenharmony_ci		   VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) |
231762306a36Sopenharmony_ci		   VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M));
231862306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
231962306a36Sopenharmony_ci
232062306a36Sopenharmony_ci	drm_dev_exit(idx);
232162306a36Sopenharmony_ci}
232262306a36Sopenharmony_ci
232362306a36Sopenharmony_cistatic void vc4_hdmi_set_n_cts(struct vc4_hdmi *vc4_hdmi, unsigned int samplerate)
232462306a36Sopenharmony_ci{
232562306a36Sopenharmony_ci	const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
232662306a36Sopenharmony_ci	u32 n, cts;
232762306a36Sopenharmony_ci	u64 tmp;
232862306a36Sopenharmony_ci
232962306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
233062306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->hw_lock);
233162306a36Sopenharmony_ci
233262306a36Sopenharmony_ci	n = 128 * samplerate / 1000;
233362306a36Sopenharmony_ci	tmp = (u64)(mode->clock * 1000) * n;
233462306a36Sopenharmony_ci	do_div(tmp, 128 * samplerate);
233562306a36Sopenharmony_ci	cts = tmp;
233662306a36Sopenharmony_ci
233762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CRP_CFG,
233862306a36Sopenharmony_ci		   VC4_HDMI_CRP_CFG_EXTERNAL_CTS_EN |
233962306a36Sopenharmony_ci		   VC4_SET_FIELD(n, VC4_HDMI_CRP_CFG_N));
234062306a36Sopenharmony_ci
234162306a36Sopenharmony_ci	/*
234262306a36Sopenharmony_ci	 * We could get slightly more accurate clocks in some cases by
234362306a36Sopenharmony_ci	 * providing a CTS_1 value.  The two CTS values are alternated
234462306a36Sopenharmony_ci	 * between based on the period fields
234562306a36Sopenharmony_ci	 */
234662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CTS_0, cts);
234762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CTS_1, cts);
234862306a36Sopenharmony_ci}
234962306a36Sopenharmony_ci
235062306a36Sopenharmony_cistatic inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai)
235162306a36Sopenharmony_ci{
235262306a36Sopenharmony_ci	struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
235362306a36Sopenharmony_ci
235462306a36Sopenharmony_ci	return snd_soc_card_get_drvdata(card);
235562306a36Sopenharmony_ci}
235662306a36Sopenharmony_ci
235762306a36Sopenharmony_cistatic bool vc4_hdmi_audio_can_stream(struct vc4_hdmi *vc4_hdmi)
235862306a36Sopenharmony_ci{
235962306a36Sopenharmony_ci	struct drm_display_info *display = &vc4_hdmi->connector.display_info;
236062306a36Sopenharmony_ci
236162306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
236262306a36Sopenharmony_ci
236362306a36Sopenharmony_ci	/*
236462306a36Sopenharmony_ci	 * If the encoder is currently in DVI mode, treat the codec DAI
236562306a36Sopenharmony_ci	 * as missing.
236662306a36Sopenharmony_ci	 */
236762306a36Sopenharmony_ci	if (!display->is_hdmi)
236862306a36Sopenharmony_ci		return false;
236962306a36Sopenharmony_ci
237062306a36Sopenharmony_ci	return true;
237162306a36Sopenharmony_ci}
237262306a36Sopenharmony_ci
237362306a36Sopenharmony_cistatic int vc4_hdmi_audio_startup(struct device *dev, void *data)
237462306a36Sopenharmony_ci{
237562306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
237662306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
237762306a36Sopenharmony_ci	unsigned long flags;
237862306a36Sopenharmony_ci	int ret = 0;
237962306a36Sopenharmony_ci	int idx;
238062306a36Sopenharmony_ci
238162306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
238262306a36Sopenharmony_ci
238362306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx)) {
238462306a36Sopenharmony_ci		ret = -ENODEV;
238562306a36Sopenharmony_ci		goto out;
238662306a36Sopenharmony_ci	}
238762306a36Sopenharmony_ci
238862306a36Sopenharmony_ci	if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) {
238962306a36Sopenharmony_ci		ret = -ENODEV;
239062306a36Sopenharmony_ci		goto out_dev_exit;
239162306a36Sopenharmony_ci	}
239262306a36Sopenharmony_ci
239362306a36Sopenharmony_ci	vc4_hdmi->audio.streaming = true;
239462306a36Sopenharmony_ci
239562306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
239662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_CTL,
239762306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_RESET |
239862306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_FLUSH |
239962306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_DLATE |
240062306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_ERRORE |
240162306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_ERRORF);
240262306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
240362306a36Sopenharmony_ci
240462306a36Sopenharmony_ci	if (vc4_hdmi->variant->phy_rng_enable)
240562306a36Sopenharmony_ci		vc4_hdmi->variant->phy_rng_enable(vc4_hdmi);
240662306a36Sopenharmony_ci
240762306a36Sopenharmony_ciout_dev_exit:
240862306a36Sopenharmony_ci	drm_dev_exit(idx);
240962306a36Sopenharmony_ciout:
241062306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
241162306a36Sopenharmony_ci
241262306a36Sopenharmony_ci	return ret;
241362306a36Sopenharmony_ci}
241462306a36Sopenharmony_ci
241562306a36Sopenharmony_cistatic void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi)
241662306a36Sopenharmony_ci{
241762306a36Sopenharmony_ci	struct drm_encoder *encoder = &vc4_hdmi->encoder.base;
241862306a36Sopenharmony_ci	struct device *dev = &vc4_hdmi->pdev->dev;
241962306a36Sopenharmony_ci	unsigned long flags;
242062306a36Sopenharmony_ci	int ret;
242162306a36Sopenharmony_ci
242262306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->mutex);
242362306a36Sopenharmony_ci
242462306a36Sopenharmony_ci	vc4_hdmi->audio.streaming = false;
242562306a36Sopenharmony_ci	ret = vc4_hdmi_stop_packet(encoder, HDMI_INFOFRAME_TYPE_AUDIO, false);
242662306a36Sopenharmony_ci	if (ret)
242762306a36Sopenharmony_ci		dev_err(dev, "Failed to stop audio infoframe: %d\n", ret);
242862306a36Sopenharmony_ci
242962306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
243062306a36Sopenharmony_ci
243162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_RESET);
243262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_ERRORF);
243362306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_FLUSH);
243462306a36Sopenharmony_ci
243562306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
243662306a36Sopenharmony_ci}
243762306a36Sopenharmony_ci
243862306a36Sopenharmony_cistatic void vc4_hdmi_audio_shutdown(struct device *dev, void *data)
243962306a36Sopenharmony_ci{
244062306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
244162306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
244262306a36Sopenharmony_ci	unsigned long flags;
244362306a36Sopenharmony_ci	int idx;
244462306a36Sopenharmony_ci
244562306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
244662306a36Sopenharmony_ci
244762306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
244862306a36Sopenharmony_ci		goto out;
244962306a36Sopenharmony_ci
245062306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
245162306a36Sopenharmony_ci
245262306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_CTL,
245362306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_DLATE |
245462306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_ERRORE |
245562306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_ERRORF);
245662306a36Sopenharmony_ci
245762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
245862306a36Sopenharmony_ci
245962306a36Sopenharmony_ci	if (vc4_hdmi->variant->phy_rng_disable)
246062306a36Sopenharmony_ci		vc4_hdmi->variant->phy_rng_disable(vc4_hdmi);
246162306a36Sopenharmony_ci
246262306a36Sopenharmony_ci	vc4_hdmi->audio.streaming = false;
246362306a36Sopenharmony_ci	vc4_hdmi_audio_reset(vc4_hdmi);
246462306a36Sopenharmony_ci
246562306a36Sopenharmony_ci	drm_dev_exit(idx);
246662306a36Sopenharmony_ci
246762306a36Sopenharmony_ciout:
246862306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
246962306a36Sopenharmony_ci}
247062306a36Sopenharmony_ci
247162306a36Sopenharmony_cistatic int sample_rate_to_mai_fmt(int samplerate)
247262306a36Sopenharmony_ci{
247362306a36Sopenharmony_ci	switch (samplerate) {
247462306a36Sopenharmony_ci	case 8000:
247562306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_8000;
247662306a36Sopenharmony_ci	case 11025:
247762306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_11025;
247862306a36Sopenharmony_ci	case 12000:
247962306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_12000;
248062306a36Sopenharmony_ci	case 16000:
248162306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_16000;
248262306a36Sopenharmony_ci	case 22050:
248362306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_22050;
248462306a36Sopenharmony_ci	case 24000:
248562306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_24000;
248662306a36Sopenharmony_ci	case 32000:
248762306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_32000;
248862306a36Sopenharmony_ci	case 44100:
248962306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_44100;
249062306a36Sopenharmony_ci	case 48000:
249162306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_48000;
249262306a36Sopenharmony_ci	case 64000:
249362306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_64000;
249462306a36Sopenharmony_ci	case 88200:
249562306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_88200;
249662306a36Sopenharmony_ci	case 96000:
249762306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_96000;
249862306a36Sopenharmony_ci	case 128000:
249962306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_128000;
250062306a36Sopenharmony_ci	case 176400:
250162306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_176400;
250262306a36Sopenharmony_ci	case 192000:
250362306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_192000;
250462306a36Sopenharmony_ci	default:
250562306a36Sopenharmony_ci		return VC4_HDMI_MAI_SAMPLE_RATE_NOT_INDICATED;
250662306a36Sopenharmony_ci	}
250762306a36Sopenharmony_ci}
250862306a36Sopenharmony_ci
250962306a36Sopenharmony_ci/* HDMI audio codec callbacks */
251062306a36Sopenharmony_cistatic int vc4_hdmi_audio_prepare(struct device *dev, void *data,
251162306a36Sopenharmony_ci				  struct hdmi_codec_daifmt *daifmt,
251262306a36Sopenharmony_ci				  struct hdmi_codec_params *params)
251362306a36Sopenharmony_ci{
251462306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
251562306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
251662306a36Sopenharmony_ci	struct drm_encoder *encoder = &vc4_hdmi->encoder.base;
251762306a36Sopenharmony_ci	unsigned int sample_rate = params->sample_rate;
251862306a36Sopenharmony_ci	unsigned int channels = params->channels;
251962306a36Sopenharmony_ci	unsigned long flags;
252062306a36Sopenharmony_ci	u32 audio_packet_config, channel_mask;
252162306a36Sopenharmony_ci	u32 channel_map;
252262306a36Sopenharmony_ci	u32 mai_audio_format;
252362306a36Sopenharmony_ci	u32 mai_sample_rate;
252462306a36Sopenharmony_ci	int ret = 0;
252562306a36Sopenharmony_ci	int idx;
252662306a36Sopenharmony_ci
252762306a36Sopenharmony_ci	dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__,
252862306a36Sopenharmony_ci		sample_rate, params->sample_width, channels);
252962306a36Sopenharmony_ci
253062306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
253162306a36Sopenharmony_ci
253262306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx)) {
253362306a36Sopenharmony_ci		ret = -ENODEV;
253462306a36Sopenharmony_ci		goto out;
253562306a36Sopenharmony_ci	}
253662306a36Sopenharmony_ci
253762306a36Sopenharmony_ci	if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) {
253862306a36Sopenharmony_ci		ret = -EINVAL;
253962306a36Sopenharmony_ci		goto out_dev_exit;
254062306a36Sopenharmony_ci	}
254162306a36Sopenharmony_ci
254262306a36Sopenharmony_ci	vc4_hdmi_audio_set_mai_clock(vc4_hdmi, sample_rate);
254362306a36Sopenharmony_ci
254462306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
254562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_CTL,
254662306a36Sopenharmony_ci		   VC4_SET_FIELD(channels, VC4_HD_MAI_CTL_CHNUM) |
254762306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_WHOLSMP |
254862306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_CHALIGN |
254962306a36Sopenharmony_ci		   VC4_HD_MAI_CTL_ENABLE);
255062306a36Sopenharmony_ci
255162306a36Sopenharmony_ci	mai_sample_rate = sample_rate_to_mai_fmt(sample_rate);
255262306a36Sopenharmony_ci	if (params->iec.status[0] & IEC958_AES0_NONAUDIO &&
255362306a36Sopenharmony_ci	    params->channels == 8)
255462306a36Sopenharmony_ci		mai_audio_format = VC4_HDMI_MAI_FORMAT_HBR;
255562306a36Sopenharmony_ci	else
255662306a36Sopenharmony_ci		mai_audio_format = VC4_HDMI_MAI_FORMAT_PCM;
255762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_FMT,
255862306a36Sopenharmony_ci		   VC4_SET_FIELD(mai_sample_rate,
255962306a36Sopenharmony_ci				 VC4_HDMI_MAI_FORMAT_SAMPLE_RATE) |
256062306a36Sopenharmony_ci		   VC4_SET_FIELD(mai_audio_format,
256162306a36Sopenharmony_ci				 VC4_HDMI_MAI_FORMAT_AUDIO_FORMAT));
256262306a36Sopenharmony_ci
256362306a36Sopenharmony_ci	/* The B frame identifier should match the value used by alsa-lib (8) */
256462306a36Sopenharmony_ci	audio_packet_config =
256562306a36Sopenharmony_ci		VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_SAMPLE_FLAT |
256662306a36Sopenharmony_ci		VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_INACTIVE_CHANNELS |
256762306a36Sopenharmony_ci		VC4_SET_FIELD(0x8, VC4_HDMI_AUDIO_PACKET_B_FRAME_IDENTIFIER);
256862306a36Sopenharmony_ci
256962306a36Sopenharmony_ci	channel_mask = GENMASK(channels - 1, 0);
257062306a36Sopenharmony_ci	audio_packet_config |= VC4_SET_FIELD(channel_mask,
257162306a36Sopenharmony_ci					     VC4_HDMI_AUDIO_PACKET_CEA_MASK);
257262306a36Sopenharmony_ci
257362306a36Sopenharmony_ci	/* Set the MAI threshold */
257462306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_THR,
257562306a36Sopenharmony_ci		   VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_PANICHIGH) |
257662306a36Sopenharmony_ci		   VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_PANICLOW) |
257762306a36Sopenharmony_ci		   VC4_SET_FIELD(0x06, VC4_HD_MAI_THR_DREQHIGH) |
257862306a36Sopenharmony_ci		   VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_DREQLOW));
257962306a36Sopenharmony_ci
258062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_CONFIG,
258162306a36Sopenharmony_ci		   VC4_HDMI_MAI_CONFIG_BIT_REVERSE |
258262306a36Sopenharmony_ci		   VC4_HDMI_MAI_CONFIG_FORMAT_REVERSE |
258362306a36Sopenharmony_ci		   VC4_SET_FIELD(channel_mask, VC4_HDMI_MAI_CHANNEL_MASK));
258462306a36Sopenharmony_ci
258562306a36Sopenharmony_ci	channel_map = vc4_hdmi->variant->channel_map(vc4_hdmi, channel_mask);
258662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_MAI_CHANNEL_MAP, channel_map);
258762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_AUDIO_PACKET_CONFIG, audio_packet_config);
258862306a36Sopenharmony_ci
258962306a36Sopenharmony_ci	vc4_hdmi_set_n_cts(vc4_hdmi, sample_rate);
259062306a36Sopenharmony_ci
259162306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
259262306a36Sopenharmony_ci
259362306a36Sopenharmony_ci	memcpy(&vc4_hdmi->audio.infoframe, &params->cea, sizeof(params->cea));
259462306a36Sopenharmony_ci	vc4_hdmi_set_audio_infoframe(encoder);
259562306a36Sopenharmony_ci
259662306a36Sopenharmony_ciout_dev_exit:
259762306a36Sopenharmony_ci	drm_dev_exit(idx);
259862306a36Sopenharmony_ciout:
259962306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
260062306a36Sopenharmony_ci
260162306a36Sopenharmony_ci	return ret;
260262306a36Sopenharmony_ci}
260362306a36Sopenharmony_ci
260462306a36Sopenharmony_cistatic const struct snd_soc_component_driver vc4_hdmi_audio_cpu_dai_comp = {
260562306a36Sopenharmony_ci	.name = "vc4-hdmi-cpu-dai-component",
260662306a36Sopenharmony_ci	.legacy_dai_naming = 1,
260762306a36Sopenharmony_ci};
260862306a36Sopenharmony_ci
260962306a36Sopenharmony_cistatic int vc4_hdmi_audio_cpu_dai_probe(struct snd_soc_dai *dai)
261062306a36Sopenharmony_ci{
261162306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai);
261262306a36Sopenharmony_ci
261362306a36Sopenharmony_ci	snd_soc_dai_init_dma_data(dai, &vc4_hdmi->audio.dma_data, NULL);
261462306a36Sopenharmony_ci
261562306a36Sopenharmony_ci	return 0;
261662306a36Sopenharmony_ci}
261762306a36Sopenharmony_ci
261862306a36Sopenharmony_cistatic const struct snd_soc_dai_ops vc4_snd_dai_ops = {
261962306a36Sopenharmony_ci	.probe  = vc4_hdmi_audio_cpu_dai_probe,
262062306a36Sopenharmony_ci};
262162306a36Sopenharmony_ci
262262306a36Sopenharmony_cistatic struct snd_soc_dai_driver vc4_hdmi_audio_cpu_dai_drv = {
262362306a36Sopenharmony_ci	.name = "vc4-hdmi-cpu-dai",
262462306a36Sopenharmony_ci	.ops = &vc4_snd_dai_ops,
262562306a36Sopenharmony_ci	.playback = {
262662306a36Sopenharmony_ci		.stream_name = "Playback",
262762306a36Sopenharmony_ci		.channels_min = 1,
262862306a36Sopenharmony_ci		.channels_max = 8,
262962306a36Sopenharmony_ci		.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
263062306a36Sopenharmony_ci			 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
263162306a36Sopenharmony_ci			 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
263262306a36Sopenharmony_ci			 SNDRV_PCM_RATE_192000,
263362306a36Sopenharmony_ci		.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
263462306a36Sopenharmony_ci	},
263562306a36Sopenharmony_ci};
263662306a36Sopenharmony_ci
263762306a36Sopenharmony_cistatic const struct snd_dmaengine_pcm_config pcm_conf = {
263862306a36Sopenharmony_ci	.chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "audio-rx",
263962306a36Sopenharmony_ci	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
264062306a36Sopenharmony_ci};
264162306a36Sopenharmony_ci
264262306a36Sopenharmony_cistatic int vc4_hdmi_audio_get_eld(struct device *dev, void *data,
264362306a36Sopenharmony_ci				  uint8_t *buf, size_t len)
264462306a36Sopenharmony_ci{
264562306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
264662306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
264762306a36Sopenharmony_ci
264862306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
264962306a36Sopenharmony_ci	memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
265062306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
265162306a36Sopenharmony_ci
265262306a36Sopenharmony_ci	return 0;
265362306a36Sopenharmony_ci}
265462306a36Sopenharmony_ci
265562306a36Sopenharmony_cistatic const struct hdmi_codec_ops vc4_hdmi_codec_ops = {
265662306a36Sopenharmony_ci	.get_eld = vc4_hdmi_audio_get_eld,
265762306a36Sopenharmony_ci	.prepare = vc4_hdmi_audio_prepare,
265862306a36Sopenharmony_ci	.audio_shutdown = vc4_hdmi_audio_shutdown,
265962306a36Sopenharmony_ci	.audio_startup = vc4_hdmi_audio_startup,
266062306a36Sopenharmony_ci};
266162306a36Sopenharmony_ci
266262306a36Sopenharmony_cistatic struct hdmi_codec_pdata vc4_hdmi_codec_pdata = {
266362306a36Sopenharmony_ci	.ops = &vc4_hdmi_codec_ops,
266462306a36Sopenharmony_ci	.max_i2s_channels = 8,
266562306a36Sopenharmony_ci	.i2s = 1,
266662306a36Sopenharmony_ci};
266762306a36Sopenharmony_ci
266862306a36Sopenharmony_cistatic void vc4_hdmi_audio_codec_release(void *ptr)
266962306a36Sopenharmony_ci{
267062306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = ptr;
267162306a36Sopenharmony_ci
267262306a36Sopenharmony_ci	platform_device_unregister(vc4_hdmi->audio.codec_pdev);
267362306a36Sopenharmony_ci	vc4_hdmi->audio.codec_pdev = NULL;
267462306a36Sopenharmony_ci}
267562306a36Sopenharmony_ci
267662306a36Sopenharmony_cistatic int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
267762306a36Sopenharmony_ci{
267862306a36Sopenharmony_ci	const struct vc4_hdmi_register *mai_data =
267962306a36Sopenharmony_ci		&vc4_hdmi->variant->registers[HDMI_MAI_DATA];
268062306a36Sopenharmony_ci	struct snd_soc_dai_link *dai_link = &vc4_hdmi->audio.link;
268162306a36Sopenharmony_ci	struct snd_soc_card *card = &vc4_hdmi->audio.card;
268262306a36Sopenharmony_ci	struct device *dev = &vc4_hdmi->pdev->dev;
268362306a36Sopenharmony_ci	struct platform_device *codec_pdev;
268462306a36Sopenharmony_ci	const __be32 *addr;
268562306a36Sopenharmony_ci	int index, len;
268662306a36Sopenharmony_ci	int ret;
268762306a36Sopenharmony_ci
268862306a36Sopenharmony_ci	/*
268962306a36Sopenharmony_ci	 * ASoC makes it a bit hard to retrieve a pointer to the
269062306a36Sopenharmony_ci	 * vc4_hdmi structure. Registering the card will overwrite our
269162306a36Sopenharmony_ci	 * device drvdata with a pointer to the snd_soc_card structure,
269262306a36Sopenharmony_ci	 * which can then be used to retrieve whatever drvdata we want
269362306a36Sopenharmony_ci	 * to associate.
269462306a36Sopenharmony_ci	 *
269562306a36Sopenharmony_ci	 * However, that doesn't fly in the case where we wouldn't
269662306a36Sopenharmony_ci	 * register an ASoC card (because of an old DT that is missing
269762306a36Sopenharmony_ci	 * the dmas properties for example), then the card isn't
269862306a36Sopenharmony_ci	 * registered and the device drvdata wouldn't be set.
269962306a36Sopenharmony_ci	 *
270062306a36Sopenharmony_ci	 * We can deal with both cases by making sure a snd_soc_card
270162306a36Sopenharmony_ci	 * pointer and a vc4_hdmi structure are pointing to the same
270262306a36Sopenharmony_ci	 * memory address, so we can treat them indistinctly without any
270362306a36Sopenharmony_ci	 * issue.
270462306a36Sopenharmony_ci	 */
270562306a36Sopenharmony_ci	BUILD_BUG_ON(offsetof(struct vc4_hdmi_audio, card) != 0);
270662306a36Sopenharmony_ci	BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0);
270762306a36Sopenharmony_ci
270862306a36Sopenharmony_ci	if (!of_find_property(dev->of_node, "dmas", &len) || !len) {
270962306a36Sopenharmony_ci		dev_warn(dev,
271062306a36Sopenharmony_ci			 "'dmas' DT property is missing or empty, no HDMI audio\n");
271162306a36Sopenharmony_ci		return 0;
271262306a36Sopenharmony_ci	}
271362306a36Sopenharmony_ci
271462306a36Sopenharmony_ci	if (mai_data->reg != VC4_HD) {
271562306a36Sopenharmony_ci		WARN_ONCE(true, "MAI isn't in the HD block\n");
271662306a36Sopenharmony_ci		return -EINVAL;
271762306a36Sopenharmony_ci	}
271862306a36Sopenharmony_ci
271962306a36Sopenharmony_ci	/*
272062306a36Sopenharmony_ci	 * Get the physical address of VC4_HD_MAI_DATA. We need to retrieve
272162306a36Sopenharmony_ci	 * the bus address specified in the DT, because the physical address
272262306a36Sopenharmony_ci	 * (the one returned by platform_get_resource()) is not appropriate
272362306a36Sopenharmony_ci	 * for DMA transfers.
272462306a36Sopenharmony_ci	 * This VC/MMU should probably be exposed to avoid this kind of hacks.
272562306a36Sopenharmony_ci	 */
272662306a36Sopenharmony_ci	index = of_property_match_string(dev->of_node, "reg-names", "hd");
272762306a36Sopenharmony_ci	/* Before BCM2711, we don't have a named register range */
272862306a36Sopenharmony_ci	if (index < 0)
272962306a36Sopenharmony_ci		index = 1;
273062306a36Sopenharmony_ci
273162306a36Sopenharmony_ci	addr = of_get_address(dev->of_node, index, NULL, NULL);
273262306a36Sopenharmony_ci
273362306a36Sopenharmony_ci	vc4_hdmi->audio.dma_data.addr = be32_to_cpup(addr) + mai_data->offset;
273462306a36Sopenharmony_ci	vc4_hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
273562306a36Sopenharmony_ci	vc4_hdmi->audio.dma_data.maxburst = 2;
273662306a36Sopenharmony_ci
273762306a36Sopenharmony_ci	/*
273862306a36Sopenharmony_ci	 * NOTE: Strictly speaking, we should probably use a DRM-managed
273962306a36Sopenharmony_ci	 * registration there to avoid removing all the audio components
274062306a36Sopenharmony_ci	 * by the time the driver doesn't have any user anymore.
274162306a36Sopenharmony_ci	 *
274262306a36Sopenharmony_ci	 * However, the ASoC core uses a number of devm_kzalloc calls
274362306a36Sopenharmony_ci	 * when registering, even when using non-device-managed
274462306a36Sopenharmony_ci	 * functions (such as in snd_soc_register_component()).
274562306a36Sopenharmony_ci	 *
274662306a36Sopenharmony_ci	 * If we call snd_soc_unregister_component() in a DRM-managed
274762306a36Sopenharmony_ci	 * action, the device-managed actions have already been executed
274862306a36Sopenharmony_ci	 * and thus we would access memory that has been freed.
274962306a36Sopenharmony_ci	 *
275062306a36Sopenharmony_ci	 * Using device-managed hooks here probably leaves us open to a
275162306a36Sopenharmony_ci	 * bunch of issues if userspace still has a handle on the ALSA
275262306a36Sopenharmony_ci	 * device when the device is removed. However, this is mitigated
275362306a36Sopenharmony_ci	 * by the use of drm_dev_enter()/drm_dev_exit() in the audio
275462306a36Sopenharmony_ci	 * path to prevent the access to the device resources if it
275562306a36Sopenharmony_ci	 * isn't there anymore.
275662306a36Sopenharmony_ci	 *
275762306a36Sopenharmony_ci	 * Then, the vc4_hdmi structure is DRM-managed and thus only
275862306a36Sopenharmony_ci	 * freed whenever the last user has closed the DRM device file.
275962306a36Sopenharmony_ci	 * It should thus outlive ALSA in most situations.
276062306a36Sopenharmony_ci	 */
276162306a36Sopenharmony_ci	ret = devm_snd_dmaengine_pcm_register(dev, &pcm_conf, 0);
276262306a36Sopenharmony_ci	if (ret) {
276362306a36Sopenharmony_ci		dev_err(dev, "Could not register PCM component: %d\n", ret);
276462306a36Sopenharmony_ci		return ret;
276562306a36Sopenharmony_ci	}
276662306a36Sopenharmony_ci
276762306a36Sopenharmony_ci	ret = devm_snd_soc_register_component(dev, &vc4_hdmi_audio_cpu_dai_comp,
276862306a36Sopenharmony_ci					      &vc4_hdmi_audio_cpu_dai_drv, 1);
276962306a36Sopenharmony_ci	if (ret) {
277062306a36Sopenharmony_ci		dev_err(dev, "Could not register CPU DAI: %d\n", ret);
277162306a36Sopenharmony_ci		return ret;
277262306a36Sopenharmony_ci	}
277362306a36Sopenharmony_ci
277462306a36Sopenharmony_ci	codec_pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
277562306a36Sopenharmony_ci						   PLATFORM_DEVID_AUTO,
277662306a36Sopenharmony_ci						   &vc4_hdmi_codec_pdata,
277762306a36Sopenharmony_ci						   sizeof(vc4_hdmi_codec_pdata));
277862306a36Sopenharmony_ci	if (IS_ERR(codec_pdev)) {
277962306a36Sopenharmony_ci		dev_err(dev, "Couldn't register the HDMI codec: %ld\n", PTR_ERR(codec_pdev));
278062306a36Sopenharmony_ci		return PTR_ERR(codec_pdev);
278162306a36Sopenharmony_ci	}
278262306a36Sopenharmony_ci	vc4_hdmi->audio.codec_pdev = codec_pdev;
278362306a36Sopenharmony_ci
278462306a36Sopenharmony_ci	ret = devm_add_action_or_reset(dev, vc4_hdmi_audio_codec_release, vc4_hdmi);
278562306a36Sopenharmony_ci	if (ret)
278662306a36Sopenharmony_ci		return ret;
278762306a36Sopenharmony_ci
278862306a36Sopenharmony_ci	dai_link->cpus		= &vc4_hdmi->audio.cpu;
278962306a36Sopenharmony_ci	dai_link->codecs	= &vc4_hdmi->audio.codec;
279062306a36Sopenharmony_ci	dai_link->platforms	= &vc4_hdmi->audio.platform;
279162306a36Sopenharmony_ci
279262306a36Sopenharmony_ci	dai_link->num_cpus	= 1;
279362306a36Sopenharmony_ci	dai_link->num_codecs	= 1;
279462306a36Sopenharmony_ci	dai_link->num_platforms	= 1;
279562306a36Sopenharmony_ci
279662306a36Sopenharmony_ci	dai_link->name = "MAI";
279762306a36Sopenharmony_ci	dai_link->stream_name = "MAI PCM";
279862306a36Sopenharmony_ci	dai_link->codecs->dai_name = "i2s-hifi";
279962306a36Sopenharmony_ci	dai_link->cpus->dai_name = dev_name(dev);
280062306a36Sopenharmony_ci	dai_link->codecs->name = dev_name(&codec_pdev->dev);
280162306a36Sopenharmony_ci	dai_link->platforms->name = dev_name(dev);
280262306a36Sopenharmony_ci
280362306a36Sopenharmony_ci	card->dai_link = dai_link;
280462306a36Sopenharmony_ci	card->num_links = 1;
280562306a36Sopenharmony_ci	card->name = vc4_hdmi->variant->card_name;
280662306a36Sopenharmony_ci	card->driver_name = "vc4-hdmi";
280762306a36Sopenharmony_ci	card->dev = dev;
280862306a36Sopenharmony_ci	card->owner = THIS_MODULE;
280962306a36Sopenharmony_ci
281062306a36Sopenharmony_ci	/*
281162306a36Sopenharmony_ci	 * Be careful, snd_soc_register_card() calls dev_set_drvdata() and
281262306a36Sopenharmony_ci	 * stores a pointer to the snd card object in dev->driver_data. This
281362306a36Sopenharmony_ci	 * means we cannot use it for something else. The hdmi back-pointer is
281462306a36Sopenharmony_ci	 * now stored in card->drvdata and should be retrieved with
281562306a36Sopenharmony_ci	 * snd_soc_card_get_drvdata() if needed.
281662306a36Sopenharmony_ci	 */
281762306a36Sopenharmony_ci	snd_soc_card_set_drvdata(card, vc4_hdmi);
281862306a36Sopenharmony_ci	ret = devm_snd_soc_register_card(dev, card);
281962306a36Sopenharmony_ci	if (ret)
282062306a36Sopenharmony_ci		dev_err_probe(dev, ret, "Could not register sound card\n");
282162306a36Sopenharmony_ci
282262306a36Sopenharmony_ci	return ret;
282362306a36Sopenharmony_ci
282462306a36Sopenharmony_ci}
282562306a36Sopenharmony_ci
282662306a36Sopenharmony_cistatic irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv)
282762306a36Sopenharmony_ci{
282862306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = priv;
282962306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
283062306a36Sopenharmony_ci	struct drm_device *dev = connector->dev;
283162306a36Sopenharmony_ci
283262306a36Sopenharmony_ci	if (dev && dev->registered)
283362306a36Sopenharmony_ci		drm_connector_helper_hpd_irq_event(connector);
283462306a36Sopenharmony_ci
283562306a36Sopenharmony_ci	return IRQ_HANDLED;
283662306a36Sopenharmony_ci}
283762306a36Sopenharmony_ci
283862306a36Sopenharmony_cistatic int vc4_hdmi_hotplug_init(struct vc4_hdmi *vc4_hdmi)
283962306a36Sopenharmony_ci{
284062306a36Sopenharmony_ci	struct drm_connector *connector = &vc4_hdmi->connector;
284162306a36Sopenharmony_ci	struct platform_device *pdev = vc4_hdmi->pdev;
284262306a36Sopenharmony_ci	int ret;
284362306a36Sopenharmony_ci
284462306a36Sopenharmony_ci	if (vc4_hdmi->variant->external_irq_controller) {
284562306a36Sopenharmony_ci		unsigned int hpd_con = platform_get_irq_byname(pdev, "hpd-connected");
284662306a36Sopenharmony_ci		unsigned int hpd_rm = platform_get_irq_byname(pdev, "hpd-removed");
284762306a36Sopenharmony_ci
284862306a36Sopenharmony_ci		ret = devm_request_threaded_irq(&pdev->dev, hpd_con,
284962306a36Sopenharmony_ci						NULL,
285062306a36Sopenharmony_ci						vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT,
285162306a36Sopenharmony_ci						"vc4 hdmi hpd connected", vc4_hdmi);
285262306a36Sopenharmony_ci		if (ret)
285362306a36Sopenharmony_ci			return ret;
285462306a36Sopenharmony_ci
285562306a36Sopenharmony_ci		ret = devm_request_threaded_irq(&pdev->dev, hpd_rm,
285662306a36Sopenharmony_ci						NULL,
285762306a36Sopenharmony_ci						vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT,
285862306a36Sopenharmony_ci						"vc4 hdmi hpd disconnected", vc4_hdmi);
285962306a36Sopenharmony_ci		if (ret)
286062306a36Sopenharmony_ci			return ret;
286162306a36Sopenharmony_ci
286262306a36Sopenharmony_ci		connector->polled = DRM_CONNECTOR_POLL_HPD;
286362306a36Sopenharmony_ci	}
286462306a36Sopenharmony_ci
286562306a36Sopenharmony_ci	return 0;
286662306a36Sopenharmony_ci}
286762306a36Sopenharmony_ci
286862306a36Sopenharmony_ci#ifdef CONFIG_DRM_VC4_HDMI_CEC
286962306a36Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler_rx_thread(int irq, void *priv)
287062306a36Sopenharmony_ci{
287162306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = priv;
287262306a36Sopenharmony_ci
287362306a36Sopenharmony_ci	if (vc4_hdmi->cec_rx_msg.len)
287462306a36Sopenharmony_ci		cec_received_msg(vc4_hdmi->cec_adap,
287562306a36Sopenharmony_ci				 &vc4_hdmi->cec_rx_msg);
287662306a36Sopenharmony_ci
287762306a36Sopenharmony_ci	return IRQ_HANDLED;
287862306a36Sopenharmony_ci}
287962306a36Sopenharmony_ci
288062306a36Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler_tx_thread(int irq, void *priv)
288162306a36Sopenharmony_ci{
288262306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = priv;
288362306a36Sopenharmony_ci
288462306a36Sopenharmony_ci	if (vc4_hdmi->cec_tx_ok) {
288562306a36Sopenharmony_ci		cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_OK,
288662306a36Sopenharmony_ci				  0, 0, 0, 0);
288762306a36Sopenharmony_ci	} else {
288862306a36Sopenharmony_ci		/*
288962306a36Sopenharmony_ci		 * This CEC implementation makes 1 retry, so if we
289062306a36Sopenharmony_ci		 * get a NACK, then that means it made 2 attempts.
289162306a36Sopenharmony_ci		 */
289262306a36Sopenharmony_ci		cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_NACK,
289362306a36Sopenharmony_ci				  0, 2, 0, 0);
289462306a36Sopenharmony_ci	}
289562306a36Sopenharmony_ci	return IRQ_HANDLED;
289662306a36Sopenharmony_ci}
289762306a36Sopenharmony_ci
289862306a36Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler_thread(int irq, void *priv)
289962306a36Sopenharmony_ci{
290062306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = priv;
290162306a36Sopenharmony_ci	irqreturn_t ret;
290262306a36Sopenharmony_ci
290362306a36Sopenharmony_ci	if (vc4_hdmi->cec_irq_was_rx)
290462306a36Sopenharmony_ci		ret = vc4_cec_irq_handler_rx_thread(irq, priv);
290562306a36Sopenharmony_ci	else
290662306a36Sopenharmony_ci		ret = vc4_cec_irq_handler_tx_thread(irq, priv);
290762306a36Sopenharmony_ci
290862306a36Sopenharmony_ci	return ret;
290962306a36Sopenharmony_ci}
291062306a36Sopenharmony_ci
291162306a36Sopenharmony_cistatic void vc4_cec_read_msg(struct vc4_hdmi *vc4_hdmi, u32 cntrl1)
291262306a36Sopenharmony_ci{
291362306a36Sopenharmony_ci	struct drm_device *dev = vc4_hdmi->connector.dev;
291462306a36Sopenharmony_ci	struct cec_msg *msg = &vc4_hdmi->cec_rx_msg;
291562306a36Sopenharmony_ci	unsigned int i;
291662306a36Sopenharmony_ci
291762306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->hw_lock);
291862306a36Sopenharmony_ci
291962306a36Sopenharmony_ci	msg->len = 1 + ((cntrl1 & VC4_HDMI_CEC_REC_WRD_CNT_MASK) >>
292062306a36Sopenharmony_ci					VC4_HDMI_CEC_REC_WRD_CNT_SHIFT);
292162306a36Sopenharmony_ci
292262306a36Sopenharmony_ci	if (msg->len > 16) {
292362306a36Sopenharmony_ci		drm_err(dev, "Attempting to read too much data (%d)\n", msg->len);
292462306a36Sopenharmony_ci		return;
292562306a36Sopenharmony_ci	}
292662306a36Sopenharmony_ci
292762306a36Sopenharmony_ci	for (i = 0; i < msg->len; i += 4) {
292862306a36Sopenharmony_ci		u32 val = HDMI_READ(HDMI_CEC_RX_DATA_1 + (i >> 2));
292962306a36Sopenharmony_ci
293062306a36Sopenharmony_ci		msg->msg[i] = val & 0xff;
293162306a36Sopenharmony_ci		msg->msg[i + 1] = (val >> 8) & 0xff;
293262306a36Sopenharmony_ci		msg->msg[i + 2] = (val >> 16) & 0xff;
293362306a36Sopenharmony_ci		msg->msg[i + 3] = (val >> 24) & 0xff;
293462306a36Sopenharmony_ci	}
293562306a36Sopenharmony_ci}
293662306a36Sopenharmony_ci
293762306a36Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler_tx_bare_locked(struct vc4_hdmi *vc4_hdmi)
293862306a36Sopenharmony_ci{
293962306a36Sopenharmony_ci	u32 cntrl1;
294062306a36Sopenharmony_ci
294162306a36Sopenharmony_ci	/*
294262306a36Sopenharmony_ci	 * We don't need to protect the register access using
294362306a36Sopenharmony_ci	 * drm_dev_enter() there because the interrupt handler lifetime
294462306a36Sopenharmony_ci	 * is tied to the device itself, and not to the DRM device.
294562306a36Sopenharmony_ci	 *
294662306a36Sopenharmony_ci	 * So when the device will be gone, one of the first thing we
294762306a36Sopenharmony_ci	 * will be doing will be to unregister the interrupt handler,
294862306a36Sopenharmony_ci	 * and then unregister the DRM device. drm_dev_enter() would
294962306a36Sopenharmony_ci	 * thus always succeed if we are here.
295062306a36Sopenharmony_ci	 */
295162306a36Sopenharmony_ci
295262306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->hw_lock);
295362306a36Sopenharmony_ci
295462306a36Sopenharmony_ci	cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1);
295562306a36Sopenharmony_ci	vc4_hdmi->cec_tx_ok = cntrl1 & VC4_HDMI_CEC_TX_STATUS_GOOD;
295662306a36Sopenharmony_ci	cntrl1 &= ~VC4_HDMI_CEC_START_XMIT_BEGIN;
295762306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_1, cntrl1);
295862306a36Sopenharmony_ci
295962306a36Sopenharmony_ci	return IRQ_WAKE_THREAD;
296062306a36Sopenharmony_ci}
296162306a36Sopenharmony_ci
296262306a36Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler_tx_bare(int irq, void *priv)
296362306a36Sopenharmony_ci{
296462306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = priv;
296562306a36Sopenharmony_ci	irqreturn_t ret;
296662306a36Sopenharmony_ci
296762306a36Sopenharmony_ci	spin_lock(&vc4_hdmi->hw_lock);
296862306a36Sopenharmony_ci	ret = vc4_cec_irq_handler_tx_bare_locked(vc4_hdmi);
296962306a36Sopenharmony_ci	spin_unlock(&vc4_hdmi->hw_lock);
297062306a36Sopenharmony_ci
297162306a36Sopenharmony_ci	return ret;
297262306a36Sopenharmony_ci}
297362306a36Sopenharmony_ci
297462306a36Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler_rx_bare_locked(struct vc4_hdmi *vc4_hdmi)
297562306a36Sopenharmony_ci{
297662306a36Sopenharmony_ci	u32 cntrl1;
297762306a36Sopenharmony_ci
297862306a36Sopenharmony_ci	lockdep_assert_held(&vc4_hdmi->hw_lock);
297962306a36Sopenharmony_ci
298062306a36Sopenharmony_ci	/*
298162306a36Sopenharmony_ci	 * We don't need to protect the register access using
298262306a36Sopenharmony_ci	 * drm_dev_enter() there because the interrupt handler lifetime
298362306a36Sopenharmony_ci	 * is tied to the device itself, and not to the DRM device.
298462306a36Sopenharmony_ci	 *
298562306a36Sopenharmony_ci	 * So when the device will be gone, one of the first thing we
298662306a36Sopenharmony_ci	 * will be doing will be to unregister the interrupt handler,
298762306a36Sopenharmony_ci	 * and then unregister the DRM device. drm_dev_enter() would
298862306a36Sopenharmony_ci	 * thus always succeed if we are here.
298962306a36Sopenharmony_ci	 */
299062306a36Sopenharmony_ci
299162306a36Sopenharmony_ci	vc4_hdmi->cec_rx_msg.len = 0;
299262306a36Sopenharmony_ci	cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1);
299362306a36Sopenharmony_ci	vc4_cec_read_msg(vc4_hdmi, cntrl1);
299462306a36Sopenharmony_ci	cntrl1 |= VC4_HDMI_CEC_CLEAR_RECEIVE_OFF;
299562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_1, cntrl1);
299662306a36Sopenharmony_ci	cntrl1 &= ~VC4_HDMI_CEC_CLEAR_RECEIVE_OFF;
299762306a36Sopenharmony_ci
299862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_1, cntrl1);
299962306a36Sopenharmony_ci
300062306a36Sopenharmony_ci	return IRQ_WAKE_THREAD;
300162306a36Sopenharmony_ci}
300262306a36Sopenharmony_ci
300362306a36Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler_rx_bare(int irq, void *priv)
300462306a36Sopenharmony_ci{
300562306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = priv;
300662306a36Sopenharmony_ci	irqreturn_t ret;
300762306a36Sopenharmony_ci
300862306a36Sopenharmony_ci	spin_lock(&vc4_hdmi->hw_lock);
300962306a36Sopenharmony_ci	ret = vc4_cec_irq_handler_rx_bare_locked(vc4_hdmi);
301062306a36Sopenharmony_ci	spin_unlock(&vc4_hdmi->hw_lock);
301162306a36Sopenharmony_ci
301262306a36Sopenharmony_ci	return ret;
301362306a36Sopenharmony_ci}
301462306a36Sopenharmony_ci
301562306a36Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler(int irq, void *priv)
301662306a36Sopenharmony_ci{
301762306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = priv;
301862306a36Sopenharmony_ci	u32 stat = HDMI_READ(HDMI_CEC_CPU_STATUS);
301962306a36Sopenharmony_ci	irqreturn_t ret;
302062306a36Sopenharmony_ci	u32 cntrl5;
302162306a36Sopenharmony_ci
302262306a36Sopenharmony_ci	/*
302362306a36Sopenharmony_ci	 * We don't need to protect the register access using
302462306a36Sopenharmony_ci	 * drm_dev_enter() there because the interrupt handler lifetime
302562306a36Sopenharmony_ci	 * is tied to the device itself, and not to the DRM device.
302662306a36Sopenharmony_ci	 *
302762306a36Sopenharmony_ci	 * So when the device will be gone, one of the first thing we
302862306a36Sopenharmony_ci	 * will be doing will be to unregister the interrupt handler,
302962306a36Sopenharmony_ci	 * and then unregister the DRM device. drm_dev_enter() would
303062306a36Sopenharmony_ci	 * thus always succeed if we are here.
303162306a36Sopenharmony_ci	 */
303262306a36Sopenharmony_ci
303362306a36Sopenharmony_ci	if (!(stat & VC4_HDMI_CPU_CEC))
303462306a36Sopenharmony_ci		return IRQ_NONE;
303562306a36Sopenharmony_ci
303662306a36Sopenharmony_ci	spin_lock(&vc4_hdmi->hw_lock);
303762306a36Sopenharmony_ci	cntrl5 = HDMI_READ(HDMI_CEC_CNTRL_5);
303862306a36Sopenharmony_ci	vc4_hdmi->cec_irq_was_rx = cntrl5 & VC4_HDMI_CEC_RX_CEC_INT;
303962306a36Sopenharmony_ci	if (vc4_hdmi->cec_irq_was_rx)
304062306a36Sopenharmony_ci		ret = vc4_cec_irq_handler_rx_bare_locked(vc4_hdmi);
304162306a36Sopenharmony_ci	else
304262306a36Sopenharmony_ci		ret = vc4_cec_irq_handler_tx_bare_locked(vc4_hdmi);
304362306a36Sopenharmony_ci
304462306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CPU_CLEAR, VC4_HDMI_CPU_CEC);
304562306a36Sopenharmony_ci	spin_unlock(&vc4_hdmi->hw_lock);
304662306a36Sopenharmony_ci
304762306a36Sopenharmony_ci	return ret;
304862306a36Sopenharmony_ci}
304962306a36Sopenharmony_ci
305062306a36Sopenharmony_cistatic int vc4_hdmi_cec_enable(struct cec_adapter *adap)
305162306a36Sopenharmony_ci{
305262306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
305362306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
305462306a36Sopenharmony_ci	/* clock period in microseconds */
305562306a36Sopenharmony_ci	const u32 usecs = 1000000 / CEC_CLOCK_FREQ;
305662306a36Sopenharmony_ci	unsigned long flags;
305762306a36Sopenharmony_ci	u32 val;
305862306a36Sopenharmony_ci	int ret;
305962306a36Sopenharmony_ci	int idx;
306062306a36Sopenharmony_ci
306162306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
306262306a36Sopenharmony_ci		/*
306362306a36Sopenharmony_ci		 * We can't return an error code, because the CEC
306462306a36Sopenharmony_ci		 * framework will emit WARN_ON messages at unbind
306562306a36Sopenharmony_ci		 * otherwise.
306662306a36Sopenharmony_ci		 */
306762306a36Sopenharmony_ci		return 0;
306862306a36Sopenharmony_ci
306962306a36Sopenharmony_ci	ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev);
307062306a36Sopenharmony_ci	if (ret) {
307162306a36Sopenharmony_ci		drm_dev_exit(idx);
307262306a36Sopenharmony_ci		return ret;
307362306a36Sopenharmony_ci	}
307462306a36Sopenharmony_ci
307562306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
307662306a36Sopenharmony_ci
307762306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
307862306a36Sopenharmony_ci
307962306a36Sopenharmony_ci	val = HDMI_READ(HDMI_CEC_CNTRL_5);
308062306a36Sopenharmony_ci	val &= ~(VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET |
308162306a36Sopenharmony_ci		 VC4_HDMI_CEC_CNT_TO_4700_US_MASK |
308262306a36Sopenharmony_ci		 VC4_HDMI_CEC_CNT_TO_4500_US_MASK);
308362306a36Sopenharmony_ci	val |= ((4700 / usecs) << VC4_HDMI_CEC_CNT_TO_4700_US_SHIFT) |
308462306a36Sopenharmony_ci	       ((4500 / usecs) << VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT);
308562306a36Sopenharmony_ci
308662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_5, val |
308762306a36Sopenharmony_ci		   VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
308862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_5, val);
308962306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_2,
309062306a36Sopenharmony_ci		   ((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) |
309162306a36Sopenharmony_ci		   ((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) |
309262306a36Sopenharmony_ci		   ((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) |
309362306a36Sopenharmony_ci		   ((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) |
309462306a36Sopenharmony_ci		   ((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT));
309562306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_3,
309662306a36Sopenharmony_ci		   ((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) |
309762306a36Sopenharmony_ci		   ((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) |
309862306a36Sopenharmony_ci		   ((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) |
309962306a36Sopenharmony_ci		   ((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT));
310062306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_4,
310162306a36Sopenharmony_ci		   ((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) |
310262306a36Sopenharmony_ci		   ((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) |
310362306a36Sopenharmony_ci		   ((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) |
310462306a36Sopenharmony_ci		   ((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT));
310562306a36Sopenharmony_ci
310662306a36Sopenharmony_ci	if (!vc4_hdmi->variant->external_irq_controller)
310762306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CEC_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC);
310862306a36Sopenharmony_ci
310962306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
311062306a36Sopenharmony_ci
311162306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
311262306a36Sopenharmony_ci	drm_dev_exit(idx);
311362306a36Sopenharmony_ci
311462306a36Sopenharmony_ci	return 0;
311562306a36Sopenharmony_ci}
311662306a36Sopenharmony_ci
311762306a36Sopenharmony_cistatic int vc4_hdmi_cec_disable(struct cec_adapter *adap)
311862306a36Sopenharmony_ci{
311962306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
312062306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
312162306a36Sopenharmony_ci	unsigned long flags;
312262306a36Sopenharmony_ci	int idx;
312362306a36Sopenharmony_ci
312462306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
312562306a36Sopenharmony_ci		/*
312662306a36Sopenharmony_ci		 * We can't return an error code, because the CEC
312762306a36Sopenharmony_ci		 * framework will emit WARN_ON messages at unbind
312862306a36Sopenharmony_ci		 * otherwise.
312962306a36Sopenharmony_ci		 */
313062306a36Sopenharmony_ci		return 0;
313162306a36Sopenharmony_ci
313262306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
313362306a36Sopenharmony_ci
313462306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
313562306a36Sopenharmony_ci
313662306a36Sopenharmony_ci	if (!vc4_hdmi->variant->external_irq_controller)
313762306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, VC4_HDMI_CPU_CEC);
313862306a36Sopenharmony_ci
313962306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_5, HDMI_READ(HDMI_CEC_CNTRL_5) |
314062306a36Sopenharmony_ci		   VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
314162306a36Sopenharmony_ci
314262306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
314362306a36Sopenharmony_ci
314462306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
314562306a36Sopenharmony_ci
314662306a36Sopenharmony_ci	pm_runtime_put(&vc4_hdmi->pdev->dev);
314762306a36Sopenharmony_ci
314862306a36Sopenharmony_ci	drm_dev_exit(idx);
314962306a36Sopenharmony_ci
315062306a36Sopenharmony_ci	return 0;
315162306a36Sopenharmony_ci}
315262306a36Sopenharmony_ci
315362306a36Sopenharmony_cistatic int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
315462306a36Sopenharmony_ci{
315562306a36Sopenharmony_ci	if (enable)
315662306a36Sopenharmony_ci		return vc4_hdmi_cec_enable(adap);
315762306a36Sopenharmony_ci	else
315862306a36Sopenharmony_ci		return vc4_hdmi_cec_disable(adap);
315962306a36Sopenharmony_ci}
316062306a36Sopenharmony_ci
316162306a36Sopenharmony_cistatic int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
316262306a36Sopenharmony_ci{
316362306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
316462306a36Sopenharmony_ci	struct drm_device *drm = vc4_hdmi->connector.dev;
316562306a36Sopenharmony_ci	unsigned long flags;
316662306a36Sopenharmony_ci	int idx;
316762306a36Sopenharmony_ci
316862306a36Sopenharmony_ci	if (!drm_dev_enter(drm, &idx))
316962306a36Sopenharmony_ci		/*
317062306a36Sopenharmony_ci		 * We can't return an error code, because the CEC
317162306a36Sopenharmony_ci		 * framework will emit WARN_ON messages at unbind
317262306a36Sopenharmony_ci		 * otherwise.
317362306a36Sopenharmony_ci		 */
317462306a36Sopenharmony_ci		return 0;
317562306a36Sopenharmony_ci
317662306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
317762306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
317862306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_1,
317962306a36Sopenharmony_ci		   (HDMI_READ(HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) |
318062306a36Sopenharmony_ci		   (log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT);
318162306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
318262306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
318362306a36Sopenharmony_ci
318462306a36Sopenharmony_ci	drm_dev_exit(idx);
318562306a36Sopenharmony_ci
318662306a36Sopenharmony_ci	return 0;
318762306a36Sopenharmony_ci}
318862306a36Sopenharmony_ci
318962306a36Sopenharmony_cistatic int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
319062306a36Sopenharmony_ci				      u32 signal_free_time, struct cec_msg *msg)
319162306a36Sopenharmony_ci{
319262306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
319362306a36Sopenharmony_ci	struct drm_device *dev = vc4_hdmi->connector.dev;
319462306a36Sopenharmony_ci	unsigned long flags;
319562306a36Sopenharmony_ci	u32 val;
319662306a36Sopenharmony_ci	unsigned int i;
319762306a36Sopenharmony_ci	int idx;
319862306a36Sopenharmony_ci
319962306a36Sopenharmony_ci	if (!drm_dev_enter(dev, &idx))
320062306a36Sopenharmony_ci		return -ENODEV;
320162306a36Sopenharmony_ci
320262306a36Sopenharmony_ci	if (msg->len > 16) {
320362306a36Sopenharmony_ci		drm_err(dev, "Attempting to transmit too much data (%d)\n", msg->len);
320462306a36Sopenharmony_ci		drm_dev_exit(idx);
320562306a36Sopenharmony_ci		return -ENOMEM;
320662306a36Sopenharmony_ci	}
320762306a36Sopenharmony_ci
320862306a36Sopenharmony_ci	mutex_lock(&vc4_hdmi->mutex);
320962306a36Sopenharmony_ci
321062306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
321162306a36Sopenharmony_ci
321262306a36Sopenharmony_ci	for (i = 0; i < msg->len; i += 4)
321362306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CEC_TX_DATA_1 + (i >> 2),
321462306a36Sopenharmony_ci			   (msg->msg[i]) |
321562306a36Sopenharmony_ci			   (msg->msg[i + 1] << 8) |
321662306a36Sopenharmony_ci			   (msg->msg[i + 2] << 16) |
321762306a36Sopenharmony_ci			   (msg->msg[i + 3] << 24));
321862306a36Sopenharmony_ci
321962306a36Sopenharmony_ci	val = HDMI_READ(HDMI_CEC_CNTRL_1);
322062306a36Sopenharmony_ci	val &= ~VC4_HDMI_CEC_START_XMIT_BEGIN;
322162306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_1, val);
322262306a36Sopenharmony_ci	val &= ~VC4_HDMI_CEC_MESSAGE_LENGTH_MASK;
322362306a36Sopenharmony_ci	val |= (msg->len - 1) << VC4_HDMI_CEC_MESSAGE_LENGTH_SHIFT;
322462306a36Sopenharmony_ci	val |= VC4_HDMI_CEC_START_XMIT_BEGIN;
322562306a36Sopenharmony_ci
322662306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_1, val);
322762306a36Sopenharmony_ci
322862306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
322962306a36Sopenharmony_ci	mutex_unlock(&vc4_hdmi->mutex);
323062306a36Sopenharmony_ci	drm_dev_exit(idx);
323162306a36Sopenharmony_ci
323262306a36Sopenharmony_ci	return 0;
323362306a36Sopenharmony_ci}
323462306a36Sopenharmony_ci
323562306a36Sopenharmony_cistatic const struct cec_adap_ops vc4_hdmi_cec_adap_ops = {
323662306a36Sopenharmony_ci	.adap_enable = vc4_hdmi_cec_adap_enable,
323762306a36Sopenharmony_ci	.adap_log_addr = vc4_hdmi_cec_adap_log_addr,
323862306a36Sopenharmony_ci	.adap_transmit = vc4_hdmi_cec_adap_transmit,
323962306a36Sopenharmony_ci};
324062306a36Sopenharmony_ci
324162306a36Sopenharmony_cistatic void vc4_hdmi_cec_release(void *ptr)
324262306a36Sopenharmony_ci{
324362306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = ptr;
324462306a36Sopenharmony_ci
324562306a36Sopenharmony_ci	cec_unregister_adapter(vc4_hdmi->cec_adap);
324662306a36Sopenharmony_ci	vc4_hdmi->cec_adap = NULL;
324762306a36Sopenharmony_ci}
324862306a36Sopenharmony_ci
324962306a36Sopenharmony_cistatic int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
325062306a36Sopenharmony_ci{
325162306a36Sopenharmony_ci	struct cec_connector_info conn_info;
325262306a36Sopenharmony_ci	struct platform_device *pdev = vc4_hdmi->pdev;
325362306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
325462306a36Sopenharmony_ci	int ret;
325562306a36Sopenharmony_ci
325662306a36Sopenharmony_ci	if (!of_property_present(dev->of_node, "interrupts")) {
325762306a36Sopenharmony_ci		dev_warn(dev, "'interrupts' DT property is missing, no CEC\n");
325862306a36Sopenharmony_ci		return 0;
325962306a36Sopenharmony_ci	}
326062306a36Sopenharmony_ci
326162306a36Sopenharmony_ci	vc4_hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops,
326262306a36Sopenharmony_ci						  vc4_hdmi,
326362306a36Sopenharmony_ci						  vc4_hdmi->variant->card_name,
326462306a36Sopenharmony_ci						  CEC_CAP_DEFAULTS |
326562306a36Sopenharmony_ci						  CEC_CAP_CONNECTOR_INFO, 1);
326662306a36Sopenharmony_ci	ret = PTR_ERR_OR_ZERO(vc4_hdmi->cec_adap);
326762306a36Sopenharmony_ci	if (ret < 0)
326862306a36Sopenharmony_ci		return ret;
326962306a36Sopenharmony_ci
327062306a36Sopenharmony_ci	cec_fill_conn_info_from_drm(&conn_info, &vc4_hdmi->connector);
327162306a36Sopenharmony_ci	cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info);
327262306a36Sopenharmony_ci
327362306a36Sopenharmony_ci	if (vc4_hdmi->variant->external_irq_controller) {
327462306a36Sopenharmony_ci		ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-rx"),
327562306a36Sopenharmony_ci						vc4_cec_irq_handler_rx_bare,
327662306a36Sopenharmony_ci						vc4_cec_irq_handler_rx_thread, 0,
327762306a36Sopenharmony_ci						"vc4 hdmi cec rx", vc4_hdmi);
327862306a36Sopenharmony_ci		if (ret)
327962306a36Sopenharmony_ci			goto err_delete_cec_adap;
328062306a36Sopenharmony_ci
328162306a36Sopenharmony_ci		ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-tx"),
328262306a36Sopenharmony_ci						vc4_cec_irq_handler_tx_bare,
328362306a36Sopenharmony_ci						vc4_cec_irq_handler_tx_thread, 0,
328462306a36Sopenharmony_ci						"vc4 hdmi cec tx", vc4_hdmi);
328562306a36Sopenharmony_ci		if (ret)
328662306a36Sopenharmony_ci			goto err_delete_cec_adap;
328762306a36Sopenharmony_ci	} else {
328862306a36Sopenharmony_ci		ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
328962306a36Sopenharmony_ci						vc4_cec_irq_handler,
329062306a36Sopenharmony_ci						vc4_cec_irq_handler_thread, 0,
329162306a36Sopenharmony_ci						"vc4 hdmi cec", vc4_hdmi);
329262306a36Sopenharmony_ci		if (ret)
329362306a36Sopenharmony_ci			goto err_delete_cec_adap;
329462306a36Sopenharmony_ci	}
329562306a36Sopenharmony_ci
329662306a36Sopenharmony_ci	ret = cec_register_adapter(vc4_hdmi->cec_adap, &pdev->dev);
329762306a36Sopenharmony_ci	if (ret < 0)
329862306a36Sopenharmony_ci		goto err_delete_cec_adap;
329962306a36Sopenharmony_ci
330062306a36Sopenharmony_ci	/*
330162306a36Sopenharmony_ci	 * NOTE: Strictly speaking, we should probably use a DRM-managed
330262306a36Sopenharmony_ci	 * registration there to avoid removing the CEC adapter by the
330362306a36Sopenharmony_ci	 * time the DRM driver doesn't have any user anymore.
330462306a36Sopenharmony_ci	 *
330562306a36Sopenharmony_ci	 * However, the CEC framework already cleans up the CEC adapter
330662306a36Sopenharmony_ci	 * only when the last user has closed its file descriptor, so we
330762306a36Sopenharmony_ci	 * don't need to handle it in DRM.
330862306a36Sopenharmony_ci	 *
330962306a36Sopenharmony_ci	 * By the time the device-managed hook is executed, we will give
331062306a36Sopenharmony_ci	 * up our reference to the CEC adapter and therefore don't
331162306a36Sopenharmony_ci	 * really care when it's actually freed.
331262306a36Sopenharmony_ci	 *
331362306a36Sopenharmony_ci	 * There's still a problematic sequence: if we unregister our
331462306a36Sopenharmony_ci	 * CEC adapter, but the userspace keeps a handle on the CEC
331562306a36Sopenharmony_ci	 * adapter but not the DRM device for some reason. In such a
331662306a36Sopenharmony_ci	 * case, our vc4_hdmi structure will be freed, but the
331762306a36Sopenharmony_ci	 * cec_adapter structure will have a dangling pointer to what
331862306a36Sopenharmony_ci	 * used to be our HDMI controller. If we get a CEC call at that
331962306a36Sopenharmony_ci	 * moment, we could end up with a use-after-free. Fortunately,
332062306a36Sopenharmony_ci	 * the CEC framework already handles this too, by calling
332162306a36Sopenharmony_ci	 * cec_is_registered() in cec_ioctl() and cec_poll().
332262306a36Sopenharmony_ci	 */
332362306a36Sopenharmony_ci	ret = devm_add_action_or_reset(dev, vc4_hdmi_cec_release, vc4_hdmi);
332462306a36Sopenharmony_ci	if (ret)
332562306a36Sopenharmony_ci		return ret;
332662306a36Sopenharmony_ci
332762306a36Sopenharmony_ci	return 0;
332862306a36Sopenharmony_ci
332962306a36Sopenharmony_cierr_delete_cec_adap:
333062306a36Sopenharmony_ci	cec_delete_adapter(vc4_hdmi->cec_adap);
333162306a36Sopenharmony_ci
333262306a36Sopenharmony_ci	return ret;
333362306a36Sopenharmony_ci}
333462306a36Sopenharmony_ci#else
333562306a36Sopenharmony_cistatic int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
333662306a36Sopenharmony_ci{
333762306a36Sopenharmony_ci	return 0;
333862306a36Sopenharmony_ci}
333962306a36Sopenharmony_ci#endif
334062306a36Sopenharmony_ci
334162306a36Sopenharmony_cistatic void vc4_hdmi_free_regset(struct drm_device *drm, void *ptr)
334262306a36Sopenharmony_ci{
334362306a36Sopenharmony_ci	struct debugfs_reg32 *regs = ptr;
334462306a36Sopenharmony_ci
334562306a36Sopenharmony_ci	kfree(regs);
334662306a36Sopenharmony_ci}
334762306a36Sopenharmony_ci
334862306a36Sopenharmony_cistatic int vc4_hdmi_build_regset(struct drm_device *drm,
334962306a36Sopenharmony_ci				 struct vc4_hdmi *vc4_hdmi,
335062306a36Sopenharmony_ci				 struct debugfs_regset32 *regset,
335162306a36Sopenharmony_ci				 enum vc4_hdmi_regs reg)
335262306a36Sopenharmony_ci{
335362306a36Sopenharmony_ci	const struct vc4_hdmi_variant *variant = vc4_hdmi->variant;
335462306a36Sopenharmony_ci	struct debugfs_reg32 *regs, *new_regs;
335562306a36Sopenharmony_ci	unsigned int count = 0;
335662306a36Sopenharmony_ci	unsigned int i;
335762306a36Sopenharmony_ci	int ret;
335862306a36Sopenharmony_ci
335962306a36Sopenharmony_ci	regs = kcalloc(variant->num_registers, sizeof(*regs),
336062306a36Sopenharmony_ci		       GFP_KERNEL);
336162306a36Sopenharmony_ci	if (!regs)
336262306a36Sopenharmony_ci		return -ENOMEM;
336362306a36Sopenharmony_ci
336462306a36Sopenharmony_ci	for (i = 0; i < variant->num_registers; i++) {
336562306a36Sopenharmony_ci		const struct vc4_hdmi_register *field =	&variant->registers[i];
336662306a36Sopenharmony_ci
336762306a36Sopenharmony_ci		if (field->reg != reg)
336862306a36Sopenharmony_ci			continue;
336962306a36Sopenharmony_ci
337062306a36Sopenharmony_ci		regs[count].name = field->name;
337162306a36Sopenharmony_ci		regs[count].offset = field->offset;
337262306a36Sopenharmony_ci		count++;
337362306a36Sopenharmony_ci	}
337462306a36Sopenharmony_ci
337562306a36Sopenharmony_ci	new_regs = krealloc(regs, count * sizeof(*regs), GFP_KERNEL);
337662306a36Sopenharmony_ci	if (!new_regs)
337762306a36Sopenharmony_ci		return -ENOMEM;
337862306a36Sopenharmony_ci
337962306a36Sopenharmony_ci	regset->base = __vc4_hdmi_get_field_base(vc4_hdmi, reg);
338062306a36Sopenharmony_ci	regset->regs = new_regs;
338162306a36Sopenharmony_ci	regset->nregs = count;
338262306a36Sopenharmony_ci
338362306a36Sopenharmony_ci	ret = drmm_add_action_or_reset(drm, vc4_hdmi_free_regset, new_regs);
338462306a36Sopenharmony_ci	if (ret)
338562306a36Sopenharmony_ci		return ret;
338662306a36Sopenharmony_ci
338762306a36Sopenharmony_ci	return 0;
338862306a36Sopenharmony_ci}
338962306a36Sopenharmony_ci
339062306a36Sopenharmony_cistatic int vc4_hdmi_init_resources(struct drm_device *drm,
339162306a36Sopenharmony_ci				   struct vc4_hdmi *vc4_hdmi)
339262306a36Sopenharmony_ci{
339362306a36Sopenharmony_ci	struct platform_device *pdev = vc4_hdmi->pdev;
339462306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
339562306a36Sopenharmony_ci	int ret;
339662306a36Sopenharmony_ci
339762306a36Sopenharmony_ci	vc4_hdmi->hdmicore_regs = vc4_ioremap_regs(pdev, 0);
339862306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->hdmicore_regs))
339962306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->hdmicore_regs);
340062306a36Sopenharmony_ci
340162306a36Sopenharmony_ci	vc4_hdmi->hd_regs = vc4_ioremap_regs(pdev, 1);
340262306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->hd_regs))
340362306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->hd_regs);
340462306a36Sopenharmony_ci
340562306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD);
340662306a36Sopenharmony_ci	if (ret)
340762306a36Sopenharmony_ci		return ret;
340862306a36Sopenharmony_ci
340962306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI);
341062306a36Sopenharmony_ci	if (ret)
341162306a36Sopenharmony_ci		return ret;
341262306a36Sopenharmony_ci
341362306a36Sopenharmony_ci	vc4_hdmi->pixel_clock = devm_clk_get(dev, "pixel");
341462306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->pixel_clock)) {
341562306a36Sopenharmony_ci		ret = PTR_ERR(vc4_hdmi->pixel_clock);
341662306a36Sopenharmony_ci		if (ret != -EPROBE_DEFER)
341762306a36Sopenharmony_ci			DRM_ERROR("Failed to get pixel clock\n");
341862306a36Sopenharmony_ci		return ret;
341962306a36Sopenharmony_ci	}
342062306a36Sopenharmony_ci
342162306a36Sopenharmony_ci	vc4_hdmi->hsm_clock = devm_clk_get(dev, "hdmi");
342262306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->hsm_clock)) {
342362306a36Sopenharmony_ci		DRM_ERROR("Failed to get HDMI state machine clock\n");
342462306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->hsm_clock);
342562306a36Sopenharmony_ci	}
342662306a36Sopenharmony_ci	vc4_hdmi->audio_clock = vc4_hdmi->hsm_clock;
342762306a36Sopenharmony_ci	vc4_hdmi->cec_clock = vc4_hdmi->hsm_clock;
342862306a36Sopenharmony_ci
342962306a36Sopenharmony_ci	return 0;
343062306a36Sopenharmony_ci}
343162306a36Sopenharmony_ci
343262306a36Sopenharmony_cistatic int vc5_hdmi_init_resources(struct drm_device *drm,
343362306a36Sopenharmony_ci				   struct vc4_hdmi *vc4_hdmi)
343462306a36Sopenharmony_ci{
343562306a36Sopenharmony_ci	struct platform_device *pdev = vc4_hdmi->pdev;
343662306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
343762306a36Sopenharmony_ci	struct resource *res;
343862306a36Sopenharmony_ci	int ret;
343962306a36Sopenharmony_ci
344062306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi");
344162306a36Sopenharmony_ci	if (!res)
344262306a36Sopenharmony_ci		return -ENODEV;
344362306a36Sopenharmony_ci
344462306a36Sopenharmony_ci	vc4_hdmi->hdmicore_regs = devm_ioremap(dev, res->start,
344562306a36Sopenharmony_ci					       resource_size(res));
344662306a36Sopenharmony_ci	if (!vc4_hdmi->hdmicore_regs)
344762306a36Sopenharmony_ci		return -ENOMEM;
344862306a36Sopenharmony_ci
344962306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hd");
345062306a36Sopenharmony_ci	if (!res)
345162306a36Sopenharmony_ci		return -ENODEV;
345262306a36Sopenharmony_ci
345362306a36Sopenharmony_ci	vc4_hdmi->hd_regs = devm_ioremap(dev, res->start, resource_size(res));
345462306a36Sopenharmony_ci	if (!vc4_hdmi->hd_regs)
345562306a36Sopenharmony_ci		return -ENOMEM;
345662306a36Sopenharmony_ci
345762306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cec");
345862306a36Sopenharmony_ci	if (!res)
345962306a36Sopenharmony_ci		return -ENODEV;
346062306a36Sopenharmony_ci
346162306a36Sopenharmony_ci	vc4_hdmi->cec_regs = devm_ioremap(dev, res->start, resource_size(res));
346262306a36Sopenharmony_ci	if (!vc4_hdmi->cec_regs)
346362306a36Sopenharmony_ci		return -ENOMEM;
346462306a36Sopenharmony_ci
346562306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csc");
346662306a36Sopenharmony_ci	if (!res)
346762306a36Sopenharmony_ci		return -ENODEV;
346862306a36Sopenharmony_ci
346962306a36Sopenharmony_ci	vc4_hdmi->csc_regs = devm_ioremap(dev, res->start, resource_size(res));
347062306a36Sopenharmony_ci	if (!vc4_hdmi->csc_regs)
347162306a36Sopenharmony_ci		return -ENOMEM;
347262306a36Sopenharmony_ci
347362306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dvp");
347462306a36Sopenharmony_ci	if (!res)
347562306a36Sopenharmony_ci		return -ENODEV;
347662306a36Sopenharmony_ci
347762306a36Sopenharmony_ci	vc4_hdmi->dvp_regs = devm_ioremap(dev, res->start, resource_size(res));
347862306a36Sopenharmony_ci	if (!vc4_hdmi->dvp_regs)
347962306a36Sopenharmony_ci		return -ENOMEM;
348062306a36Sopenharmony_ci
348162306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy");
348262306a36Sopenharmony_ci	if (!res)
348362306a36Sopenharmony_ci		return -ENODEV;
348462306a36Sopenharmony_ci
348562306a36Sopenharmony_ci	vc4_hdmi->phy_regs = devm_ioremap(dev, res->start, resource_size(res));
348662306a36Sopenharmony_ci	if (!vc4_hdmi->phy_regs)
348762306a36Sopenharmony_ci		return -ENOMEM;
348862306a36Sopenharmony_ci
348962306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "packet");
349062306a36Sopenharmony_ci	if (!res)
349162306a36Sopenharmony_ci		return -ENODEV;
349262306a36Sopenharmony_ci
349362306a36Sopenharmony_ci	vc4_hdmi->ram_regs = devm_ioremap(dev, res->start, resource_size(res));
349462306a36Sopenharmony_ci	if (!vc4_hdmi->ram_regs)
349562306a36Sopenharmony_ci		return -ENOMEM;
349662306a36Sopenharmony_ci
349762306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rm");
349862306a36Sopenharmony_ci	if (!res)
349962306a36Sopenharmony_ci		return -ENODEV;
350062306a36Sopenharmony_ci
350162306a36Sopenharmony_ci	vc4_hdmi->rm_regs = devm_ioremap(dev, res->start, resource_size(res));
350262306a36Sopenharmony_ci	if (!vc4_hdmi->rm_regs)
350362306a36Sopenharmony_ci		return -ENOMEM;
350462306a36Sopenharmony_ci
350562306a36Sopenharmony_ci	vc4_hdmi->hsm_clock = devm_clk_get(dev, "hdmi");
350662306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->hsm_clock)) {
350762306a36Sopenharmony_ci		DRM_ERROR("Failed to get HDMI state machine clock\n");
350862306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->hsm_clock);
350962306a36Sopenharmony_ci	}
351062306a36Sopenharmony_ci
351162306a36Sopenharmony_ci	vc4_hdmi->pixel_bvb_clock = devm_clk_get(dev, "bvb");
351262306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->pixel_bvb_clock)) {
351362306a36Sopenharmony_ci		DRM_ERROR("Failed to get pixel bvb clock\n");
351462306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->pixel_bvb_clock);
351562306a36Sopenharmony_ci	}
351662306a36Sopenharmony_ci
351762306a36Sopenharmony_ci	vc4_hdmi->audio_clock = devm_clk_get(dev, "audio");
351862306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->audio_clock)) {
351962306a36Sopenharmony_ci		DRM_ERROR("Failed to get audio clock\n");
352062306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->audio_clock);
352162306a36Sopenharmony_ci	}
352262306a36Sopenharmony_ci
352362306a36Sopenharmony_ci	vc4_hdmi->cec_clock = devm_clk_get(dev, "cec");
352462306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->cec_clock)) {
352562306a36Sopenharmony_ci		DRM_ERROR("Failed to get CEC clock\n");
352662306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->cec_clock);
352762306a36Sopenharmony_ci	}
352862306a36Sopenharmony_ci
352962306a36Sopenharmony_ci	vc4_hdmi->reset = devm_reset_control_get(dev, NULL);
353062306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->reset)) {
353162306a36Sopenharmony_ci		DRM_ERROR("Failed to get HDMI reset line\n");
353262306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->reset);
353362306a36Sopenharmony_ci	}
353462306a36Sopenharmony_ci
353562306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI);
353662306a36Sopenharmony_ci	if (ret)
353762306a36Sopenharmony_ci		return ret;
353862306a36Sopenharmony_ci
353962306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD);
354062306a36Sopenharmony_ci	if (ret)
354162306a36Sopenharmony_ci		return ret;
354262306a36Sopenharmony_ci
354362306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->cec_regset, VC5_CEC);
354462306a36Sopenharmony_ci	if (ret)
354562306a36Sopenharmony_ci		return ret;
354662306a36Sopenharmony_ci
354762306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->csc_regset, VC5_CSC);
354862306a36Sopenharmony_ci	if (ret)
354962306a36Sopenharmony_ci		return ret;
355062306a36Sopenharmony_ci
355162306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->dvp_regset, VC5_DVP);
355262306a36Sopenharmony_ci	if (ret)
355362306a36Sopenharmony_ci		return ret;
355462306a36Sopenharmony_ci
355562306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->phy_regset, VC5_PHY);
355662306a36Sopenharmony_ci	if (ret)
355762306a36Sopenharmony_ci		return ret;
355862306a36Sopenharmony_ci
355962306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->ram_regset, VC5_RAM);
356062306a36Sopenharmony_ci	if (ret)
356162306a36Sopenharmony_ci		return ret;
356262306a36Sopenharmony_ci
356362306a36Sopenharmony_ci	ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->rm_regset, VC5_RM);
356462306a36Sopenharmony_ci	if (ret)
356562306a36Sopenharmony_ci		return ret;
356662306a36Sopenharmony_ci
356762306a36Sopenharmony_ci	return 0;
356862306a36Sopenharmony_ci}
356962306a36Sopenharmony_ci
357062306a36Sopenharmony_cistatic int vc4_hdmi_runtime_suspend(struct device *dev)
357162306a36Sopenharmony_ci{
357262306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
357362306a36Sopenharmony_ci
357462306a36Sopenharmony_ci	clk_disable_unprepare(vc4_hdmi->hsm_clock);
357562306a36Sopenharmony_ci
357662306a36Sopenharmony_ci	return 0;
357762306a36Sopenharmony_ci}
357862306a36Sopenharmony_ci
357962306a36Sopenharmony_cistatic int vc4_hdmi_runtime_resume(struct device *dev)
358062306a36Sopenharmony_ci{
358162306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
358262306a36Sopenharmony_ci	unsigned long __maybe_unused flags;
358362306a36Sopenharmony_ci	u32 __maybe_unused value;
358462306a36Sopenharmony_ci	unsigned long rate;
358562306a36Sopenharmony_ci	int ret;
358662306a36Sopenharmony_ci
358762306a36Sopenharmony_ci	ret = clk_prepare_enable(vc4_hdmi->hsm_clock);
358862306a36Sopenharmony_ci	if (ret)
358962306a36Sopenharmony_ci		return ret;
359062306a36Sopenharmony_ci
359162306a36Sopenharmony_ci	/*
359262306a36Sopenharmony_ci	 * Whenever the RaspberryPi boots without an HDMI monitor
359362306a36Sopenharmony_ci	 * plugged in, the firmware won't have initialized the HSM clock
359462306a36Sopenharmony_ci	 * rate and it will be reported as 0.
359562306a36Sopenharmony_ci	 *
359662306a36Sopenharmony_ci	 * If we try to access a register of the controller in such a
359762306a36Sopenharmony_ci	 * case, it will lead to a silent CPU stall. Let's make sure we
359862306a36Sopenharmony_ci	 * prevent such a case.
359962306a36Sopenharmony_ci	 */
360062306a36Sopenharmony_ci	rate = clk_get_rate(vc4_hdmi->hsm_clock);
360162306a36Sopenharmony_ci	if (!rate) {
360262306a36Sopenharmony_ci		ret = -EINVAL;
360362306a36Sopenharmony_ci		goto err_disable_clk;
360462306a36Sopenharmony_ci	}
360562306a36Sopenharmony_ci
360662306a36Sopenharmony_ci	if (vc4_hdmi->variant->reset)
360762306a36Sopenharmony_ci		vc4_hdmi->variant->reset(vc4_hdmi);
360862306a36Sopenharmony_ci
360962306a36Sopenharmony_ci#ifdef CONFIG_DRM_VC4_HDMI_CEC
361062306a36Sopenharmony_ci	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
361162306a36Sopenharmony_ci	value = HDMI_READ(HDMI_CEC_CNTRL_1);
361262306a36Sopenharmony_ci	/* Set the logical address to Unregistered */
361362306a36Sopenharmony_ci	value |= VC4_HDMI_CEC_ADDR_MASK;
361462306a36Sopenharmony_ci	HDMI_WRITE(HDMI_CEC_CNTRL_1, value);
361562306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
361662306a36Sopenharmony_ci
361762306a36Sopenharmony_ci	vc4_hdmi_cec_update_clk_div(vc4_hdmi);
361862306a36Sopenharmony_ci
361962306a36Sopenharmony_ci	if (!vc4_hdmi->variant->external_irq_controller) {
362062306a36Sopenharmony_ci		spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
362162306a36Sopenharmony_ci		HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff);
362262306a36Sopenharmony_ci		spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
362362306a36Sopenharmony_ci	}
362462306a36Sopenharmony_ci#endif
362562306a36Sopenharmony_ci
362662306a36Sopenharmony_ci	return 0;
362762306a36Sopenharmony_ci
362862306a36Sopenharmony_cierr_disable_clk:
362962306a36Sopenharmony_ci	clk_disable_unprepare(vc4_hdmi->hsm_clock);
363062306a36Sopenharmony_ci	return ret;
363162306a36Sopenharmony_ci}
363262306a36Sopenharmony_ci
363362306a36Sopenharmony_cistatic void vc4_hdmi_put_ddc_device(void *ptr)
363462306a36Sopenharmony_ci{
363562306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi = ptr;
363662306a36Sopenharmony_ci
363762306a36Sopenharmony_ci	put_device(&vc4_hdmi->ddc->dev);
363862306a36Sopenharmony_ci}
363962306a36Sopenharmony_ci
364062306a36Sopenharmony_cistatic int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
364162306a36Sopenharmony_ci{
364262306a36Sopenharmony_ci	const struct vc4_hdmi_variant *variant = of_device_get_match_data(dev);
364362306a36Sopenharmony_ci	struct platform_device *pdev = to_platform_device(dev);
364462306a36Sopenharmony_ci	struct drm_device *drm = dev_get_drvdata(master);
364562306a36Sopenharmony_ci	struct vc4_hdmi *vc4_hdmi;
364662306a36Sopenharmony_ci	struct drm_encoder *encoder;
364762306a36Sopenharmony_ci	struct device_node *ddc_node;
364862306a36Sopenharmony_ci	int ret;
364962306a36Sopenharmony_ci
365062306a36Sopenharmony_ci	vc4_hdmi = drmm_kzalloc(drm, sizeof(*vc4_hdmi), GFP_KERNEL);
365162306a36Sopenharmony_ci	if (!vc4_hdmi)
365262306a36Sopenharmony_ci		return -ENOMEM;
365362306a36Sopenharmony_ci
365462306a36Sopenharmony_ci	ret = drmm_mutex_init(drm, &vc4_hdmi->mutex);
365562306a36Sopenharmony_ci	if (ret)
365662306a36Sopenharmony_ci		return ret;
365762306a36Sopenharmony_ci
365862306a36Sopenharmony_ci	spin_lock_init(&vc4_hdmi->hw_lock);
365962306a36Sopenharmony_ci	INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq);
366062306a36Sopenharmony_ci
366162306a36Sopenharmony_ci	dev_set_drvdata(dev, vc4_hdmi);
366262306a36Sopenharmony_ci	encoder = &vc4_hdmi->encoder.base;
366362306a36Sopenharmony_ci	vc4_hdmi->encoder.type = variant->encoder_type;
366462306a36Sopenharmony_ci	vc4_hdmi->encoder.pre_crtc_configure = vc4_hdmi_encoder_pre_crtc_configure;
366562306a36Sopenharmony_ci	vc4_hdmi->encoder.pre_crtc_enable = vc4_hdmi_encoder_pre_crtc_enable;
366662306a36Sopenharmony_ci	vc4_hdmi->encoder.post_crtc_enable = vc4_hdmi_encoder_post_crtc_enable;
366762306a36Sopenharmony_ci	vc4_hdmi->encoder.post_crtc_disable = vc4_hdmi_encoder_post_crtc_disable;
366862306a36Sopenharmony_ci	vc4_hdmi->encoder.post_crtc_powerdown = vc4_hdmi_encoder_post_crtc_powerdown;
366962306a36Sopenharmony_ci	vc4_hdmi->pdev = pdev;
367062306a36Sopenharmony_ci	vc4_hdmi->variant = variant;
367162306a36Sopenharmony_ci
367262306a36Sopenharmony_ci	/*
367362306a36Sopenharmony_ci	 * Since we don't know the state of the controller and its
367462306a36Sopenharmony_ci	 * display (if any), let's assume it's always enabled.
367562306a36Sopenharmony_ci	 * vc4_hdmi_disable_scrambling() will thus run at boot, make
367662306a36Sopenharmony_ci	 * sure it's disabled, and avoid any inconsistency.
367762306a36Sopenharmony_ci	 */
367862306a36Sopenharmony_ci	if (variant->max_pixel_clock > HDMI_14_MAX_TMDS_CLK)
367962306a36Sopenharmony_ci		vc4_hdmi->scdc_enabled = true;
368062306a36Sopenharmony_ci
368162306a36Sopenharmony_ci	ret = variant->init_resources(drm, vc4_hdmi);
368262306a36Sopenharmony_ci	if (ret)
368362306a36Sopenharmony_ci		return ret;
368462306a36Sopenharmony_ci
368562306a36Sopenharmony_ci	ddc_node = of_parse_phandle(dev->of_node, "ddc", 0);
368662306a36Sopenharmony_ci	if (!ddc_node) {
368762306a36Sopenharmony_ci		DRM_ERROR("Failed to find ddc node in device tree\n");
368862306a36Sopenharmony_ci		return -ENODEV;
368962306a36Sopenharmony_ci	}
369062306a36Sopenharmony_ci
369162306a36Sopenharmony_ci	vc4_hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node);
369262306a36Sopenharmony_ci	of_node_put(ddc_node);
369362306a36Sopenharmony_ci	if (!vc4_hdmi->ddc) {
369462306a36Sopenharmony_ci		DRM_DEBUG("Failed to get ddc i2c adapter by node\n");
369562306a36Sopenharmony_ci		return -EPROBE_DEFER;
369662306a36Sopenharmony_ci	}
369762306a36Sopenharmony_ci
369862306a36Sopenharmony_ci	ret = devm_add_action_or_reset(dev, vc4_hdmi_put_ddc_device, vc4_hdmi);
369962306a36Sopenharmony_ci	if (ret)
370062306a36Sopenharmony_ci		return ret;
370162306a36Sopenharmony_ci
370262306a36Sopenharmony_ci	/* Only use the GPIO HPD pin if present in the DT, otherwise
370362306a36Sopenharmony_ci	 * we'll use the HDMI core's register.
370462306a36Sopenharmony_ci	 */
370562306a36Sopenharmony_ci	vc4_hdmi->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
370662306a36Sopenharmony_ci	if (IS_ERR(vc4_hdmi->hpd_gpio)) {
370762306a36Sopenharmony_ci		return PTR_ERR(vc4_hdmi->hpd_gpio);
370862306a36Sopenharmony_ci	}
370962306a36Sopenharmony_ci
371062306a36Sopenharmony_ci	vc4_hdmi->disable_wifi_frequencies =
371162306a36Sopenharmony_ci		of_property_read_bool(dev->of_node, "wifi-2.4ghz-coexistence");
371262306a36Sopenharmony_ci
371362306a36Sopenharmony_ci	ret = devm_pm_runtime_enable(dev);
371462306a36Sopenharmony_ci	if (ret)
371562306a36Sopenharmony_ci		return ret;
371662306a36Sopenharmony_ci
371762306a36Sopenharmony_ci	/*
371862306a36Sopenharmony_ci	 *  We need to have the device powered up at this point to call
371962306a36Sopenharmony_ci	 *  our reset hook and for the CEC init.
372062306a36Sopenharmony_ci	 */
372162306a36Sopenharmony_ci	ret = pm_runtime_resume_and_get(dev);
372262306a36Sopenharmony_ci	if (ret)
372362306a36Sopenharmony_ci		return ret;
372462306a36Sopenharmony_ci
372562306a36Sopenharmony_ci	if ((of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi0") ||
372662306a36Sopenharmony_ci	     of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1")) &&
372762306a36Sopenharmony_ci	    HDMI_READ(HDMI_VID_CTL) & VC4_HD_VID_CTL_ENABLE) {
372862306a36Sopenharmony_ci		clk_prepare_enable(vc4_hdmi->pixel_clock);
372962306a36Sopenharmony_ci		clk_prepare_enable(vc4_hdmi->hsm_clock);
373062306a36Sopenharmony_ci		clk_prepare_enable(vc4_hdmi->pixel_bvb_clock);
373162306a36Sopenharmony_ci	}
373262306a36Sopenharmony_ci
373362306a36Sopenharmony_ci	ret = drmm_encoder_init(drm, encoder,
373462306a36Sopenharmony_ci				&vc4_hdmi_encoder_funcs,
373562306a36Sopenharmony_ci				DRM_MODE_ENCODER_TMDS,
373662306a36Sopenharmony_ci				NULL);
373762306a36Sopenharmony_ci	if (ret)
373862306a36Sopenharmony_ci		goto err_put_runtime_pm;
373962306a36Sopenharmony_ci
374062306a36Sopenharmony_ci	drm_encoder_helper_add(encoder, &vc4_hdmi_encoder_helper_funcs);
374162306a36Sopenharmony_ci
374262306a36Sopenharmony_ci	ret = vc4_hdmi_connector_init(drm, vc4_hdmi);
374362306a36Sopenharmony_ci	if (ret)
374462306a36Sopenharmony_ci		goto err_put_runtime_pm;
374562306a36Sopenharmony_ci
374662306a36Sopenharmony_ci	ret = vc4_hdmi_hotplug_init(vc4_hdmi);
374762306a36Sopenharmony_ci	if (ret)
374862306a36Sopenharmony_ci		goto err_put_runtime_pm;
374962306a36Sopenharmony_ci
375062306a36Sopenharmony_ci	ret = vc4_hdmi_cec_init(vc4_hdmi);
375162306a36Sopenharmony_ci	if (ret)
375262306a36Sopenharmony_ci		goto err_put_runtime_pm;
375362306a36Sopenharmony_ci
375462306a36Sopenharmony_ci	ret = vc4_hdmi_audio_init(vc4_hdmi);
375562306a36Sopenharmony_ci	if (ret)
375662306a36Sopenharmony_ci		goto err_put_runtime_pm;
375762306a36Sopenharmony_ci
375862306a36Sopenharmony_ci	pm_runtime_put_sync(dev);
375962306a36Sopenharmony_ci
376062306a36Sopenharmony_ci	return 0;
376162306a36Sopenharmony_ci
376262306a36Sopenharmony_cierr_put_runtime_pm:
376362306a36Sopenharmony_ci	pm_runtime_put_sync(dev);
376462306a36Sopenharmony_ci
376562306a36Sopenharmony_ci	return ret;
376662306a36Sopenharmony_ci}
376762306a36Sopenharmony_ci
376862306a36Sopenharmony_cistatic const struct component_ops vc4_hdmi_ops = {
376962306a36Sopenharmony_ci	.bind   = vc4_hdmi_bind,
377062306a36Sopenharmony_ci};
377162306a36Sopenharmony_ci
377262306a36Sopenharmony_cistatic int vc4_hdmi_dev_probe(struct platform_device *pdev)
377362306a36Sopenharmony_ci{
377462306a36Sopenharmony_ci	return component_add(&pdev->dev, &vc4_hdmi_ops);
377562306a36Sopenharmony_ci}
377662306a36Sopenharmony_ci
377762306a36Sopenharmony_cistatic void vc4_hdmi_dev_remove(struct platform_device *pdev)
377862306a36Sopenharmony_ci{
377962306a36Sopenharmony_ci	component_del(&pdev->dev, &vc4_hdmi_ops);
378062306a36Sopenharmony_ci}
378162306a36Sopenharmony_ci
378262306a36Sopenharmony_cistatic const struct vc4_hdmi_variant bcm2835_variant = {
378362306a36Sopenharmony_ci	.encoder_type		= VC4_ENCODER_TYPE_HDMI0,
378462306a36Sopenharmony_ci	.debugfs_name		= "hdmi_regs",
378562306a36Sopenharmony_ci	.card_name		= "vc4-hdmi",
378662306a36Sopenharmony_ci	.max_pixel_clock	= 162000000,
378762306a36Sopenharmony_ci	.registers		= vc4_hdmi_fields,
378862306a36Sopenharmony_ci	.num_registers		= ARRAY_SIZE(vc4_hdmi_fields),
378962306a36Sopenharmony_ci
379062306a36Sopenharmony_ci	.init_resources		= vc4_hdmi_init_resources,
379162306a36Sopenharmony_ci	.csc_setup		= vc4_hdmi_csc_setup,
379262306a36Sopenharmony_ci	.reset			= vc4_hdmi_reset,
379362306a36Sopenharmony_ci	.set_timings		= vc4_hdmi_set_timings,
379462306a36Sopenharmony_ci	.phy_init		= vc4_hdmi_phy_init,
379562306a36Sopenharmony_ci	.phy_disable		= vc4_hdmi_phy_disable,
379662306a36Sopenharmony_ci	.phy_rng_enable		= vc4_hdmi_phy_rng_enable,
379762306a36Sopenharmony_ci	.phy_rng_disable	= vc4_hdmi_phy_rng_disable,
379862306a36Sopenharmony_ci	.channel_map		= vc4_hdmi_channel_map,
379962306a36Sopenharmony_ci	.supports_hdr		= false,
380062306a36Sopenharmony_ci};
380162306a36Sopenharmony_ci
380262306a36Sopenharmony_cistatic const struct vc4_hdmi_variant bcm2711_hdmi0_variant = {
380362306a36Sopenharmony_ci	.encoder_type		= VC4_ENCODER_TYPE_HDMI0,
380462306a36Sopenharmony_ci	.debugfs_name		= "hdmi0_regs",
380562306a36Sopenharmony_ci	.card_name		= "vc4-hdmi-0",
380662306a36Sopenharmony_ci	.max_pixel_clock	= 600000000,
380762306a36Sopenharmony_ci	.registers		= vc5_hdmi_hdmi0_fields,
380862306a36Sopenharmony_ci	.num_registers		= ARRAY_SIZE(vc5_hdmi_hdmi0_fields),
380962306a36Sopenharmony_ci	.phy_lane_mapping	= {
381062306a36Sopenharmony_ci		PHY_LANE_0,
381162306a36Sopenharmony_ci		PHY_LANE_1,
381262306a36Sopenharmony_ci		PHY_LANE_2,
381362306a36Sopenharmony_ci		PHY_LANE_CK,
381462306a36Sopenharmony_ci	},
381562306a36Sopenharmony_ci	.unsupported_odd_h_timings	= true,
381662306a36Sopenharmony_ci	.external_irq_controller	= true,
381762306a36Sopenharmony_ci
381862306a36Sopenharmony_ci	.init_resources		= vc5_hdmi_init_resources,
381962306a36Sopenharmony_ci	.csc_setup		= vc5_hdmi_csc_setup,
382062306a36Sopenharmony_ci	.reset			= vc5_hdmi_reset,
382162306a36Sopenharmony_ci	.set_timings		= vc5_hdmi_set_timings,
382262306a36Sopenharmony_ci	.phy_init		= vc5_hdmi_phy_init,
382362306a36Sopenharmony_ci	.phy_disable		= vc5_hdmi_phy_disable,
382462306a36Sopenharmony_ci	.phy_rng_enable		= vc5_hdmi_phy_rng_enable,
382562306a36Sopenharmony_ci	.phy_rng_disable	= vc5_hdmi_phy_rng_disable,
382662306a36Sopenharmony_ci	.channel_map		= vc5_hdmi_channel_map,
382762306a36Sopenharmony_ci	.supports_hdr		= true,
382862306a36Sopenharmony_ci	.hp_detect		= vc5_hdmi_hp_detect,
382962306a36Sopenharmony_ci};
383062306a36Sopenharmony_ci
383162306a36Sopenharmony_cistatic const struct vc4_hdmi_variant bcm2711_hdmi1_variant = {
383262306a36Sopenharmony_ci	.encoder_type		= VC4_ENCODER_TYPE_HDMI1,
383362306a36Sopenharmony_ci	.debugfs_name		= "hdmi1_regs",
383462306a36Sopenharmony_ci	.card_name		= "vc4-hdmi-1",
383562306a36Sopenharmony_ci	.max_pixel_clock	= HDMI_14_MAX_TMDS_CLK,
383662306a36Sopenharmony_ci	.registers		= vc5_hdmi_hdmi1_fields,
383762306a36Sopenharmony_ci	.num_registers		= ARRAY_SIZE(vc5_hdmi_hdmi1_fields),
383862306a36Sopenharmony_ci	.phy_lane_mapping	= {
383962306a36Sopenharmony_ci		PHY_LANE_1,
384062306a36Sopenharmony_ci		PHY_LANE_0,
384162306a36Sopenharmony_ci		PHY_LANE_CK,
384262306a36Sopenharmony_ci		PHY_LANE_2,
384362306a36Sopenharmony_ci	},
384462306a36Sopenharmony_ci	.unsupported_odd_h_timings	= true,
384562306a36Sopenharmony_ci	.external_irq_controller	= true,
384662306a36Sopenharmony_ci
384762306a36Sopenharmony_ci	.init_resources		= vc5_hdmi_init_resources,
384862306a36Sopenharmony_ci	.csc_setup		= vc5_hdmi_csc_setup,
384962306a36Sopenharmony_ci	.reset			= vc5_hdmi_reset,
385062306a36Sopenharmony_ci	.set_timings		= vc5_hdmi_set_timings,
385162306a36Sopenharmony_ci	.phy_init		= vc5_hdmi_phy_init,
385262306a36Sopenharmony_ci	.phy_disable		= vc5_hdmi_phy_disable,
385362306a36Sopenharmony_ci	.phy_rng_enable		= vc5_hdmi_phy_rng_enable,
385462306a36Sopenharmony_ci	.phy_rng_disable	= vc5_hdmi_phy_rng_disable,
385562306a36Sopenharmony_ci	.channel_map		= vc5_hdmi_channel_map,
385662306a36Sopenharmony_ci	.supports_hdr		= true,
385762306a36Sopenharmony_ci	.hp_detect		= vc5_hdmi_hp_detect,
385862306a36Sopenharmony_ci};
385962306a36Sopenharmony_ci
386062306a36Sopenharmony_cistatic const struct of_device_id vc4_hdmi_dt_match[] = {
386162306a36Sopenharmony_ci	{ .compatible = "brcm,bcm2835-hdmi", .data = &bcm2835_variant },
386262306a36Sopenharmony_ci	{ .compatible = "brcm,bcm2711-hdmi0", .data = &bcm2711_hdmi0_variant },
386362306a36Sopenharmony_ci	{ .compatible = "brcm,bcm2711-hdmi1", .data = &bcm2711_hdmi1_variant },
386462306a36Sopenharmony_ci	{}
386562306a36Sopenharmony_ci};
386662306a36Sopenharmony_ci
386762306a36Sopenharmony_cistatic const struct dev_pm_ops vc4_hdmi_pm_ops = {
386862306a36Sopenharmony_ci	SET_RUNTIME_PM_OPS(vc4_hdmi_runtime_suspend,
386962306a36Sopenharmony_ci			   vc4_hdmi_runtime_resume,
387062306a36Sopenharmony_ci			   NULL)
387162306a36Sopenharmony_ci};
387262306a36Sopenharmony_ci
387362306a36Sopenharmony_cistruct platform_driver vc4_hdmi_driver = {
387462306a36Sopenharmony_ci	.probe = vc4_hdmi_dev_probe,
387562306a36Sopenharmony_ci	.remove_new = vc4_hdmi_dev_remove,
387662306a36Sopenharmony_ci	.driver = {
387762306a36Sopenharmony_ci		.name = "vc4_hdmi",
387862306a36Sopenharmony_ci		.of_match_table = vc4_hdmi_dt_match,
387962306a36Sopenharmony_ci		.pm = &vc4_hdmi_pm_ops,
388062306a36Sopenharmony_ci	},
388162306a36Sopenharmony_ci};
3882