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, ¶ms->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