18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2015 Broadcom 48c2ecf20Sopenharmony_ci * Copyright (c) 2014 The Linux Foundation. All rights reserved. 58c2ecf20Sopenharmony_ci * Copyright (C) 2013 Red Hat 68c2ecf20Sopenharmony_ci * Author: Rob Clark <robdclark@gmail.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/** 108c2ecf20Sopenharmony_ci * DOC: VC4 Falcon HDMI module 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * The HDMI core has a state machine and a PHY. On BCM2835, most of 138c2ecf20Sopenharmony_ci * the unit operates off of the HSM clock from CPRMAN. It also 148c2ecf20Sopenharmony_ci * internally uses the PLLH_PIX clock for the PHY. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * HDMI infoframes are kept within a small packet ram, where each 178c2ecf20Sopenharmony_ci * packet can be individually enabled for including in a frame. 188c2ecf20Sopenharmony_ci * 198c2ecf20Sopenharmony_ci * HDMI audio is implemented entirely within the HDMI IP block. A 208c2ecf20Sopenharmony_ci * register in the HDMI encoder takes SPDIF frames from the DMA engine 218c2ecf20Sopenharmony_ci * and transfers them over an internal MAI (multi-channel audio 228c2ecf20Sopenharmony_ci * interconnect) bus to the encoder side for insertion into the video 238c2ecf20Sopenharmony_ci * blank regions. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * The driver's HDMI encoder does not yet support power management. 268c2ecf20Sopenharmony_ci * The HDMI encoder's power domain and the HSM/pixel clocks are kept 278c2ecf20Sopenharmony_ci * continuously running, and only the HDMI logic and packet ram are 288c2ecf20Sopenharmony_ci * powered off/on at disable/enable time. 298c2ecf20Sopenharmony_ci * 308c2ecf20Sopenharmony_ci * The driver does not yet support CEC control, though the HDMI 318c2ecf20Sopenharmony_ci * encoder block has CEC support. 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h> 358c2ecf20Sopenharmony_ci#include <drm/drm_edid.h> 368c2ecf20Sopenharmony_ci#include <drm/drm_probe_helper.h> 378c2ecf20Sopenharmony_ci#include <drm/drm_simple_kms_helper.h> 388c2ecf20Sopenharmony_ci#include <linux/clk.h> 398c2ecf20Sopenharmony_ci#include <linux/component.h> 408c2ecf20Sopenharmony_ci#include <linux/i2c.h> 418c2ecf20Sopenharmony_ci#include <linux/of_address.h> 428c2ecf20Sopenharmony_ci#include <linux/of_gpio.h> 438c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 448c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 458c2ecf20Sopenharmony_ci#include <linux/rational.h> 468c2ecf20Sopenharmony_ci#include <linux/reset.h> 478c2ecf20Sopenharmony_ci#include <sound/dmaengine_pcm.h> 488c2ecf20Sopenharmony_ci#include <sound/pcm_drm_eld.h> 498c2ecf20Sopenharmony_ci#include <sound/pcm_params.h> 508c2ecf20Sopenharmony_ci#include <sound/soc.h> 518c2ecf20Sopenharmony_ci#include "media/cec.h" 528c2ecf20Sopenharmony_ci#include "vc4_drv.h" 538c2ecf20Sopenharmony_ci#include "vc4_hdmi.h" 548c2ecf20Sopenharmony_ci#include "vc4_hdmi_regs.h" 558c2ecf20Sopenharmony_ci#include "vc4_regs.h" 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZA_HFP_SHIFT 16 588c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZA_HFP_MASK VC4_MASK(28, 16) 598c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZA_VPOS BIT(15) 608c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZA_HPOS BIT(14) 618c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZA_HAP_SHIFT 0 628c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZA_HAP_MASK VC4_MASK(13, 0) 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZB_HBP_SHIFT 16 658c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZB_HBP_MASK VC4_MASK(26, 16) 668c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZB_HSP_SHIFT 0 678c2ecf20Sopenharmony_ci#define VC5_HDMI_HORZB_HSP_MASK VC4_MASK(10, 0) 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci#define VC5_HDMI_VERTA_VSP_SHIFT 24 708c2ecf20Sopenharmony_ci#define VC5_HDMI_VERTA_VSP_MASK VC4_MASK(28, 24) 718c2ecf20Sopenharmony_ci#define VC5_HDMI_VERTA_VFP_SHIFT 16 728c2ecf20Sopenharmony_ci#define VC5_HDMI_VERTA_VFP_MASK VC4_MASK(22, 16) 738c2ecf20Sopenharmony_ci#define VC5_HDMI_VERTA_VAL_SHIFT 0 748c2ecf20Sopenharmony_ci#define VC5_HDMI_VERTA_VAL_MASK VC4_MASK(12, 0) 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci#define VC5_HDMI_VERTB_VSPO_SHIFT 16 778c2ecf20Sopenharmony_ci#define VC5_HDMI_VERTB_VSPO_MASK VC4_MASK(29, 16) 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci# define VC4_HD_M_SW_RST BIT(2) 808c2ecf20Sopenharmony_ci# define VC4_HD_M_ENABLE BIT(0) 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci#define HSM_MIN_CLOCK_FREQ 120000000 838c2ecf20Sopenharmony_ci#define CEC_CLOCK_FREQ 40000 848c2ecf20Sopenharmony_ci#define VC4_HSM_MID_CLOCK 149985000 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci#define HDMI_14_MAX_TMDS_CLK (340 * 1000 * 1000) 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistatic int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci struct drm_info_node *node = (struct drm_info_node *)m->private; 918c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = node->info_ent->data; 928c2ecf20Sopenharmony_ci struct drm_printer p = drm_seq_file_printer(m); 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci drm_print_regset32(&p, &vc4_hdmi->hdmi_regset); 958c2ecf20Sopenharmony_ci drm_print_regset32(&p, &vc4_hdmi->hd_regset); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_M_CTL, VC4_HD_M_SW_RST); 1038c2ecf20Sopenharmony_ci udelay(1); 1048c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_M_CTL, 0); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_M_CTL, VC4_HD_M_ENABLE); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_SW_RESET_CONTROL, 1098c2ecf20Sopenharmony_ci VC4_HDMI_SW_RESET_HDMI | 1108c2ecf20Sopenharmony_ci VC4_HDMI_SW_RESET_FORMAT_DETECT); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_SW_RESET_CONTROL, 0); 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci reset_control_reset(vc4_hdmi->reset); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_DVP_CTL, 0); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CLOCK_STOP, 1228c2ecf20Sopenharmony_ci HDMI_READ(HDMI_CLOCK_STOP) | VC4_DVP_HT_CLOCK_STOP_PIXEL); 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci#ifdef CONFIG_DRM_VC4_HDMI_CEC 1268c2ecf20Sopenharmony_cistatic void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci u16 clk_cnt; 1298c2ecf20Sopenharmony_ci u32 value; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci value = HDMI_READ(HDMI_CEC_CNTRL_1); 1328c2ecf20Sopenharmony_ci value &= ~VC4_HDMI_CEC_DIV_CLK_CNT_MASK; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci /* 1358c2ecf20Sopenharmony_ci * Set the clock divider: the hsm_clock rate and this divider 1368c2ecf20Sopenharmony_ci * setting will give a 40 kHz CEC clock. 1378c2ecf20Sopenharmony_ci */ 1388c2ecf20Sopenharmony_ci clk_cnt = clk_get_rate(vc4_hdmi->hsm_clock) / CEC_CLOCK_FREQ; 1398c2ecf20Sopenharmony_ci value |= clk_cnt << VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT; 1408c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_1, value); 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci#else 1438c2ecf20Sopenharmony_cistatic void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) {} 1448c2ecf20Sopenharmony_ci#endif 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic enum drm_connector_status 1478c2ecf20Sopenharmony_civc4_hdmi_connector_detect(struct drm_connector *connector, bool force) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector); 1508c2ecf20Sopenharmony_ci bool connected = false; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci WARN_ON(pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev)); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci if (vc4_hdmi->hpd_gpio) { 1558c2ecf20Sopenharmony_ci if (gpio_get_value_cansleep(vc4_hdmi->hpd_gpio) ^ 1568c2ecf20Sopenharmony_ci vc4_hdmi->hpd_active_low) 1578c2ecf20Sopenharmony_ci connected = true; 1588c2ecf20Sopenharmony_ci } else if (drm_probe_ddc(vc4_hdmi->ddc)) { 1598c2ecf20Sopenharmony_ci connected = true; 1608c2ecf20Sopenharmony_ci } else if (HDMI_READ(HDMI_HOTPLUG) & VC4_HDMI_HOTPLUG_CONNECTED) { 1618c2ecf20Sopenharmony_ci connected = true; 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci if (connected) { 1658c2ecf20Sopenharmony_ci if (connector->status != connector_status_connected) { 1668c2ecf20Sopenharmony_ci struct edid *edid = drm_get_edid(connector, vc4_hdmi->ddc); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (edid) { 1698c2ecf20Sopenharmony_ci cec_s_phys_addr_from_edid(vc4_hdmi->cec_adap, edid); 1708c2ecf20Sopenharmony_ci vc4_hdmi->encoder.hdmi_monitor = drm_detect_hdmi_monitor(edid); 1718c2ecf20Sopenharmony_ci kfree(edid); 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci pm_runtime_put(&vc4_hdmi->pdev->dev); 1768c2ecf20Sopenharmony_ci return connector_status_connected; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci cec_phys_addr_invalidate(vc4_hdmi->cec_adap); 1808c2ecf20Sopenharmony_ci pm_runtime_put(&vc4_hdmi->pdev->dev); 1818c2ecf20Sopenharmony_ci return connector_status_disconnected; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic void vc4_hdmi_connector_destroy(struct drm_connector *connector) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci drm_connector_unregister(connector); 1878c2ecf20Sopenharmony_ci drm_connector_cleanup(connector); 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic int vc4_hdmi_connector_get_modes(struct drm_connector *connector) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector); 1938c2ecf20Sopenharmony_ci struct vc4_hdmi_encoder *vc4_encoder = &vc4_hdmi->encoder; 1948c2ecf20Sopenharmony_ci int ret = 0; 1958c2ecf20Sopenharmony_ci struct edid *edid; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci edid = drm_get_edid(connector, vc4_hdmi->ddc); 1988c2ecf20Sopenharmony_ci cec_s_phys_addr_from_edid(vc4_hdmi->cec_adap, edid); 1998c2ecf20Sopenharmony_ci if (!edid) 2008c2ecf20Sopenharmony_ci return -ENODEV; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci drm_connector_update_edid_property(connector, edid); 2058c2ecf20Sopenharmony_ci ret = drm_add_edid_modes(connector, edid); 2068c2ecf20Sopenharmony_ci kfree(edid); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci return ret; 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic void vc4_hdmi_connector_reset(struct drm_connector *connector) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci drm_atomic_helper_connector_reset(connector); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci if (connector->state) 2168c2ecf20Sopenharmony_ci drm_atomic_helper_connector_tv_reset(connector); 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic const struct drm_connector_funcs vc4_hdmi_connector_funcs = { 2208c2ecf20Sopenharmony_ci .detect = vc4_hdmi_connector_detect, 2218c2ecf20Sopenharmony_ci .fill_modes = drm_helper_probe_single_connector_modes, 2228c2ecf20Sopenharmony_ci .destroy = vc4_hdmi_connector_destroy, 2238c2ecf20Sopenharmony_ci .reset = vc4_hdmi_connector_reset, 2248c2ecf20Sopenharmony_ci .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 2258c2ecf20Sopenharmony_ci .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 2268c2ecf20Sopenharmony_ci}; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic const struct drm_connector_helper_funcs vc4_hdmi_connector_helper_funcs = { 2298c2ecf20Sopenharmony_ci .get_modes = vc4_hdmi_connector_get_modes, 2308c2ecf20Sopenharmony_ci}; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic int vc4_hdmi_connector_init(struct drm_device *dev, 2338c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci struct drm_connector *connector = &vc4_hdmi->connector; 2368c2ecf20Sopenharmony_ci struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; 2378c2ecf20Sopenharmony_ci int ret; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci drm_connector_init_with_ddc(dev, connector, 2408c2ecf20Sopenharmony_ci &vc4_hdmi_connector_funcs, 2418c2ecf20Sopenharmony_ci DRM_MODE_CONNECTOR_HDMIA, 2428c2ecf20Sopenharmony_ci vc4_hdmi->ddc); 2438c2ecf20Sopenharmony_ci drm_connector_helper_add(connector, &vc4_hdmi_connector_helper_funcs); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci /* Create and attach TV margin props to this connector. */ 2468c2ecf20Sopenharmony_ci ret = drm_mode_create_tv_margin_properties(dev); 2478c2ecf20Sopenharmony_ci if (ret) 2488c2ecf20Sopenharmony_ci return ret; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci drm_connector_attach_tv_margin_properties(connector); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci connector->polled = (DRM_CONNECTOR_POLL_CONNECT | 2538c2ecf20Sopenharmony_ci DRM_CONNECTOR_POLL_DISCONNECT); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci connector->interlace_allowed = 1; 2568c2ecf20Sopenharmony_ci connector->doublescan_allowed = 0; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci drm_connector_attach_encoder(connector, encoder); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci return 0; 2618c2ecf20Sopenharmony_ci} 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cistatic int vc4_hdmi_stop_packet(struct drm_encoder *encoder, 2648c2ecf20Sopenharmony_ci enum hdmi_infoframe_type type) 2658c2ecf20Sopenharmony_ci{ 2668c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 2678c2ecf20Sopenharmony_ci u32 packet_id = type - 0x80; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 2708c2ecf20Sopenharmony_ci HDMI_READ(HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id)); 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci return wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) & 2738c2ecf20Sopenharmony_ci BIT(packet_id)), 100); 2748c2ecf20Sopenharmony_ci} 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_cistatic void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, 2778c2ecf20Sopenharmony_ci union hdmi_infoframe *frame) 2788c2ecf20Sopenharmony_ci{ 2798c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 2808c2ecf20Sopenharmony_ci u32 packet_id = frame->any.type - 0x80; 2818c2ecf20Sopenharmony_ci const struct vc4_hdmi_register *ram_packet_start = 2828c2ecf20Sopenharmony_ci &vc4_hdmi->variant->registers[HDMI_RAM_PACKET_START]; 2838c2ecf20Sopenharmony_ci u32 packet_reg = ram_packet_start->offset + VC4_HDMI_PACKET_STRIDE * packet_id; 2848c2ecf20Sopenharmony_ci void __iomem *base = __vc4_hdmi_get_field_base(vc4_hdmi, 2858c2ecf20Sopenharmony_ci ram_packet_start->reg); 2868c2ecf20Sopenharmony_ci uint8_t buffer[VC4_HDMI_PACKET_STRIDE]; 2878c2ecf20Sopenharmony_ci ssize_t len, i; 2888c2ecf20Sopenharmony_ci int ret; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci WARN_ONCE(!(HDMI_READ(HDMI_RAM_PACKET_CONFIG) & 2918c2ecf20Sopenharmony_ci VC4_HDMI_RAM_PACKET_ENABLE), 2928c2ecf20Sopenharmony_ci "Packet RAM has to be on to store the packet."); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer)); 2958c2ecf20Sopenharmony_ci if (len < 0) 2968c2ecf20Sopenharmony_ci return; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci ret = vc4_hdmi_stop_packet(encoder, frame->any.type); 2998c2ecf20Sopenharmony_ci if (ret) { 3008c2ecf20Sopenharmony_ci DRM_ERROR("Failed to wait for infoframe to go idle: %d\n", ret); 3018c2ecf20Sopenharmony_ci return; 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci for (i = 0; i < len; i += 7) { 3058c2ecf20Sopenharmony_ci writel(buffer[i + 0] << 0 | 3068c2ecf20Sopenharmony_ci buffer[i + 1] << 8 | 3078c2ecf20Sopenharmony_ci buffer[i + 2] << 16, 3088c2ecf20Sopenharmony_ci base + packet_reg); 3098c2ecf20Sopenharmony_ci packet_reg += 4; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci writel(buffer[i + 3] << 0 | 3128c2ecf20Sopenharmony_ci buffer[i + 4] << 8 | 3138c2ecf20Sopenharmony_ci buffer[i + 5] << 16 | 3148c2ecf20Sopenharmony_ci buffer[i + 6] << 24, 3158c2ecf20Sopenharmony_ci base + packet_reg); 3168c2ecf20Sopenharmony_ci packet_reg += 4; 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 3208c2ecf20Sopenharmony_ci HDMI_READ(HDMI_RAM_PACKET_CONFIG) | BIT(packet_id)); 3218c2ecf20Sopenharmony_ci ret = wait_for((HDMI_READ(HDMI_RAM_PACKET_STATUS) & 3228c2ecf20Sopenharmony_ci BIT(packet_id)), 100); 3238c2ecf20Sopenharmony_ci if (ret) 3248c2ecf20Sopenharmony_ci DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret); 3258c2ecf20Sopenharmony_ci} 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_cistatic void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) 3288c2ecf20Sopenharmony_ci{ 3298c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 3308c2ecf20Sopenharmony_ci struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); 3318c2ecf20Sopenharmony_ci struct drm_connector *connector = &vc4_hdmi->connector; 3328c2ecf20Sopenharmony_ci struct drm_connector_state *cstate = connector->state; 3338c2ecf20Sopenharmony_ci struct drm_crtc *crtc = encoder->crtc; 3348c2ecf20Sopenharmony_ci const struct drm_display_mode *mode = &crtc->state->adjusted_mode; 3358c2ecf20Sopenharmony_ci union hdmi_infoframe frame; 3368c2ecf20Sopenharmony_ci int ret; 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, 3398c2ecf20Sopenharmony_ci connector, mode); 3408c2ecf20Sopenharmony_ci if (ret < 0) { 3418c2ecf20Sopenharmony_ci DRM_ERROR("couldn't fill AVI infoframe\n"); 3428c2ecf20Sopenharmony_ci return; 3438c2ecf20Sopenharmony_ci } 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci drm_hdmi_avi_infoframe_quant_range(&frame.avi, 3468c2ecf20Sopenharmony_ci connector, mode, 3478c2ecf20Sopenharmony_ci vc4_encoder->limited_rgb_range ? 3488c2ecf20Sopenharmony_ci HDMI_QUANTIZATION_RANGE_LIMITED : 3498c2ecf20Sopenharmony_ci HDMI_QUANTIZATION_RANGE_FULL); 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci drm_hdmi_avi_infoframe_bars(&frame.avi, cstate); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci vc4_hdmi_write_infoframe(encoder, &frame); 3548c2ecf20Sopenharmony_ci} 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_cistatic void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder) 3578c2ecf20Sopenharmony_ci{ 3588c2ecf20Sopenharmony_ci union hdmi_infoframe frame; 3598c2ecf20Sopenharmony_ci int ret; 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci ret = hdmi_spd_infoframe_init(&frame.spd, "Broadcom", "Videocore"); 3628c2ecf20Sopenharmony_ci if (ret < 0) { 3638c2ecf20Sopenharmony_ci DRM_ERROR("couldn't fill SPD infoframe\n"); 3648c2ecf20Sopenharmony_ci return; 3658c2ecf20Sopenharmony_ci } 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci frame.spd.sdi = HDMI_SPD_SDI_PC; 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci vc4_hdmi_write_infoframe(encoder, &frame); 3708c2ecf20Sopenharmony_ci} 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_cistatic void vc4_hdmi_set_audio_infoframe(struct drm_encoder *encoder) 3738c2ecf20Sopenharmony_ci{ 3748c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 3758c2ecf20Sopenharmony_ci union hdmi_infoframe frame; 3768c2ecf20Sopenharmony_ci int ret; 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci ret = hdmi_audio_infoframe_init(&frame.audio); 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci frame.audio.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; 3818c2ecf20Sopenharmony_ci frame.audio.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; 3828c2ecf20Sopenharmony_ci frame.audio.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; 3838c2ecf20Sopenharmony_ci frame.audio.channels = vc4_hdmi->audio.channels; 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci vc4_hdmi_write_infoframe(encoder, &frame); 3868c2ecf20Sopenharmony_ci} 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_cistatic void vc4_hdmi_set_infoframes(struct drm_encoder *encoder) 3898c2ecf20Sopenharmony_ci{ 3908c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci vc4_hdmi_set_avi_infoframe(encoder); 3938c2ecf20Sopenharmony_ci vc4_hdmi_set_spd_infoframe(encoder); 3948c2ecf20Sopenharmony_ci /* 3958c2ecf20Sopenharmony_ci * If audio was streaming, then we need to reenabled the audio 3968c2ecf20Sopenharmony_ci * infoframe here during encoder_enable. 3978c2ecf20Sopenharmony_ci */ 3988c2ecf20Sopenharmony_ci if (vc4_hdmi->audio.streaming) 3998c2ecf20Sopenharmony_ci vc4_hdmi_set_audio_infoframe(encoder); 4008c2ecf20Sopenharmony_ci} 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_cistatic void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder) 4038c2ecf20Sopenharmony_ci{ 4048c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 0); 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | 4098c2ecf20Sopenharmony_ci VC4_HD_VID_CTL_CLRRGB | VC4_HD_VID_CTL_CLRSYNC); 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VID_CTL, 4128c2ecf20Sopenharmony_ci HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_BLANKPIX); 4138c2ecf20Sopenharmony_ci} 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_cistatic void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder) 4168c2ecf20Sopenharmony_ci{ 4178c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 4188c2ecf20Sopenharmony_ci int ret; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->phy_disable) 4218c2ecf20Sopenharmony_ci vc4_hdmi->variant->phy_disable(vc4_hdmi); 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VID_CTL, 4248c2ecf20Sopenharmony_ci HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE); 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci clk_disable_unprepare(vc4_hdmi->pixel_bvb_clock); 4278c2ecf20Sopenharmony_ci clk_disable_unprepare(vc4_hdmi->pixel_clock); 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci ret = pm_runtime_put(&vc4_hdmi->pdev->dev); 4308c2ecf20Sopenharmony_ci if (ret < 0) 4318c2ecf20Sopenharmony_ci DRM_ERROR("Failed to release power domain: %d\n", ret); 4328c2ecf20Sopenharmony_ci} 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_cistatic void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) 4358c2ecf20Sopenharmony_ci{ 4368c2ecf20Sopenharmony_ci} 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_cistatic void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) 4398c2ecf20Sopenharmony_ci{ 4408c2ecf20Sopenharmony_ci u32 csc_ctl; 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci csc_ctl = VC4_SET_FIELD(VC4_HD_CSC_CTL_ORDER_BGR, 4438c2ecf20Sopenharmony_ci VC4_HD_CSC_CTL_ORDER); 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci if (enable) { 4468c2ecf20Sopenharmony_ci /* CEA VICs other than #1 requre limited range RGB 4478c2ecf20Sopenharmony_ci * output unless overridden by an AVI infoframe. 4488c2ecf20Sopenharmony_ci * Apply a colorspace conversion to squash 0-255 down 4498c2ecf20Sopenharmony_ci * to 16-235. The matrix here is: 4508c2ecf20Sopenharmony_ci * 4518c2ecf20Sopenharmony_ci * [ 0 0 0.8594 16] 4528c2ecf20Sopenharmony_ci * [ 0 0.8594 0 16] 4538c2ecf20Sopenharmony_ci * [ 0.8594 0 0 16] 4548c2ecf20Sopenharmony_ci * [ 0 0 0 1] 4558c2ecf20Sopenharmony_ci */ 4568c2ecf20Sopenharmony_ci csc_ctl |= VC4_HD_CSC_CTL_ENABLE; 4578c2ecf20Sopenharmony_ci csc_ctl |= VC4_HD_CSC_CTL_RGB2YCC; 4588c2ecf20Sopenharmony_ci csc_ctl |= VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM, 4598c2ecf20Sopenharmony_ci VC4_HD_CSC_CTL_MODE); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_12_11, (0x000 << 16) | 0x000); 4628c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_14_13, (0x100 << 16) | 0x6e0); 4638c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_22_21, (0x6e0 << 16) | 0x000); 4648c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_24_23, (0x100 << 16) | 0x000); 4658c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_32_31, (0x000 << 16) | 0x6e0); 4668c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_34_33, (0x100 << 16) | 0x000); 4678c2ecf20Sopenharmony_ci } 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci /* The RGB order applies even when CSC is disabled. */ 4708c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_CTL, csc_ctl); 4718c2ecf20Sopenharmony_ci} 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_cistatic void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) 4748c2ecf20Sopenharmony_ci{ 4758c2ecf20Sopenharmony_ci u32 csc_ctl; 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci csc_ctl = 0x07; /* RGB_CONVERT_MODE = custom matrix, || USE_RGB_TO_YCBCR */ 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci if (enable) { 4808c2ecf20Sopenharmony_ci /* CEA VICs other than #1 requre limited range RGB 4818c2ecf20Sopenharmony_ci * output unless overridden by an AVI infoframe. 4828c2ecf20Sopenharmony_ci * Apply a colorspace conversion to squash 0-255 down 4838c2ecf20Sopenharmony_ci * to 16-235. The matrix here is: 4848c2ecf20Sopenharmony_ci * 4858c2ecf20Sopenharmony_ci * [ 0.8594 0 0 16] 4868c2ecf20Sopenharmony_ci * [ 0 0.8594 0 16] 4878c2ecf20Sopenharmony_ci * [ 0 0 0.8594 16] 4888c2ecf20Sopenharmony_ci * [ 0 0 0 1] 4898c2ecf20Sopenharmony_ci * Matrix is signed 2p13 fixed point, with signed 9p6 offsets 4908c2ecf20Sopenharmony_ci */ 4918c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x1b80); 4928c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_14_13, (0x0400 << 16) | 0x0000); 4938c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_22_21, (0x1b80 << 16) | 0x0000); 4948c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_24_23, (0x0400 << 16) | 0x0000); 4958c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000); 4968c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_34_33, (0x0400 << 16) | 0x1b80); 4978c2ecf20Sopenharmony_ci } else { 4988c2ecf20Sopenharmony_ci /* Still use the matrix for full range, but make it unity. 4998c2ecf20Sopenharmony_ci * Matrix is signed 2p13 fixed point, with signed 9p6 offsets 5008c2ecf20Sopenharmony_ci */ 5018c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x2000); 5028c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_14_13, (0x0000 << 16) | 0x0000); 5038c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_22_21, (0x2000 << 16) | 0x0000); 5048c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_24_23, (0x0000 << 16) | 0x0000); 5058c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000); 5068c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_34_33, (0x0000 << 16) | 0x2000); 5078c2ecf20Sopenharmony_ci } 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CSC_CTL, csc_ctl); 5108c2ecf20Sopenharmony_ci} 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_cistatic void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, 5138c2ecf20Sopenharmony_ci struct drm_display_mode *mode) 5148c2ecf20Sopenharmony_ci{ 5158c2ecf20Sopenharmony_ci bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; 5168c2ecf20Sopenharmony_ci bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; 5178c2ecf20Sopenharmony_ci bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; 5188c2ecf20Sopenharmony_ci u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1; 5198c2ecf20Sopenharmony_ci u32 verta = (VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start, 5208c2ecf20Sopenharmony_ci VC4_HDMI_VERTA_VSP) | 5218c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay, 5228c2ecf20Sopenharmony_ci VC4_HDMI_VERTA_VFP) | 5238c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->crtc_vdisplay, VC4_HDMI_VERTA_VAL)); 5248c2ecf20Sopenharmony_ci u32 vertb = (VC4_SET_FIELD(0, VC4_HDMI_VERTB_VSPO) | 5258c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end + 5268c2ecf20Sopenharmony_ci interlaced, 5278c2ecf20Sopenharmony_ci VC4_HDMI_VERTB_VBP)); 5288c2ecf20Sopenharmony_ci u32 vertb_even = (VC4_SET_FIELD(0, VC4_HDMI_VERTB_VSPO) | 5298c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->crtc_vtotal - 5308c2ecf20Sopenharmony_ci mode->crtc_vsync_end, 5318c2ecf20Sopenharmony_ci VC4_HDMI_VERTB_VBP)); 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_HORZA, 5348c2ecf20Sopenharmony_ci (vsync_pos ? VC4_HDMI_HORZA_VPOS : 0) | 5358c2ecf20Sopenharmony_ci (hsync_pos ? VC4_HDMI_HORZA_HPOS : 0) | 5368c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->hdisplay * pixel_rep, 5378c2ecf20Sopenharmony_ci VC4_HDMI_HORZA_HAP)); 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_HORZB, 5408c2ecf20Sopenharmony_ci VC4_SET_FIELD((mode->htotal - 5418c2ecf20Sopenharmony_ci mode->hsync_end) * pixel_rep, 5428c2ecf20Sopenharmony_ci VC4_HDMI_HORZB_HBP) | 5438c2ecf20Sopenharmony_ci VC4_SET_FIELD((mode->hsync_end - 5448c2ecf20Sopenharmony_ci mode->hsync_start) * pixel_rep, 5458c2ecf20Sopenharmony_ci VC4_HDMI_HORZB_HSP) | 5468c2ecf20Sopenharmony_ci VC4_SET_FIELD((mode->hsync_start - 5478c2ecf20Sopenharmony_ci mode->hdisplay) * pixel_rep, 5488c2ecf20Sopenharmony_ci VC4_HDMI_HORZB_HFP)); 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VERTA0, verta); 5518c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VERTA1, verta); 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VERTB0, vertb_even); 5548c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VERTB1, vertb); 5558c2ecf20Sopenharmony_ci} 5568c2ecf20Sopenharmony_cistatic void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, 5578c2ecf20Sopenharmony_ci struct drm_display_mode *mode) 5588c2ecf20Sopenharmony_ci{ 5598c2ecf20Sopenharmony_ci bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; 5608c2ecf20Sopenharmony_ci bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; 5618c2ecf20Sopenharmony_ci bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; 5628c2ecf20Sopenharmony_ci u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1; 5638c2ecf20Sopenharmony_ci u32 verta = (VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start, 5648c2ecf20Sopenharmony_ci VC5_HDMI_VERTA_VSP) | 5658c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay, 5668c2ecf20Sopenharmony_ci VC5_HDMI_VERTA_VFP) | 5678c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->crtc_vdisplay, VC5_HDMI_VERTA_VAL)); 5688c2ecf20Sopenharmony_ci u32 vertb = (VC4_SET_FIELD(mode->htotal >> (2 - pixel_rep), 5698c2ecf20Sopenharmony_ci VC5_HDMI_VERTB_VSPO) | 5708c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end + 5718c2ecf20Sopenharmony_ci interlaced, 5728c2ecf20Sopenharmony_ci VC4_HDMI_VERTB_VBP)); 5738c2ecf20Sopenharmony_ci u32 vertb_even = (VC4_SET_FIELD(0, VC5_HDMI_VERTB_VSPO) | 5748c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->crtc_vtotal - 5758c2ecf20Sopenharmony_ci mode->crtc_vsync_end, 5768c2ecf20Sopenharmony_ci VC4_HDMI_VERTB_VBP)); 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021); 5798c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_HORZA, 5808c2ecf20Sopenharmony_ci (vsync_pos ? VC5_HDMI_HORZA_VPOS : 0) | 5818c2ecf20Sopenharmony_ci (hsync_pos ? VC5_HDMI_HORZA_HPOS : 0) | 5828c2ecf20Sopenharmony_ci VC4_SET_FIELD(mode->hdisplay * pixel_rep, 5838c2ecf20Sopenharmony_ci VC5_HDMI_HORZA_HAP) | 5848c2ecf20Sopenharmony_ci VC4_SET_FIELD((mode->hsync_start - 5858c2ecf20Sopenharmony_ci mode->hdisplay) * pixel_rep, 5868c2ecf20Sopenharmony_ci VC5_HDMI_HORZA_HFP)); 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_HORZB, 5898c2ecf20Sopenharmony_ci VC4_SET_FIELD((mode->htotal - 5908c2ecf20Sopenharmony_ci mode->hsync_end) * pixel_rep, 5918c2ecf20Sopenharmony_ci VC5_HDMI_HORZB_HBP) | 5928c2ecf20Sopenharmony_ci VC4_SET_FIELD((mode->hsync_end - 5938c2ecf20Sopenharmony_ci mode->hsync_start) * pixel_rep, 5948c2ecf20Sopenharmony_ci VC5_HDMI_HORZB_HSP)); 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VERTA0, verta); 5978c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VERTA1, verta); 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VERTB0, vertb_even); 6008c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VERTB1, vertb); 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CLOCK_STOP, 0); 6038c2ecf20Sopenharmony_ci} 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_cistatic void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi) 6068c2ecf20Sopenharmony_ci{ 6078c2ecf20Sopenharmony_ci u32 drift; 6088c2ecf20Sopenharmony_ci int ret; 6098c2ecf20Sopenharmony_ci 6108c2ecf20Sopenharmony_ci drift = HDMI_READ(HDMI_FIFO_CTL); 6118c2ecf20Sopenharmony_ci drift &= VC4_HDMI_FIFO_VALID_WRITE_MASK; 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_FIFO_CTL, 6148c2ecf20Sopenharmony_ci drift & ~VC4_HDMI_FIFO_CTL_RECENTER); 6158c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_FIFO_CTL, 6168c2ecf20Sopenharmony_ci drift | VC4_HDMI_FIFO_CTL_RECENTER); 6178c2ecf20Sopenharmony_ci usleep_range(1000, 1100); 6188c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_FIFO_CTL, 6198c2ecf20Sopenharmony_ci drift & ~VC4_HDMI_FIFO_CTL_RECENTER); 6208c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_FIFO_CTL, 6218c2ecf20Sopenharmony_ci drift | VC4_HDMI_FIFO_CTL_RECENTER); 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci ret = wait_for(HDMI_READ(HDMI_FIFO_CTL) & 6248c2ecf20Sopenharmony_ci VC4_HDMI_FIFO_CTL_RECENTER_DONE, 1); 6258c2ecf20Sopenharmony_ci WARN_ONCE(ret, "Timeout waiting for " 6268c2ecf20Sopenharmony_ci "VC4_HDMI_FIFO_CTL_RECENTER_DONE"); 6278c2ecf20Sopenharmony_ci} 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_cistatic void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder) 6308c2ecf20Sopenharmony_ci{ 6318c2ecf20Sopenharmony_ci struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; 6328c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 6338c2ecf20Sopenharmony_ci unsigned long pixel_rate, hsm_rate; 6348c2ecf20Sopenharmony_ci int ret; 6358c2ecf20Sopenharmony_ci 6368c2ecf20Sopenharmony_ci ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev); 6378c2ecf20Sopenharmony_ci if (ret < 0) { 6388c2ecf20Sopenharmony_ci DRM_ERROR("Failed to retain power domain: %d\n", ret); 6398c2ecf20Sopenharmony_ci return; 6408c2ecf20Sopenharmony_ci } 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci pixel_rate = mode->clock * 1000 * ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1); 6438c2ecf20Sopenharmony_ci ret = clk_set_rate(vc4_hdmi->pixel_clock, pixel_rate); 6448c2ecf20Sopenharmony_ci if (ret) { 6458c2ecf20Sopenharmony_ci DRM_ERROR("Failed to set pixel clock rate: %d\n", ret); 6468c2ecf20Sopenharmony_ci return; 6478c2ecf20Sopenharmony_ci } 6488c2ecf20Sopenharmony_ci 6498c2ecf20Sopenharmony_ci ret = clk_prepare_enable(vc4_hdmi->pixel_clock); 6508c2ecf20Sopenharmony_ci if (ret) { 6518c2ecf20Sopenharmony_ci DRM_ERROR("Failed to turn on pixel clock: %d\n", ret); 6528c2ecf20Sopenharmony_ci return; 6538c2ecf20Sopenharmony_ci } 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_ci /* 6568c2ecf20Sopenharmony_ci * As stated in RPi's vc4 firmware "HDMI state machine (HSM) clock must 6578c2ecf20Sopenharmony_ci * be faster than pixel clock, infinitesimally faster, tested in 6588c2ecf20Sopenharmony_ci * simulation. Otherwise, exact value is unimportant for HDMI 6598c2ecf20Sopenharmony_ci * operation." This conflicts with bcm2835's vc4 documentation, which 6608c2ecf20Sopenharmony_ci * states HSM's clock has to be at least 108% of the pixel clock. 6618c2ecf20Sopenharmony_ci * 6628c2ecf20Sopenharmony_ci * Real life tests reveal that vc4's firmware statement holds up, and 6638c2ecf20Sopenharmony_ci * users are able to use pixel clocks closer to HSM's, namely for 6648c2ecf20Sopenharmony_ci * 1920x1200@60Hz. So it was decided to have leave a 1% margin between 6658c2ecf20Sopenharmony_ci * both clocks. Which, for RPi0-3 implies a maximum pixel clock of 6668c2ecf20Sopenharmony_ci * 162MHz. 6678c2ecf20Sopenharmony_ci * 6688c2ecf20Sopenharmony_ci * Additionally, the AXI clock needs to be at least 25% of 6698c2ecf20Sopenharmony_ci * pixel clock, but HSM ends up being the limiting factor. 6708c2ecf20Sopenharmony_ci */ 6718c2ecf20Sopenharmony_ci hsm_rate = max_t(unsigned long, 120000000, (pixel_rate / 100) * 101); 6728c2ecf20Sopenharmony_ci ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate); 6738c2ecf20Sopenharmony_ci if (ret) { 6748c2ecf20Sopenharmony_ci DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); 6758c2ecf20Sopenharmony_ci return; 6768c2ecf20Sopenharmony_ci } 6778c2ecf20Sopenharmony_ci 6788c2ecf20Sopenharmony_ci vc4_hdmi_cec_update_clk_div(vc4_hdmi); 6798c2ecf20Sopenharmony_ci 6808c2ecf20Sopenharmony_ci /* 6818c2ecf20Sopenharmony_ci * FIXME: When the pixel freq is 594MHz (4k60), this needs to be setup 6828c2ecf20Sopenharmony_ci * at 300MHz. 6838c2ecf20Sopenharmony_ci */ 6848c2ecf20Sopenharmony_ci ret = clk_set_min_rate(vc4_hdmi->pixel_bvb_clock, 6858c2ecf20Sopenharmony_ci (hsm_rate > VC4_HSM_MID_CLOCK ? 150000000 : 75000000)); 6868c2ecf20Sopenharmony_ci if (ret) { 6878c2ecf20Sopenharmony_ci DRM_ERROR("Failed to set pixel bvb clock rate: %d\n", ret); 6888c2ecf20Sopenharmony_ci clk_disable_unprepare(vc4_hdmi->pixel_clock); 6898c2ecf20Sopenharmony_ci return; 6908c2ecf20Sopenharmony_ci } 6918c2ecf20Sopenharmony_ci 6928c2ecf20Sopenharmony_ci ret = clk_prepare_enable(vc4_hdmi->pixel_bvb_clock); 6938c2ecf20Sopenharmony_ci if (ret) { 6948c2ecf20Sopenharmony_ci DRM_ERROR("Failed to turn on pixel bvb clock: %d\n", ret); 6958c2ecf20Sopenharmony_ci clk_disable_unprepare(vc4_hdmi->pixel_clock); 6968c2ecf20Sopenharmony_ci return; 6978c2ecf20Sopenharmony_ci } 6988c2ecf20Sopenharmony_ci 6998c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->phy_init) 7008c2ecf20Sopenharmony_ci vc4_hdmi->variant->phy_init(vc4_hdmi, mode); 7018c2ecf20Sopenharmony_ci 7028c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_SCHEDULER_CONTROL, 7038c2ecf20Sopenharmony_ci HDMI_READ(HDMI_SCHEDULER_CONTROL) | 7048c2ecf20Sopenharmony_ci VC4_HDMI_SCHEDULER_CONTROL_MANUAL_FORMAT | 7058c2ecf20Sopenharmony_ci VC4_HDMI_SCHEDULER_CONTROL_IGNORE_VSYNC_PREDICTS); 7068c2ecf20Sopenharmony_ci 7078c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->set_timings) 7088c2ecf20Sopenharmony_ci vc4_hdmi->variant->set_timings(vc4_hdmi, mode); 7098c2ecf20Sopenharmony_ci} 7108c2ecf20Sopenharmony_ci 7118c2ecf20Sopenharmony_cistatic void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder) 7128c2ecf20Sopenharmony_ci{ 7138c2ecf20Sopenharmony_ci struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; 7148c2ecf20Sopenharmony_ci struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); 7158c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 7168c2ecf20Sopenharmony_ci 7178c2ecf20Sopenharmony_ci if (vc4_encoder->hdmi_monitor && 7188c2ecf20Sopenharmony_ci drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED) { 7198c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->csc_setup) 7208c2ecf20Sopenharmony_ci vc4_hdmi->variant->csc_setup(vc4_hdmi, true); 7218c2ecf20Sopenharmony_ci 7228c2ecf20Sopenharmony_ci vc4_encoder->limited_rgb_range = true; 7238c2ecf20Sopenharmony_ci } else { 7248c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->csc_setup) 7258c2ecf20Sopenharmony_ci vc4_hdmi->variant->csc_setup(vc4_hdmi, false); 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ci vc4_encoder->limited_rgb_range = false; 7288c2ecf20Sopenharmony_ci } 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); 7318c2ecf20Sopenharmony_ci} 7328c2ecf20Sopenharmony_ci 7338c2ecf20Sopenharmony_cistatic void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder) 7348c2ecf20Sopenharmony_ci{ 7358c2ecf20Sopenharmony_ci struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; 7368c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 7378c2ecf20Sopenharmony_ci struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); 7388c2ecf20Sopenharmony_ci bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; 7398c2ecf20Sopenharmony_ci bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; 7408c2ecf20Sopenharmony_ci int ret; 7418c2ecf20Sopenharmony_ci 7428c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VID_CTL, 7438c2ecf20Sopenharmony_ci VC4_HD_VID_CTL_ENABLE | 7448c2ecf20Sopenharmony_ci VC4_HD_VID_CTL_UNDERFLOW_ENABLE | 7458c2ecf20Sopenharmony_ci VC4_HD_VID_CTL_FRAME_COUNTER_RESET | 7468c2ecf20Sopenharmony_ci (vsync_pos ? 0 : VC4_HD_VID_CTL_VSYNC_LOW) | 7478c2ecf20Sopenharmony_ci (hsync_pos ? 0 : VC4_HD_VID_CTL_HSYNC_LOW)); 7488c2ecf20Sopenharmony_ci 7498c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_VID_CTL, 7508c2ecf20Sopenharmony_ci HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_BLANKPIX); 7518c2ecf20Sopenharmony_ci 7528c2ecf20Sopenharmony_ci if (vc4_encoder->hdmi_monitor) { 7538c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_SCHEDULER_CONTROL, 7548c2ecf20Sopenharmony_ci HDMI_READ(HDMI_SCHEDULER_CONTROL) | 7558c2ecf20Sopenharmony_ci VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI); 7568c2ecf20Sopenharmony_ci 7578c2ecf20Sopenharmony_ci ret = wait_for(HDMI_READ(HDMI_SCHEDULER_CONTROL) & 7588c2ecf20Sopenharmony_ci VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE, 1000); 7598c2ecf20Sopenharmony_ci WARN_ONCE(ret, "Timeout waiting for " 7608c2ecf20Sopenharmony_ci "VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n"); 7618c2ecf20Sopenharmony_ci } else { 7628c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 7638c2ecf20Sopenharmony_ci HDMI_READ(HDMI_RAM_PACKET_CONFIG) & 7648c2ecf20Sopenharmony_ci ~(VC4_HDMI_RAM_PACKET_ENABLE)); 7658c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_SCHEDULER_CONTROL, 7668c2ecf20Sopenharmony_ci HDMI_READ(HDMI_SCHEDULER_CONTROL) & 7678c2ecf20Sopenharmony_ci ~VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI); 7688c2ecf20Sopenharmony_ci 7698c2ecf20Sopenharmony_ci ret = wait_for(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) & 7708c2ecf20Sopenharmony_ci VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE), 1000); 7718c2ecf20Sopenharmony_ci WARN_ONCE(ret, "Timeout waiting for " 7728c2ecf20Sopenharmony_ci "!VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n"); 7738c2ecf20Sopenharmony_ci } 7748c2ecf20Sopenharmony_ci 7758c2ecf20Sopenharmony_ci if (vc4_encoder->hdmi_monitor) { 7768c2ecf20Sopenharmony_ci WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) & 7778c2ecf20Sopenharmony_ci VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE)); 7788c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_SCHEDULER_CONTROL, 7798c2ecf20Sopenharmony_ci HDMI_READ(HDMI_SCHEDULER_CONTROL) | 7808c2ecf20Sopenharmony_ci VC4_HDMI_SCHEDULER_CONTROL_VERT_ALWAYS_KEEPOUT); 7818c2ecf20Sopenharmony_ci 7828c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 7838c2ecf20Sopenharmony_ci VC4_HDMI_RAM_PACKET_ENABLE); 7848c2ecf20Sopenharmony_ci 7858c2ecf20Sopenharmony_ci vc4_hdmi_set_infoframes(encoder); 7868c2ecf20Sopenharmony_ci } 7878c2ecf20Sopenharmony_ci 7888c2ecf20Sopenharmony_ci vc4_hdmi_recenter_fifo(vc4_hdmi); 7898c2ecf20Sopenharmony_ci} 7908c2ecf20Sopenharmony_ci 7918c2ecf20Sopenharmony_cistatic void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) 7928c2ecf20Sopenharmony_ci{ 7938c2ecf20Sopenharmony_ci} 7948c2ecf20Sopenharmony_ci 7958c2ecf20Sopenharmony_ci#define WIFI_2_4GHz_CH1_MIN_FREQ 2400000000ULL 7968c2ecf20Sopenharmony_ci#define WIFI_2_4GHz_CH1_MAX_FREQ 2422000000ULL 7978c2ecf20Sopenharmony_ci 7988c2ecf20Sopenharmony_cistatic int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, 7998c2ecf20Sopenharmony_ci struct drm_crtc_state *crtc_state, 8008c2ecf20Sopenharmony_ci struct drm_connector_state *conn_state) 8018c2ecf20Sopenharmony_ci{ 8028c2ecf20Sopenharmony_ci struct drm_display_mode *mode = &crtc_state->adjusted_mode; 8038c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 8048c2ecf20Sopenharmony_ci unsigned long long pixel_rate = mode->clock * 1000; 8058c2ecf20Sopenharmony_ci unsigned long long tmds_rate; 8068c2ecf20Sopenharmony_ci 8078c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->unsupported_odd_h_timings && 8088c2ecf20Sopenharmony_ci !(mode->flags & DRM_MODE_FLAG_DBLCLK) && 8098c2ecf20Sopenharmony_ci ((mode->hdisplay % 2) || (mode->hsync_start % 2) || 8108c2ecf20Sopenharmony_ci (mode->hsync_end % 2) || (mode->htotal % 2))) 8118c2ecf20Sopenharmony_ci return -EINVAL; 8128c2ecf20Sopenharmony_ci 8138c2ecf20Sopenharmony_ci /* 8148c2ecf20Sopenharmony_ci * The 1440p@60 pixel rate is in the same range than the first 8158c2ecf20Sopenharmony_ci * WiFi channel (between 2.4GHz and 2.422GHz with 22MHz 8168c2ecf20Sopenharmony_ci * bandwidth). Slightly lower the frequency to bring it out of 8178c2ecf20Sopenharmony_ci * the WiFi range. 8188c2ecf20Sopenharmony_ci */ 8198c2ecf20Sopenharmony_ci tmds_rate = pixel_rate * 10; 8208c2ecf20Sopenharmony_ci if (vc4_hdmi->disable_wifi_frequencies && 8218c2ecf20Sopenharmony_ci (tmds_rate >= WIFI_2_4GHz_CH1_MIN_FREQ && 8228c2ecf20Sopenharmony_ci tmds_rate <= WIFI_2_4GHz_CH1_MAX_FREQ)) { 8238c2ecf20Sopenharmony_ci mode->clock = 238560; 8248c2ecf20Sopenharmony_ci pixel_rate = mode->clock * 1000; 8258c2ecf20Sopenharmony_ci } 8268c2ecf20Sopenharmony_ci 8278c2ecf20Sopenharmony_ci if (mode->flags & DRM_MODE_FLAG_DBLCLK) 8288c2ecf20Sopenharmony_ci pixel_rate = pixel_rate * 2; 8298c2ecf20Sopenharmony_ci 8308c2ecf20Sopenharmony_ci if (pixel_rate > vc4_hdmi->variant->max_pixel_clock) 8318c2ecf20Sopenharmony_ci return -EINVAL; 8328c2ecf20Sopenharmony_ci 8338c2ecf20Sopenharmony_ci return 0; 8348c2ecf20Sopenharmony_ci} 8358c2ecf20Sopenharmony_ci 8368c2ecf20Sopenharmony_cistatic enum drm_mode_status 8378c2ecf20Sopenharmony_civc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder, 8388c2ecf20Sopenharmony_ci const struct drm_display_mode *mode) 8398c2ecf20Sopenharmony_ci{ 8408c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); 8418c2ecf20Sopenharmony_ci 8428c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->unsupported_odd_h_timings && 8438c2ecf20Sopenharmony_ci !(mode->flags & DRM_MODE_FLAG_DBLCLK) && 8448c2ecf20Sopenharmony_ci ((mode->hdisplay % 2) || (mode->hsync_start % 2) || 8458c2ecf20Sopenharmony_ci (mode->hsync_end % 2) || (mode->htotal % 2))) 8468c2ecf20Sopenharmony_ci return MODE_H_ILLEGAL; 8478c2ecf20Sopenharmony_ci 8488c2ecf20Sopenharmony_ci if ((mode->clock * 1000) > vc4_hdmi->variant->max_pixel_clock) 8498c2ecf20Sopenharmony_ci return MODE_CLOCK_HIGH; 8508c2ecf20Sopenharmony_ci 8518c2ecf20Sopenharmony_ci return MODE_OK; 8528c2ecf20Sopenharmony_ci} 8538c2ecf20Sopenharmony_ci 8548c2ecf20Sopenharmony_cistatic const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { 8558c2ecf20Sopenharmony_ci .atomic_check = vc4_hdmi_encoder_atomic_check, 8568c2ecf20Sopenharmony_ci .mode_valid = vc4_hdmi_encoder_mode_valid, 8578c2ecf20Sopenharmony_ci .disable = vc4_hdmi_encoder_disable, 8588c2ecf20Sopenharmony_ci .enable = vc4_hdmi_encoder_enable, 8598c2ecf20Sopenharmony_ci}; 8608c2ecf20Sopenharmony_ci 8618c2ecf20Sopenharmony_cistatic u32 vc4_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask) 8628c2ecf20Sopenharmony_ci{ 8638c2ecf20Sopenharmony_ci int i; 8648c2ecf20Sopenharmony_ci u32 channel_map = 0; 8658c2ecf20Sopenharmony_ci 8668c2ecf20Sopenharmony_ci for (i = 0; i < 8; i++) { 8678c2ecf20Sopenharmony_ci if (channel_mask & BIT(i)) 8688c2ecf20Sopenharmony_ci channel_map |= i << (3 * i); 8698c2ecf20Sopenharmony_ci } 8708c2ecf20Sopenharmony_ci return channel_map; 8718c2ecf20Sopenharmony_ci} 8728c2ecf20Sopenharmony_ci 8738c2ecf20Sopenharmony_cistatic u32 vc5_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask) 8748c2ecf20Sopenharmony_ci{ 8758c2ecf20Sopenharmony_ci int i; 8768c2ecf20Sopenharmony_ci u32 channel_map = 0; 8778c2ecf20Sopenharmony_ci 8788c2ecf20Sopenharmony_ci for (i = 0; i < 8; i++) { 8798c2ecf20Sopenharmony_ci if (channel_mask & BIT(i)) 8808c2ecf20Sopenharmony_ci channel_map |= i << (4 * i); 8818c2ecf20Sopenharmony_ci } 8828c2ecf20Sopenharmony_ci return channel_map; 8838c2ecf20Sopenharmony_ci} 8848c2ecf20Sopenharmony_ci 8858c2ecf20Sopenharmony_ci/* HDMI audio codec callbacks */ 8868c2ecf20Sopenharmony_cistatic void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi) 8878c2ecf20Sopenharmony_ci{ 8888c2ecf20Sopenharmony_ci u32 hsm_clock = clk_get_rate(vc4_hdmi->audio_clock); 8898c2ecf20Sopenharmony_ci unsigned long n, m; 8908c2ecf20Sopenharmony_ci 8918c2ecf20Sopenharmony_ci rational_best_approximation(hsm_clock, vc4_hdmi->audio.samplerate, 8928c2ecf20Sopenharmony_ci VC4_HD_MAI_SMP_N_MASK >> 8938c2ecf20Sopenharmony_ci VC4_HD_MAI_SMP_N_SHIFT, 8948c2ecf20Sopenharmony_ci (VC4_HD_MAI_SMP_M_MASK >> 8958c2ecf20Sopenharmony_ci VC4_HD_MAI_SMP_M_SHIFT) + 1, 8968c2ecf20Sopenharmony_ci &n, &m); 8978c2ecf20Sopenharmony_ci 8988c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_SMP, 8998c2ecf20Sopenharmony_ci VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) | 9008c2ecf20Sopenharmony_ci VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M)); 9018c2ecf20Sopenharmony_ci} 9028c2ecf20Sopenharmony_ci 9038c2ecf20Sopenharmony_cistatic void vc4_hdmi_set_n_cts(struct vc4_hdmi *vc4_hdmi) 9048c2ecf20Sopenharmony_ci{ 9058c2ecf20Sopenharmony_ci struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; 9068c2ecf20Sopenharmony_ci struct drm_crtc *crtc = encoder->crtc; 9078c2ecf20Sopenharmony_ci const struct drm_display_mode *mode = &crtc->state->adjusted_mode; 9088c2ecf20Sopenharmony_ci u32 samplerate = vc4_hdmi->audio.samplerate; 9098c2ecf20Sopenharmony_ci u32 n, cts; 9108c2ecf20Sopenharmony_ci u64 tmp; 9118c2ecf20Sopenharmony_ci 9128c2ecf20Sopenharmony_ci n = 128 * samplerate / 1000; 9138c2ecf20Sopenharmony_ci tmp = (u64)(mode->clock * 1000) * n; 9148c2ecf20Sopenharmony_ci do_div(tmp, 128 * samplerate); 9158c2ecf20Sopenharmony_ci cts = tmp; 9168c2ecf20Sopenharmony_ci 9178c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CRP_CFG, 9188c2ecf20Sopenharmony_ci VC4_HDMI_CRP_CFG_EXTERNAL_CTS_EN | 9198c2ecf20Sopenharmony_ci VC4_SET_FIELD(n, VC4_HDMI_CRP_CFG_N)); 9208c2ecf20Sopenharmony_ci 9218c2ecf20Sopenharmony_ci /* 9228c2ecf20Sopenharmony_ci * We could get slightly more accurate clocks in some cases by 9238c2ecf20Sopenharmony_ci * providing a CTS_1 value. The two CTS values are alternated 9248c2ecf20Sopenharmony_ci * between based on the period fields 9258c2ecf20Sopenharmony_ci */ 9268c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CTS_0, cts); 9278c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CTS_1, cts); 9288c2ecf20Sopenharmony_ci} 9298c2ecf20Sopenharmony_ci 9308c2ecf20Sopenharmony_cistatic inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai) 9318c2ecf20Sopenharmony_ci{ 9328c2ecf20Sopenharmony_ci struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); 9338c2ecf20Sopenharmony_ci 9348c2ecf20Sopenharmony_ci return snd_soc_card_get_drvdata(card); 9358c2ecf20Sopenharmony_ci} 9368c2ecf20Sopenharmony_ci 9378c2ecf20Sopenharmony_cistatic int vc4_hdmi_audio_startup(struct snd_pcm_substream *substream, 9388c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 9398c2ecf20Sopenharmony_ci{ 9408c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); 9418c2ecf20Sopenharmony_ci struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; 9428c2ecf20Sopenharmony_ci struct drm_connector *connector = &vc4_hdmi->connector; 9438c2ecf20Sopenharmony_ci int ret; 9448c2ecf20Sopenharmony_ci 9458c2ecf20Sopenharmony_ci if (vc4_hdmi->audio.substream && vc4_hdmi->audio.substream != substream) 9468c2ecf20Sopenharmony_ci return -EINVAL; 9478c2ecf20Sopenharmony_ci 9488c2ecf20Sopenharmony_ci vc4_hdmi->audio.substream = substream; 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ci /* 9518c2ecf20Sopenharmony_ci * If the HDMI encoder hasn't probed, or the encoder is 9528c2ecf20Sopenharmony_ci * currently in DVI mode, treat the codec dai as missing. 9538c2ecf20Sopenharmony_ci */ 9548c2ecf20Sopenharmony_ci if (!encoder->crtc || !(HDMI_READ(HDMI_RAM_PACKET_CONFIG) & 9558c2ecf20Sopenharmony_ci VC4_HDMI_RAM_PACKET_ENABLE)) 9568c2ecf20Sopenharmony_ci return -ENODEV; 9578c2ecf20Sopenharmony_ci 9588c2ecf20Sopenharmony_ci ret = snd_pcm_hw_constraint_eld(substream->runtime, connector->eld); 9598c2ecf20Sopenharmony_ci if (ret) 9608c2ecf20Sopenharmony_ci return ret; 9618c2ecf20Sopenharmony_ci 9628c2ecf20Sopenharmony_ci return 0; 9638c2ecf20Sopenharmony_ci} 9648c2ecf20Sopenharmony_ci 9658c2ecf20Sopenharmony_cistatic int vc4_hdmi_audio_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 9668c2ecf20Sopenharmony_ci{ 9678c2ecf20Sopenharmony_ci return 0; 9688c2ecf20Sopenharmony_ci} 9698c2ecf20Sopenharmony_ci 9708c2ecf20Sopenharmony_cistatic void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi) 9718c2ecf20Sopenharmony_ci{ 9728c2ecf20Sopenharmony_ci struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; 9738c2ecf20Sopenharmony_ci struct device *dev = &vc4_hdmi->pdev->dev; 9748c2ecf20Sopenharmony_ci int ret; 9758c2ecf20Sopenharmony_ci 9768c2ecf20Sopenharmony_ci vc4_hdmi->audio.streaming = false; 9778c2ecf20Sopenharmony_ci ret = vc4_hdmi_stop_packet(encoder, HDMI_INFOFRAME_TYPE_AUDIO); 9788c2ecf20Sopenharmony_ci if (ret) 9798c2ecf20Sopenharmony_ci dev_err(dev, "Failed to stop audio infoframe: %d\n", ret); 9808c2ecf20Sopenharmony_ci 9818c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_RESET); 9828c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_ERRORF); 9838c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_FLUSH); 9848c2ecf20Sopenharmony_ci} 9858c2ecf20Sopenharmony_ci 9868c2ecf20Sopenharmony_cistatic void vc4_hdmi_audio_shutdown(struct snd_pcm_substream *substream, 9878c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 9888c2ecf20Sopenharmony_ci{ 9898c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); 9908c2ecf20Sopenharmony_ci 9918c2ecf20Sopenharmony_ci if (substream != vc4_hdmi->audio.substream) 9928c2ecf20Sopenharmony_ci return; 9938c2ecf20Sopenharmony_ci 9948c2ecf20Sopenharmony_ci vc4_hdmi_audio_reset(vc4_hdmi); 9958c2ecf20Sopenharmony_ci 9968c2ecf20Sopenharmony_ci vc4_hdmi->audio.substream = NULL; 9978c2ecf20Sopenharmony_ci} 9988c2ecf20Sopenharmony_ci 9998c2ecf20Sopenharmony_ci/* HDMI audio codec callbacks */ 10008c2ecf20Sopenharmony_cistatic int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream, 10018c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *params, 10028c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 10038c2ecf20Sopenharmony_ci{ 10048c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); 10058c2ecf20Sopenharmony_ci struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; 10068c2ecf20Sopenharmony_ci struct device *dev = &vc4_hdmi->pdev->dev; 10078c2ecf20Sopenharmony_ci u32 audio_packet_config, channel_mask; 10088c2ecf20Sopenharmony_ci u32 channel_map; 10098c2ecf20Sopenharmony_ci 10108c2ecf20Sopenharmony_ci if (substream != vc4_hdmi->audio.substream) 10118c2ecf20Sopenharmony_ci return -EINVAL; 10128c2ecf20Sopenharmony_ci 10138c2ecf20Sopenharmony_ci dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__, 10148c2ecf20Sopenharmony_ci params_rate(params), params_width(params), 10158c2ecf20Sopenharmony_ci params_channels(params)); 10168c2ecf20Sopenharmony_ci 10178c2ecf20Sopenharmony_ci vc4_hdmi->audio.channels = params_channels(params); 10188c2ecf20Sopenharmony_ci vc4_hdmi->audio.samplerate = params_rate(params); 10198c2ecf20Sopenharmony_ci 10208c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_CTL, 10218c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_RESET | 10228c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_FLUSH | 10238c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_DLATE | 10248c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_ERRORE | 10258c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_ERRORF); 10268c2ecf20Sopenharmony_ci 10278c2ecf20Sopenharmony_ci vc4_hdmi_audio_set_mai_clock(vc4_hdmi); 10288c2ecf20Sopenharmony_ci 10298c2ecf20Sopenharmony_ci /* The B frame identifier should match the value used by alsa-lib (8) */ 10308c2ecf20Sopenharmony_ci audio_packet_config = 10318c2ecf20Sopenharmony_ci VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_SAMPLE_FLAT | 10328c2ecf20Sopenharmony_ci VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_INACTIVE_CHANNELS | 10338c2ecf20Sopenharmony_ci VC4_SET_FIELD(0x8, VC4_HDMI_AUDIO_PACKET_B_FRAME_IDENTIFIER); 10348c2ecf20Sopenharmony_ci 10358c2ecf20Sopenharmony_ci channel_mask = GENMASK(vc4_hdmi->audio.channels - 1, 0); 10368c2ecf20Sopenharmony_ci audio_packet_config |= VC4_SET_FIELD(channel_mask, 10378c2ecf20Sopenharmony_ci VC4_HDMI_AUDIO_PACKET_CEA_MASK); 10388c2ecf20Sopenharmony_ci 10398c2ecf20Sopenharmony_ci /* Set the MAI threshold */ 10408c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_THR, 10418c2ecf20Sopenharmony_ci VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_PANICHIGH) | 10428c2ecf20Sopenharmony_ci VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_PANICLOW) | 10438c2ecf20Sopenharmony_ci VC4_SET_FIELD(0x06, VC4_HD_MAI_THR_DREQHIGH) | 10448c2ecf20Sopenharmony_ci VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_DREQLOW)); 10458c2ecf20Sopenharmony_ci 10468c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_CONFIG, 10478c2ecf20Sopenharmony_ci VC4_HDMI_MAI_CONFIG_BIT_REVERSE | 10488c2ecf20Sopenharmony_ci VC4_SET_FIELD(channel_mask, VC4_HDMI_MAI_CHANNEL_MASK)); 10498c2ecf20Sopenharmony_ci 10508c2ecf20Sopenharmony_ci channel_map = vc4_hdmi->variant->channel_map(vc4_hdmi, channel_mask); 10518c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_CHANNEL_MAP, channel_map); 10528c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_AUDIO_PACKET_CONFIG, audio_packet_config); 10538c2ecf20Sopenharmony_ci vc4_hdmi_set_n_cts(vc4_hdmi); 10548c2ecf20Sopenharmony_ci 10558c2ecf20Sopenharmony_ci vc4_hdmi_set_audio_infoframe(encoder); 10568c2ecf20Sopenharmony_ci 10578c2ecf20Sopenharmony_ci return 0; 10588c2ecf20Sopenharmony_ci} 10598c2ecf20Sopenharmony_ci 10608c2ecf20Sopenharmony_cistatic int vc4_hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd, 10618c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 10628c2ecf20Sopenharmony_ci{ 10638c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); 10648c2ecf20Sopenharmony_ci 10658c2ecf20Sopenharmony_ci switch (cmd) { 10668c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 10678c2ecf20Sopenharmony_ci vc4_hdmi->audio.streaming = true; 10688c2ecf20Sopenharmony_ci 10698c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->phy_rng_enable) 10708c2ecf20Sopenharmony_ci vc4_hdmi->variant->phy_rng_enable(vc4_hdmi); 10718c2ecf20Sopenharmony_ci 10728c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_CTL, 10738c2ecf20Sopenharmony_ci VC4_SET_FIELD(vc4_hdmi->audio.channels, 10748c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_CHNUM) | 10758c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_WHOLSMP | 10768c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_CHALIGN | 10778c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_ENABLE); 10788c2ecf20Sopenharmony_ci break; 10798c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 10808c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_MAI_CTL, 10818c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_DLATE | 10828c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_ERRORE | 10838c2ecf20Sopenharmony_ci VC4_HD_MAI_CTL_ERRORF); 10848c2ecf20Sopenharmony_ci 10858c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->phy_rng_disable) 10868c2ecf20Sopenharmony_ci vc4_hdmi->variant->phy_rng_disable(vc4_hdmi); 10878c2ecf20Sopenharmony_ci 10888c2ecf20Sopenharmony_ci vc4_hdmi->audio.streaming = false; 10898c2ecf20Sopenharmony_ci 10908c2ecf20Sopenharmony_ci break; 10918c2ecf20Sopenharmony_ci default: 10928c2ecf20Sopenharmony_ci break; 10938c2ecf20Sopenharmony_ci } 10948c2ecf20Sopenharmony_ci 10958c2ecf20Sopenharmony_ci return 0; 10968c2ecf20Sopenharmony_ci} 10978c2ecf20Sopenharmony_ci 10988c2ecf20Sopenharmony_cistatic inline struct vc4_hdmi * 10998c2ecf20Sopenharmony_cisnd_component_to_hdmi(struct snd_soc_component *component) 11008c2ecf20Sopenharmony_ci{ 11018c2ecf20Sopenharmony_ci struct snd_soc_card *card = snd_soc_component_get_drvdata(component); 11028c2ecf20Sopenharmony_ci 11038c2ecf20Sopenharmony_ci return snd_soc_card_get_drvdata(card); 11048c2ecf20Sopenharmony_ci} 11058c2ecf20Sopenharmony_ci 11068c2ecf20Sopenharmony_cistatic int vc4_hdmi_audio_eld_ctl_info(struct snd_kcontrol *kcontrol, 11078c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 11088c2ecf20Sopenharmony_ci{ 11098c2ecf20Sopenharmony_ci struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); 11108c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component); 11118c2ecf20Sopenharmony_ci struct drm_connector *connector = &vc4_hdmi->connector; 11128c2ecf20Sopenharmony_ci 11138c2ecf20Sopenharmony_ci uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; 11148c2ecf20Sopenharmony_ci uinfo->count = sizeof(connector->eld); 11158c2ecf20Sopenharmony_ci 11168c2ecf20Sopenharmony_ci return 0; 11178c2ecf20Sopenharmony_ci} 11188c2ecf20Sopenharmony_ci 11198c2ecf20Sopenharmony_cistatic int vc4_hdmi_audio_eld_ctl_get(struct snd_kcontrol *kcontrol, 11208c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 11218c2ecf20Sopenharmony_ci{ 11228c2ecf20Sopenharmony_ci struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); 11238c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component); 11248c2ecf20Sopenharmony_ci struct drm_connector *connector = &vc4_hdmi->connector; 11258c2ecf20Sopenharmony_ci 11268c2ecf20Sopenharmony_ci memcpy(ucontrol->value.bytes.data, connector->eld, 11278c2ecf20Sopenharmony_ci sizeof(connector->eld)); 11288c2ecf20Sopenharmony_ci 11298c2ecf20Sopenharmony_ci return 0; 11308c2ecf20Sopenharmony_ci} 11318c2ecf20Sopenharmony_ci 11328c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new vc4_hdmi_audio_controls[] = { 11338c2ecf20Sopenharmony_ci { 11348c2ecf20Sopenharmony_ci .access = SNDRV_CTL_ELEM_ACCESS_READ | 11358c2ecf20Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_VOLATILE, 11368c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_PCM, 11378c2ecf20Sopenharmony_ci .name = "ELD", 11388c2ecf20Sopenharmony_ci .info = vc4_hdmi_audio_eld_ctl_info, 11398c2ecf20Sopenharmony_ci .get = vc4_hdmi_audio_eld_ctl_get, 11408c2ecf20Sopenharmony_ci }, 11418c2ecf20Sopenharmony_ci}; 11428c2ecf20Sopenharmony_ci 11438c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget vc4_hdmi_audio_widgets[] = { 11448c2ecf20Sopenharmony_ci SND_SOC_DAPM_OUTPUT("TX"), 11458c2ecf20Sopenharmony_ci}; 11468c2ecf20Sopenharmony_ci 11478c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_route vc4_hdmi_audio_routes[] = { 11488c2ecf20Sopenharmony_ci { "TX", NULL, "Playback" }, 11498c2ecf20Sopenharmony_ci}; 11508c2ecf20Sopenharmony_ci 11518c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver vc4_hdmi_audio_component_drv = { 11528c2ecf20Sopenharmony_ci .name = "vc4-hdmi-codec-dai-component", 11538c2ecf20Sopenharmony_ci .controls = vc4_hdmi_audio_controls, 11548c2ecf20Sopenharmony_ci .num_controls = ARRAY_SIZE(vc4_hdmi_audio_controls), 11558c2ecf20Sopenharmony_ci .dapm_widgets = vc4_hdmi_audio_widgets, 11568c2ecf20Sopenharmony_ci .num_dapm_widgets = ARRAY_SIZE(vc4_hdmi_audio_widgets), 11578c2ecf20Sopenharmony_ci .dapm_routes = vc4_hdmi_audio_routes, 11588c2ecf20Sopenharmony_ci .num_dapm_routes = ARRAY_SIZE(vc4_hdmi_audio_routes), 11598c2ecf20Sopenharmony_ci .idle_bias_on = 1, 11608c2ecf20Sopenharmony_ci .use_pmdown_time = 1, 11618c2ecf20Sopenharmony_ci .endianness = 1, 11628c2ecf20Sopenharmony_ci .non_legacy_dai_naming = 1, 11638c2ecf20Sopenharmony_ci}; 11648c2ecf20Sopenharmony_ci 11658c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops vc4_hdmi_audio_dai_ops = { 11668c2ecf20Sopenharmony_ci .startup = vc4_hdmi_audio_startup, 11678c2ecf20Sopenharmony_ci .shutdown = vc4_hdmi_audio_shutdown, 11688c2ecf20Sopenharmony_ci .hw_params = vc4_hdmi_audio_hw_params, 11698c2ecf20Sopenharmony_ci .set_fmt = vc4_hdmi_audio_set_fmt, 11708c2ecf20Sopenharmony_ci .trigger = vc4_hdmi_audio_trigger, 11718c2ecf20Sopenharmony_ci}; 11728c2ecf20Sopenharmony_ci 11738c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver vc4_hdmi_audio_codec_dai_drv = { 11748c2ecf20Sopenharmony_ci .name = "vc4-hdmi-hifi", 11758c2ecf20Sopenharmony_ci .playback = { 11768c2ecf20Sopenharmony_ci .stream_name = "Playback", 11778c2ecf20Sopenharmony_ci .channels_min = 2, 11788c2ecf20Sopenharmony_ci .channels_max = 8, 11798c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | 11808c2ecf20Sopenharmony_ci SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | 11818c2ecf20Sopenharmony_ci SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | 11828c2ecf20Sopenharmony_ci SNDRV_PCM_RATE_192000, 11838c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, 11848c2ecf20Sopenharmony_ci }, 11858c2ecf20Sopenharmony_ci}; 11868c2ecf20Sopenharmony_ci 11878c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver vc4_hdmi_audio_cpu_dai_comp = { 11888c2ecf20Sopenharmony_ci .name = "vc4-hdmi-cpu-dai-component", 11898c2ecf20Sopenharmony_ci}; 11908c2ecf20Sopenharmony_ci 11918c2ecf20Sopenharmony_cistatic int vc4_hdmi_audio_cpu_dai_probe(struct snd_soc_dai *dai) 11928c2ecf20Sopenharmony_ci{ 11938c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); 11948c2ecf20Sopenharmony_ci 11958c2ecf20Sopenharmony_ci snd_soc_dai_init_dma_data(dai, &vc4_hdmi->audio.dma_data, NULL); 11968c2ecf20Sopenharmony_ci 11978c2ecf20Sopenharmony_ci return 0; 11988c2ecf20Sopenharmony_ci} 11998c2ecf20Sopenharmony_ci 12008c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver vc4_hdmi_audio_cpu_dai_drv = { 12018c2ecf20Sopenharmony_ci .name = "vc4-hdmi-cpu-dai", 12028c2ecf20Sopenharmony_ci .probe = vc4_hdmi_audio_cpu_dai_probe, 12038c2ecf20Sopenharmony_ci .playback = { 12048c2ecf20Sopenharmony_ci .stream_name = "Playback", 12058c2ecf20Sopenharmony_ci .channels_min = 1, 12068c2ecf20Sopenharmony_ci .channels_max = 8, 12078c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | 12088c2ecf20Sopenharmony_ci SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | 12098c2ecf20Sopenharmony_ci SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | 12108c2ecf20Sopenharmony_ci SNDRV_PCM_RATE_192000, 12118c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, 12128c2ecf20Sopenharmony_ci }, 12138c2ecf20Sopenharmony_ci .ops = &vc4_hdmi_audio_dai_ops, 12148c2ecf20Sopenharmony_ci}; 12158c2ecf20Sopenharmony_ci 12168c2ecf20Sopenharmony_cistatic const struct snd_dmaengine_pcm_config pcm_conf = { 12178c2ecf20Sopenharmony_ci .chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "audio-rx", 12188c2ecf20Sopenharmony_ci .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, 12198c2ecf20Sopenharmony_ci}; 12208c2ecf20Sopenharmony_ci 12218c2ecf20Sopenharmony_cistatic int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) 12228c2ecf20Sopenharmony_ci{ 12238c2ecf20Sopenharmony_ci const struct vc4_hdmi_register *mai_data = 12248c2ecf20Sopenharmony_ci &vc4_hdmi->variant->registers[HDMI_MAI_DATA]; 12258c2ecf20Sopenharmony_ci struct snd_soc_dai_link *dai_link = &vc4_hdmi->audio.link; 12268c2ecf20Sopenharmony_ci struct snd_soc_card *card = &vc4_hdmi->audio.card; 12278c2ecf20Sopenharmony_ci struct device *dev = &vc4_hdmi->pdev->dev; 12288c2ecf20Sopenharmony_ci const __be32 *addr; 12298c2ecf20Sopenharmony_ci int index, len; 12308c2ecf20Sopenharmony_ci int ret; 12318c2ecf20Sopenharmony_ci 12328c2ecf20Sopenharmony_ci if (!of_find_property(dev->of_node, "dmas", &len) || !len) { 12338c2ecf20Sopenharmony_ci dev_warn(dev, 12348c2ecf20Sopenharmony_ci "'dmas' DT property is missing or empty, no HDMI audio\n"); 12358c2ecf20Sopenharmony_ci return 0; 12368c2ecf20Sopenharmony_ci } 12378c2ecf20Sopenharmony_ci 12388c2ecf20Sopenharmony_ci if (mai_data->reg != VC4_HD) { 12398c2ecf20Sopenharmony_ci WARN_ONCE(true, "MAI isn't in the HD block\n"); 12408c2ecf20Sopenharmony_ci return -EINVAL; 12418c2ecf20Sopenharmony_ci } 12428c2ecf20Sopenharmony_ci 12438c2ecf20Sopenharmony_ci /* 12448c2ecf20Sopenharmony_ci * Get the physical address of VC4_HD_MAI_DATA. We need to retrieve 12458c2ecf20Sopenharmony_ci * the bus address specified in the DT, because the physical address 12468c2ecf20Sopenharmony_ci * (the one returned by platform_get_resource()) is not appropriate 12478c2ecf20Sopenharmony_ci * for DMA transfers. 12488c2ecf20Sopenharmony_ci * This VC/MMU should probably be exposed to avoid this kind of hacks. 12498c2ecf20Sopenharmony_ci */ 12508c2ecf20Sopenharmony_ci index = of_property_match_string(dev->of_node, "reg-names", "hd"); 12518c2ecf20Sopenharmony_ci /* Before BCM2711, we don't have a named register range */ 12528c2ecf20Sopenharmony_ci if (index < 0) 12538c2ecf20Sopenharmony_ci index = 1; 12548c2ecf20Sopenharmony_ci 12558c2ecf20Sopenharmony_ci addr = of_get_address(dev->of_node, index, NULL, NULL); 12568c2ecf20Sopenharmony_ci 12578c2ecf20Sopenharmony_ci vc4_hdmi->audio.dma_data.addr = be32_to_cpup(addr) + mai_data->offset; 12588c2ecf20Sopenharmony_ci vc4_hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; 12598c2ecf20Sopenharmony_ci vc4_hdmi->audio.dma_data.maxburst = 2; 12608c2ecf20Sopenharmony_ci 12618c2ecf20Sopenharmony_ci ret = devm_snd_dmaengine_pcm_register(dev, &pcm_conf, 0); 12628c2ecf20Sopenharmony_ci if (ret) { 12638c2ecf20Sopenharmony_ci dev_err(dev, "Could not register PCM component: %d\n", ret); 12648c2ecf20Sopenharmony_ci return ret; 12658c2ecf20Sopenharmony_ci } 12668c2ecf20Sopenharmony_ci 12678c2ecf20Sopenharmony_ci ret = devm_snd_soc_register_component(dev, &vc4_hdmi_audio_cpu_dai_comp, 12688c2ecf20Sopenharmony_ci &vc4_hdmi_audio_cpu_dai_drv, 1); 12698c2ecf20Sopenharmony_ci if (ret) { 12708c2ecf20Sopenharmony_ci dev_err(dev, "Could not register CPU DAI: %d\n", ret); 12718c2ecf20Sopenharmony_ci return ret; 12728c2ecf20Sopenharmony_ci } 12738c2ecf20Sopenharmony_ci 12748c2ecf20Sopenharmony_ci /* register component and codec dai */ 12758c2ecf20Sopenharmony_ci ret = devm_snd_soc_register_component(dev, &vc4_hdmi_audio_component_drv, 12768c2ecf20Sopenharmony_ci &vc4_hdmi_audio_codec_dai_drv, 1); 12778c2ecf20Sopenharmony_ci if (ret) { 12788c2ecf20Sopenharmony_ci dev_err(dev, "Could not register component: %d\n", ret); 12798c2ecf20Sopenharmony_ci return ret; 12808c2ecf20Sopenharmony_ci } 12818c2ecf20Sopenharmony_ci 12828c2ecf20Sopenharmony_ci dai_link->cpus = &vc4_hdmi->audio.cpu; 12838c2ecf20Sopenharmony_ci dai_link->codecs = &vc4_hdmi->audio.codec; 12848c2ecf20Sopenharmony_ci dai_link->platforms = &vc4_hdmi->audio.platform; 12858c2ecf20Sopenharmony_ci 12868c2ecf20Sopenharmony_ci dai_link->num_cpus = 1; 12878c2ecf20Sopenharmony_ci dai_link->num_codecs = 1; 12888c2ecf20Sopenharmony_ci dai_link->num_platforms = 1; 12898c2ecf20Sopenharmony_ci 12908c2ecf20Sopenharmony_ci dai_link->name = "MAI"; 12918c2ecf20Sopenharmony_ci dai_link->stream_name = "MAI PCM"; 12928c2ecf20Sopenharmony_ci dai_link->codecs->dai_name = vc4_hdmi_audio_codec_dai_drv.name; 12938c2ecf20Sopenharmony_ci dai_link->cpus->dai_name = dev_name(dev); 12948c2ecf20Sopenharmony_ci dai_link->codecs->name = dev_name(dev); 12958c2ecf20Sopenharmony_ci dai_link->platforms->name = dev_name(dev); 12968c2ecf20Sopenharmony_ci 12978c2ecf20Sopenharmony_ci card->dai_link = dai_link; 12988c2ecf20Sopenharmony_ci card->num_links = 1; 12998c2ecf20Sopenharmony_ci card->name = vc4_hdmi->variant->card_name; 13008c2ecf20Sopenharmony_ci card->driver_name = "vc4-hdmi"; 13018c2ecf20Sopenharmony_ci card->dev = dev; 13028c2ecf20Sopenharmony_ci card->owner = THIS_MODULE; 13038c2ecf20Sopenharmony_ci 13048c2ecf20Sopenharmony_ci /* 13058c2ecf20Sopenharmony_ci * Be careful, snd_soc_register_card() calls dev_set_drvdata() and 13068c2ecf20Sopenharmony_ci * stores a pointer to the snd card object in dev->driver_data. This 13078c2ecf20Sopenharmony_ci * means we cannot use it for something else. The hdmi back-pointer is 13088c2ecf20Sopenharmony_ci * now stored in card->drvdata and should be retrieved with 13098c2ecf20Sopenharmony_ci * snd_soc_card_get_drvdata() if needed. 13108c2ecf20Sopenharmony_ci */ 13118c2ecf20Sopenharmony_ci snd_soc_card_set_drvdata(card, vc4_hdmi); 13128c2ecf20Sopenharmony_ci ret = devm_snd_soc_register_card(dev, card); 13138c2ecf20Sopenharmony_ci if (ret) 13148c2ecf20Sopenharmony_ci dev_err(dev, "Could not register sound card: %d\n", ret); 13158c2ecf20Sopenharmony_ci 13168c2ecf20Sopenharmony_ci return ret; 13178c2ecf20Sopenharmony_ci 13188c2ecf20Sopenharmony_ci} 13198c2ecf20Sopenharmony_ci 13208c2ecf20Sopenharmony_ci#ifdef CONFIG_DRM_VC4_HDMI_CEC 13218c2ecf20Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler_thread(int irq, void *priv) 13228c2ecf20Sopenharmony_ci{ 13238c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = priv; 13248c2ecf20Sopenharmony_ci 13258c2ecf20Sopenharmony_ci if (vc4_hdmi->cec_irq_was_rx) { 13268c2ecf20Sopenharmony_ci if (vc4_hdmi->cec_rx_msg.len) 13278c2ecf20Sopenharmony_ci cec_received_msg(vc4_hdmi->cec_adap, 13288c2ecf20Sopenharmony_ci &vc4_hdmi->cec_rx_msg); 13298c2ecf20Sopenharmony_ci } else if (vc4_hdmi->cec_tx_ok) { 13308c2ecf20Sopenharmony_ci cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_OK, 13318c2ecf20Sopenharmony_ci 0, 0, 0, 0); 13328c2ecf20Sopenharmony_ci } else { 13338c2ecf20Sopenharmony_ci /* 13348c2ecf20Sopenharmony_ci * This CEC implementation makes 1 retry, so if we 13358c2ecf20Sopenharmony_ci * get a NACK, then that means it made 2 attempts. 13368c2ecf20Sopenharmony_ci */ 13378c2ecf20Sopenharmony_ci cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_NACK, 13388c2ecf20Sopenharmony_ci 0, 2, 0, 0); 13398c2ecf20Sopenharmony_ci } 13408c2ecf20Sopenharmony_ci return IRQ_HANDLED; 13418c2ecf20Sopenharmony_ci} 13428c2ecf20Sopenharmony_ci 13438c2ecf20Sopenharmony_cistatic void vc4_cec_read_msg(struct vc4_hdmi *vc4_hdmi, u32 cntrl1) 13448c2ecf20Sopenharmony_ci{ 13458c2ecf20Sopenharmony_ci struct drm_device *dev = vc4_hdmi->connector.dev; 13468c2ecf20Sopenharmony_ci struct cec_msg *msg = &vc4_hdmi->cec_rx_msg; 13478c2ecf20Sopenharmony_ci unsigned int i; 13488c2ecf20Sopenharmony_ci 13498c2ecf20Sopenharmony_ci msg->len = 1 + ((cntrl1 & VC4_HDMI_CEC_REC_WRD_CNT_MASK) >> 13508c2ecf20Sopenharmony_ci VC4_HDMI_CEC_REC_WRD_CNT_SHIFT); 13518c2ecf20Sopenharmony_ci 13528c2ecf20Sopenharmony_ci if (msg->len > 16) { 13538c2ecf20Sopenharmony_ci drm_err(dev, "Attempting to read too much data (%d)\n", msg->len); 13548c2ecf20Sopenharmony_ci return; 13558c2ecf20Sopenharmony_ci } 13568c2ecf20Sopenharmony_ci 13578c2ecf20Sopenharmony_ci for (i = 0; i < msg->len; i += 4) { 13588c2ecf20Sopenharmony_ci u32 val = HDMI_READ(HDMI_CEC_RX_DATA_1 + (i >> 2)); 13598c2ecf20Sopenharmony_ci 13608c2ecf20Sopenharmony_ci msg->msg[i] = val & 0xff; 13618c2ecf20Sopenharmony_ci msg->msg[i + 1] = (val >> 8) & 0xff; 13628c2ecf20Sopenharmony_ci msg->msg[i + 2] = (val >> 16) & 0xff; 13638c2ecf20Sopenharmony_ci msg->msg[i + 3] = (val >> 24) & 0xff; 13648c2ecf20Sopenharmony_ci } 13658c2ecf20Sopenharmony_ci} 13668c2ecf20Sopenharmony_ci 13678c2ecf20Sopenharmony_cistatic irqreturn_t vc4_cec_irq_handler(int irq, void *priv) 13688c2ecf20Sopenharmony_ci{ 13698c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = priv; 13708c2ecf20Sopenharmony_ci u32 stat = HDMI_READ(HDMI_CEC_CPU_STATUS); 13718c2ecf20Sopenharmony_ci u32 cntrl1, cntrl5; 13728c2ecf20Sopenharmony_ci 13738c2ecf20Sopenharmony_ci if (!(stat & VC4_HDMI_CPU_CEC)) 13748c2ecf20Sopenharmony_ci return IRQ_NONE; 13758c2ecf20Sopenharmony_ci vc4_hdmi->cec_rx_msg.len = 0; 13768c2ecf20Sopenharmony_ci cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1); 13778c2ecf20Sopenharmony_ci cntrl5 = HDMI_READ(HDMI_CEC_CNTRL_5); 13788c2ecf20Sopenharmony_ci vc4_hdmi->cec_irq_was_rx = cntrl5 & VC4_HDMI_CEC_RX_CEC_INT; 13798c2ecf20Sopenharmony_ci if (vc4_hdmi->cec_irq_was_rx) { 13808c2ecf20Sopenharmony_ci vc4_cec_read_msg(vc4_hdmi, cntrl1); 13818c2ecf20Sopenharmony_ci cntrl1 |= VC4_HDMI_CEC_CLEAR_RECEIVE_OFF; 13828c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_1, cntrl1); 13838c2ecf20Sopenharmony_ci cntrl1 &= ~VC4_HDMI_CEC_CLEAR_RECEIVE_OFF; 13848c2ecf20Sopenharmony_ci } else { 13858c2ecf20Sopenharmony_ci vc4_hdmi->cec_tx_ok = cntrl1 & VC4_HDMI_CEC_TX_STATUS_GOOD; 13868c2ecf20Sopenharmony_ci cntrl1 &= ~VC4_HDMI_CEC_START_XMIT_BEGIN; 13878c2ecf20Sopenharmony_ci } 13888c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_1, cntrl1); 13898c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CPU_CLEAR, VC4_HDMI_CPU_CEC); 13908c2ecf20Sopenharmony_ci 13918c2ecf20Sopenharmony_ci return IRQ_WAKE_THREAD; 13928c2ecf20Sopenharmony_ci} 13938c2ecf20Sopenharmony_ci 13948c2ecf20Sopenharmony_cistatic int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) 13958c2ecf20Sopenharmony_ci{ 13968c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); 13978c2ecf20Sopenharmony_ci /* clock period in microseconds */ 13988c2ecf20Sopenharmony_ci const u32 usecs = 1000000 / CEC_CLOCK_FREQ; 13998c2ecf20Sopenharmony_ci u32 val = HDMI_READ(HDMI_CEC_CNTRL_5); 14008c2ecf20Sopenharmony_ci 14018c2ecf20Sopenharmony_ci val &= ~(VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET | 14028c2ecf20Sopenharmony_ci VC4_HDMI_CEC_CNT_TO_4700_US_MASK | 14038c2ecf20Sopenharmony_ci VC4_HDMI_CEC_CNT_TO_4500_US_MASK); 14048c2ecf20Sopenharmony_ci val |= ((4700 / usecs) << VC4_HDMI_CEC_CNT_TO_4700_US_SHIFT) | 14058c2ecf20Sopenharmony_ci ((4500 / usecs) << VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT); 14068c2ecf20Sopenharmony_ci 14078c2ecf20Sopenharmony_ci if (enable) { 14088c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_5, val | 14098c2ecf20Sopenharmony_ci VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET); 14108c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_5, val); 14118c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_2, 14128c2ecf20Sopenharmony_ci ((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) | 14138c2ecf20Sopenharmony_ci ((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) | 14148c2ecf20Sopenharmony_ci ((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) | 14158c2ecf20Sopenharmony_ci ((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) | 14168c2ecf20Sopenharmony_ci ((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT)); 14178c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_3, 14188c2ecf20Sopenharmony_ci ((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) | 14198c2ecf20Sopenharmony_ci ((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) | 14208c2ecf20Sopenharmony_ci ((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) | 14218c2ecf20Sopenharmony_ci ((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT)); 14228c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_4, 14238c2ecf20Sopenharmony_ci ((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) | 14248c2ecf20Sopenharmony_ci ((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) | 14258c2ecf20Sopenharmony_ci ((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) | 14268c2ecf20Sopenharmony_ci ((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT)); 14278c2ecf20Sopenharmony_ci 14288c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC); 14298c2ecf20Sopenharmony_ci } else { 14308c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, VC4_HDMI_CPU_CEC); 14318c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_5, val | 14328c2ecf20Sopenharmony_ci VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET); 14338c2ecf20Sopenharmony_ci } 14348c2ecf20Sopenharmony_ci return 0; 14358c2ecf20Sopenharmony_ci} 14368c2ecf20Sopenharmony_ci 14378c2ecf20Sopenharmony_cistatic int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) 14388c2ecf20Sopenharmony_ci{ 14398c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); 14408c2ecf20Sopenharmony_ci 14418c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_1, 14428c2ecf20Sopenharmony_ci (HDMI_READ(HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) | 14438c2ecf20Sopenharmony_ci (log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT); 14448c2ecf20Sopenharmony_ci return 0; 14458c2ecf20Sopenharmony_ci} 14468c2ecf20Sopenharmony_ci 14478c2ecf20Sopenharmony_cistatic int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, 14488c2ecf20Sopenharmony_ci u32 signal_free_time, struct cec_msg *msg) 14498c2ecf20Sopenharmony_ci{ 14508c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); 14518c2ecf20Sopenharmony_ci struct drm_device *dev = vc4_hdmi->connector.dev; 14528c2ecf20Sopenharmony_ci u32 val; 14538c2ecf20Sopenharmony_ci unsigned int i; 14548c2ecf20Sopenharmony_ci 14558c2ecf20Sopenharmony_ci if (msg->len > 16) { 14568c2ecf20Sopenharmony_ci drm_err(dev, "Attempting to transmit too much data (%d)\n", msg->len); 14578c2ecf20Sopenharmony_ci return -ENOMEM; 14588c2ecf20Sopenharmony_ci } 14598c2ecf20Sopenharmony_ci 14608c2ecf20Sopenharmony_ci for (i = 0; i < msg->len; i += 4) 14618c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_TX_DATA_1 + (i >> 2), 14628c2ecf20Sopenharmony_ci (msg->msg[i]) | 14638c2ecf20Sopenharmony_ci (msg->msg[i + 1] << 8) | 14648c2ecf20Sopenharmony_ci (msg->msg[i + 2] << 16) | 14658c2ecf20Sopenharmony_ci (msg->msg[i + 3] << 24)); 14668c2ecf20Sopenharmony_ci 14678c2ecf20Sopenharmony_ci val = HDMI_READ(HDMI_CEC_CNTRL_1); 14688c2ecf20Sopenharmony_ci val &= ~VC4_HDMI_CEC_START_XMIT_BEGIN; 14698c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_1, val); 14708c2ecf20Sopenharmony_ci val &= ~VC4_HDMI_CEC_MESSAGE_LENGTH_MASK; 14718c2ecf20Sopenharmony_ci val |= (msg->len - 1) << VC4_HDMI_CEC_MESSAGE_LENGTH_SHIFT; 14728c2ecf20Sopenharmony_ci val |= VC4_HDMI_CEC_START_XMIT_BEGIN; 14738c2ecf20Sopenharmony_ci 14748c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_1, val); 14758c2ecf20Sopenharmony_ci return 0; 14768c2ecf20Sopenharmony_ci} 14778c2ecf20Sopenharmony_ci 14788c2ecf20Sopenharmony_cistatic const struct cec_adap_ops vc4_hdmi_cec_adap_ops = { 14798c2ecf20Sopenharmony_ci .adap_enable = vc4_hdmi_cec_adap_enable, 14808c2ecf20Sopenharmony_ci .adap_log_addr = vc4_hdmi_cec_adap_log_addr, 14818c2ecf20Sopenharmony_ci .adap_transmit = vc4_hdmi_cec_adap_transmit, 14828c2ecf20Sopenharmony_ci}; 14838c2ecf20Sopenharmony_ci 14848c2ecf20Sopenharmony_cistatic int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) 14858c2ecf20Sopenharmony_ci{ 14868c2ecf20Sopenharmony_ci struct cec_connector_info conn_info; 14878c2ecf20Sopenharmony_ci struct platform_device *pdev = vc4_hdmi->pdev; 14888c2ecf20Sopenharmony_ci u32 value; 14898c2ecf20Sopenharmony_ci int ret; 14908c2ecf20Sopenharmony_ci 14918c2ecf20Sopenharmony_ci if (!vc4_hdmi->variant->cec_available) 14928c2ecf20Sopenharmony_ci return 0; 14938c2ecf20Sopenharmony_ci 14948c2ecf20Sopenharmony_ci vc4_hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops, 14958c2ecf20Sopenharmony_ci vc4_hdmi, 14968c2ecf20Sopenharmony_ci vc4_hdmi->variant->card_name, 14978c2ecf20Sopenharmony_ci CEC_CAP_DEFAULTS | 14988c2ecf20Sopenharmony_ci CEC_CAP_CONNECTOR_INFO, 1); 14998c2ecf20Sopenharmony_ci ret = PTR_ERR_OR_ZERO(vc4_hdmi->cec_adap); 15008c2ecf20Sopenharmony_ci if (ret < 0) 15018c2ecf20Sopenharmony_ci return ret; 15028c2ecf20Sopenharmony_ci 15038c2ecf20Sopenharmony_ci cec_fill_conn_info_from_drm(&conn_info, &vc4_hdmi->connector); 15048c2ecf20Sopenharmony_ci cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info); 15058c2ecf20Sopenharmony_ci 15068c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff); 15078c2ecf20Sopenharmony_ci 15088c2ecf20Sopenharmony_ci value = HDMI_READ(HDMI_CEC_CNTRL_1); 15098c2ecf20Sopenharmony_ci /* Set the logical address to Unregistered */ 15108c2ecf20Sopenharmony_ci value |= VC4_HDMI_CEC_ADDR_MASK; 15118c2ecf20Sopenharmony_ci HDMI_WRITE(HDMI_CEC_CNTRL_1, value); 15128c2ecf20Sopenharmony_ci 15138c2ecf20Sopenharmony_ci vc4_hdmi_cec_update_clk_div(vc4_hdmi); 15148c2ecf20Sopenharmony_ci 15158c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, platform_get_irq(pdev, 0), 15168c2ecf20Sopenharmony_ci vc4_cec_irq_handler, 15178c2ecf20Sopenharmony_ci vc4_cec_irq_handler_thread, 0, 15188c2ecf20Sopenharmony_ci "vc4 hdmi cec", vc4_hdmi); 15198c2ecf20Sopenharmony_ci if (ret) 15208c2ecf20Sopenharmony_ci goto err_delete_cec_adap; 15218c2ecf20Sopenharmony_ci 15228c2ecf20Sopenharmony_ci ret = cec_register_adapter(vc4_hdmi->cec_adap, &pdev->dev); 15238c2ecf20Sopenharmony_ci if (ret < 0) 15248c2ecf20Sopenharmony_ci goto err_delete_cec_adap; 15258c2ecf20Sopenharmony_ci 15268c2ecf20Sopenharmony_ci return 0; 15278c2ecf20Sopenharmony_ci 15288c2ecf20Sopenharmony_cierr_delete_cec_adap: 15298c2ecf20Sopenharmony_ci cec_delete_adapter(vc4_hdmi->cec_adap); 15308c2ecf20Sopenharmony_ci 15318c2ecf20Sopenharmony_ci return ret; 15328c2ecf20Sopenharmony_ci} 15338c2ecf20Sopenharmony_ci 15348c2ecf20Sopenharmony_cistatic void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) 15358c2ecf20Sopenharmony_ci{ 15368c2ecf20Sopenharmony_ci cec_unregister_adapter(vc4_hdmi->cec_adap); 15378c2ecf20Sopenharmony_ci} 15388c2ecf20Sopenharmony_ci#else 15398c2ecf20Sopenharmony_cistatic int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) 15408c2ecf20Sopenharmony_ci{ 15418c2ecf20Sopenharmony_ci return 0; 15428c2ecf20Sopenharmony_ci} 15438c2ecf20Sopenharmony_ci 15448c2ecf20Sopenharmony_cistatic void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) {}; 15458c2ecf20Sopenharmony_ci 15468c2ecf20Sopenharmony_ci#endif 15478c2ecf20Sopenharmony_ci 15488c2ecf20Sopenharmony_cistatic int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, 15498c2ecf20Sopenharmony_ci struct debugfs_regset32 *regset, 15508c2ecf20Sopenharmony_ci enum vc4_hdmi_regs reg) 15518c2ecf20Sopenharmony_ci{ 15528c2ecf20Sopenharmony_ci const struct vc4_hdmi_variant *variant = vc4_hdmi->variant; 15538c2ecf20Sopenharmony_ci struct debugfs_reg32 *regs, *new_regs; 15548c2ecf20Sopenharmony_ci unsigned int count = 0; 15558c2ecf20Sopenharmony_ci unsigned int i; 15568c2ecf20Sopenharmony_ci 15578c2ecf20Sopenharmony_ci regs = kcalloc(variant->num_registers, sizeof(*regs), 15588c2ecf20Sopenharmony_ci GFP_KERNEL); 15598c2ecf20Sopenharmony_ci if (!regs) 15608c2ecf20Sopenharmony_ci return -ENOMEM; 15618c2ecf20Sopenharmony_ci 15628c2ecf20Sopenharmony_ci for (i = 0; i < variant->num_registers; i++) { 15638c2ecf20Sopenharmony_ci const struct vc4_hdmi_register *field = &variant->registers[i]; 15648c2ecf20Sopenharmony_ci 15658c2ecf20Sopenharmony_ci if (field->reg != reg) 15668c2ecf20Sopenharmony_ci continue; 15678c2ecf20Sopenharmony_ci 15688c2ecf20Sopenharmony_ci regs[count].name = field->name; 15698c2ecf20Sopenharmony_ci regs[count].offset = field->offset; 15708c2ecf20Sopenharmony_ci count++; 15718c2ecf20Sopenharmony_ci } 15728c2ecf20Sopenharmony_ci 15738c2ecf20Sopenharmony_ci new_regs = krealloc(regs, count * sizeof(*regs), GFP_KERNEL); 15748c2ecf20Sopenharmony_ci if (!new_regs) 15758c2ecf20Sopenharmony_ci return -ENOMEM; 15768c2ecf20Sopenharmony_ci 15778c2ecf20Sopenharmony_ci regset->base = __vc4_hdmi_get_field_base(vc4_hdmi, reg); 15788c2ecf20Sopenharmony_ci regset->regs = new_regs; 15798c2ecf20Sopenharmony_ci regset->nregs = count; 15808c2ecf20Sopenharmony_ci 15818c2ecf20Sopenharmony_ci return 0; 15828c2ecf20Sopenharmony_ci} 15838c2ecf20Sopenharmony_ci 15848c2ecf20Sopenharmony_cistatic int vc4_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) 15858c2ecf20Sopenharmony_ci{ 15868c2ecf20Sopenharmony_ci struct platform_device *pdev = vc4_hdmi->pdev; 15878c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 15888c2ecf20Sopenharmony_ci int ret; 15898c2ecf20Sopenharmony_ci 15908c2ecf20Sopenharmony_ci vc4_hdmi->hdmicore_regs = vc4_ioremap_regs(pdev, 0); 15918c2ecf20Sopenharmony_ci if (IS_ERR(vc4_hdmi->hdmicore_regs)) 15928c2ecf20Sopenharmony_ci return PTR_ERR(vc4_hdmi->hdmicore_regs); 15938c2ecf20Sopenharmony_ci 15948c2ecf20Sopenharmony_ci vc4_hdmi->hd_regs = vc4_ioremap_regs(pdev, 1); 15958c2ecf20Sopenharmony_ci if (IS_ERR(vc4_hdmi->hd_regs)) 15968c2ecf20Sopenharmony_ci return PTR_ERR(vc4_hdmi->hd_regs); 15978c2ecf20Sopenharmony_ci 15988c2ecf20Sopenharmony_ci ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); 15998c2ecf20Sopenharmony_ci if (ret) 16008c2ecf20Sopenharmony_ci return ret; 16018c2ecf20Sopenharmony_ci 16028c2ecf20Sopenharmony_ci ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); 16038c2ecf20Sopenharmony_ci if (ret) 16048c2ecf20Sopenharmony_ci return ret; 16058c2ecf20Sopenharmony_ci 16068c2ecf20Sopenharmony_ci vc4_hdmi->pixel_clock = devm_clk_get(dev, "pixel"); 16078c2ecf20Sopenharmony_ci if (IS_ERR(vc4_hdmi->pixel_clock)) { 16088c2ecf20Sopenharmony_ci ret = PTR_ERR(vc4_hdmi->pixel_clock); 16098c2ecf20Sopenharmony_ci if (ret != -EPROBE_DEFER) 16108c2ecf20Sopenharmony_ci DRM_ERROR("Failed to get pixel clock\n"); 16118c2ecf20Sopenharmony_ci return ret; 16128c2ecf20Sopenharmony_ci } 16138c2ecf20Sopenharmony_ci 16148c2ecf20Sopenharmony_ci vc4_hdmi->hsm_clock = devm_clk_get(dev, "hdmi"); 16158c2ecf20Sopenharmony_ci if (IS_ERR(vc4_hdmi->hsm_clock)) { 16168c2ecf20Sopenharmony_ci DRM_ERROR("Failed to get HDMI state machine clock\n"); 16178c2ecf20Sopenharmony_ci return PTR_ERR(vc4_hdmi->hsm_clock); 16188c2ecf20Sopenharmony_ci } 16198c2ecf20Sopenharmony_ci vc4_hdmi->audio_clock = vc4_hdmi->hsm_clock; 16208c2ecf20Sopenharmony_ci 16218c2ecf20Sopenharmony_ci return 0; 16228c2ecf20Sopenharmony_ci} 16238c2ecf20Sopenharmony_ci 16248c2ecf20Sopenharmony_cistatic int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) 16258c2ecf20Sopenharmony_ci{ 16268c2ecf20Sopenharmony_ci struct platform_device *pdev = vc4_hdmi->pdev; 16278c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 16288c2ecf20Sopenharmony_ci struct resource *res; 16298c2ecf20Sopenharmony_ci 16308c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi"); 16318c2ecf20Sopenharmony_ci if (!res) 16328c2ecf20Sopenharmony_ci return -ENODEV; 16338c2ecf20Sopenharmony_ci 16348c2ecf20Sopenharmony_ci vc4_hdmi->hdmicore_regs = devm_ioremap(dev, res->start, 16358c2ecf20Sopenharmony_ci resource_size(res)); 16368c2ecf20Sopenharmony_ci if (!vc4_hdmi->hdmicore_regs) 16378c2ecf20Sopenharmony_ci return -ENOMEM; 16388c2ecf20Sopenharmony_ci 16398c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hd"); 16408c2ecf20Sopenharmony_ci if (!res) 16418c2ecf20Sopenharmony_ci return -ENODEV; 16428c2ecf20Sopenharmony_ci 16438c2ecf20Sopenharmony_ci vc4_hdmi->hd_regs = devm_ioremap(dev, res->start, resource_size(res)); 16448c2ecf20Sopenharmony_ci if (!vc4_hdmi->hd_regs) 16458c2ecf20Sopenharmony_ci return -ENOMEM; 16468c2ecf20Sopenharmony_ci 16478c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cec"); 16488c2ecf20Sopenharmony_ci if (!res) 16498c2ecf20Sopenharmony_ci return -ENODEV; 16508c2ecf20Sopenharmony_ci 16518c2ecf20Sopenharmony_ci vc4_hdmi->cec_regs = devm_ioremap(dev, res->start, resource_size(res)); 16528c2ecf20Sopenharmony_ci if (!vc4_hdmi->cec_regs) 16538c2ecf20Sopenharmony_ci return -ENOMEM; 16548c2ecf20Sopenharmony_ci 16558c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csc"); 16568c2ecf20Sopenharmony_ci if (!res) 16578c2ecf20Sopenharmony_ci return -ENODEV; 16588c2ecf20Sopenharmony_ci 16598c2ecf20Sopenharmony_ci vc4_hdmi->csc_regs = devm_ioremap(dev, res->start, resource_size(res)); 16608c2ecf20Sopenharmony_ci if (!vc4_hdmi->csc_regs) 16618c2ecf20Sopenharmony_ci return -ENOMEM; 16628c2ecf20Sopenharmony_ci 16638c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dvp"); 16648c2ecf20Sopenharmony_ci if (!res) 16658c2ecf20Sopenharmony_ci return -ENODEV; 16668c2ecf20Sopenharmony_ci 16678c2ecf20Sopenharmony_ci vc4_hdmi->dvp_regs = devm_ioremap(dev, res->start, resource_size(res)); 16688c2ecf20Sopenharmony_ci if (!vc4_hdmi->dvp_regs) 16698c2ecf20Sopenharmony_ci return -ENOMEM; 16708c2ecf20Sopenharmony_ci 16718c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); 16728c2ecf20Sopenharmony_ci if (!res) 16738c2ecf20Sopenharmony_ci return -ENODEV; 16748c2ecf20Sopenharmony_ci 16758c2ecf20Sopenharmony_ci vc4_hdmi->phy_regs = devm_ioremap(dev, res->start, resource_size(res)); 16768c2ecf20Sopenharmony_ci if (!vc4_hdmi->phy_regs) 16778c2ecf20Sopenharmony_ci return -ENOMEM; 16788c2ecf20Sopenharmony_ci 16798c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "packet"); 16808c2ecf20Sopenharmony_ci if (!res) 16818c2ecf20Sopenharmony_ci return -ENODEV; 16828c2ecf20Sopenharmony_ci 16838c2ecf20Sopenharmony_ci vc4_hdmi->ram_regs = devm_ioremap(dev, res->start, resource_size(res)); 16848c2ecf20Sopenharmony_ci if (!vc4_hdmi->ram_regs) 16858c2ecf20Sopenharmony_ci return -ENOMEM; 16868c2ecf20Sopenharmony_ci 16878c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rm"); 16888c2ecf20Sopenharmony_ci if (!res) 16898c2ecf20Sopenharmony_ci return -ENODEV; 16908c2ecf20Sopenharmony_ci 16918c2ecf20Sopenharmony_ci vc4_hdmi->rm_regs = devm_ioremap(dev, res->start, resource_size(res)); 16928c2ecf20Sopenharmony_ci if (!vc4_hdmi->rm_regs) 16938c2ecf20Sopenharmony_ci return -ENOMEM; 16948c2ecf20Sopenharmony_ci 16958c2ecf20Sopenharmony_ci vc4_hdmi->hsm_clock = devm_clk_get(dev, "hdmi"); 16968c2ecf20Sopenharmony_ci if (IS_ERR(vc4_hdmi->hsm_clock)) { 16978c2ecf20Sopenharmony_ci DRM_ERROR("Failed to get HDMI state machine clock\n"); 16988c2ecf20Sopenharmony_ci return PTR_ERR(vc4_hdmi->hsm_clock); 16998c2ecf20Sopenharmony_ci } 17008c2ecf20Sopenharmony_ci 17018c2ecf20Sopenharmony_ci vc4_hdmi->pixel_bvb_clock = devm_clk_get(dev, "bvb"); 17028c2ecf20Sopenharmony_ci if (IS_ERR(vc4_hdmi->pixel_bvb_clock)) { 17038c2ecf20Sopenharmony_ci DRM_ERROR("Failed to get pixel bvb clock\n"); 17048c2ecf20Sopenharmony_ci return PTR_ERR(vc4_hdmi->pixel_bvb_clock); 17058c2ecf20Sopenharmony_ci } 17068c2ecf20Sopenharmony_ci 17078c2ecf20Sopenharmony_ci vc4_hdmi->audio_clock = devm_clk_get(dev, "audio"); 17088c2ecf20Sopenharmony_ci if (IS_ERR(vc4_hdmi->audio_clock)) { 17098c2ecf20Sopenharmony_ci DRM_ERROR("Failed to get audio clock\n"); 17108c2ecf20Sopenharmony_ci return PTR_ERR(vc4_hdmi->audio_clock); 17118c2ecf20Sopenharmony_ci } 17128c2ecf20Sopenharmony_ci 17138c2ecf20Sopenharmony_ci vc4_hdmi->reset = devm_reset_control_get(dev, NULL); 17148c2ecf20Sopenharmony_ci if (IS_ERR(vc4_hdmi->reset)) { 17158c2ecf20Sopenharmony_ci DRM_ERROR("Failed to get HDMI reset line\n"); 17168c2ecf20Sopenharmony_ci return PTR_ERR(vc4_hdmi->reset); 17178c2ecf20Sopenharmony_ci } 17188c2ecf20Sopenharmony_ci 17198c2ecf20Sopenharmony_ci return 0; 17208c2ecf20Sopenharmony_ci} 17218c2ecf20Sopenharmony_ci 17228c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 17238c2ecf20Sopenharmony_cistatic int vc4_hdmi_runtime_suspend(struct device *dev) 17248c2ecf20Sopenharmony_ci{ 17258c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); 17268c2ecf20Sopenharmony_ci 17278c2ecf20Sopenharmony_ci clk_disable_unprepare(vc4_hdmi->hsm_clock); 17288c2ecf20Sopenharmony_ci 17298c2ecf20Sopenharmony_ci return 0; 17308c2ecf20Sopenharmony_ci} 17318c2ecf20Sopenharmony_ci 17328c2ecf20Sopenharmony_cistatic int vc4_hdmi_runtime_resume(struct device *dev) 17338c2ecf20Sopenharmony_ci{ 17348c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); 17358c2ecf20Sopenharmony_ci int ret; 17368c2ecf20Sopenharmony_ci 17378c2ecf20Sopenharmony_ci ret = clk_prepare_enable(vc4_hdmi->hsm_clock); 17388c2ecf20Sopenharmony_ci if (ret) 17398c2ecf20Sopenharmony_ci return ret; 17408c2ecf20Sopenharmony_ci 17418c2ecf20Sopenharmony_ci return 0; 17428c2ecf20Sopenharmony_ci} 17438c2ecf20Sopenharmony_ci#endif 17448c2ecf20Sopenharmony_ci 17458c2ecf20Sopenharmony_cistatic int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) 17468c2ecf20Sopenharmony_ci{ 17478c2ecf20Sopenharmony_ci const struct vc4_hdmi_variant *variant = of_device_get_match_data(dev); 17488c2ecf20Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 17498c2ecf20Sopenharmony_ci struct drm_device *drm = dev_get_drvdata(master); 17508c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi; 17518c2ecf20Sopenharmony_ci struct drm_encoder *encoder; 17528c2ecf20Sopenharmony_ci struct device_node *ddc_node; 17538c2ecf20Sopenharmony_ci u32 value; 17548c2ecf20Sopenharmony_ci int ret; 17558c2ecf20Sopenharmony_ci 17568c2ecf20Sopenharmony_ci vc4_hdmi = devm_kzalloc(dev, sizeof(*vc4_hdmi), GFP_KERNEL); 17578c2ecf20Sopenharmony_ci if (!vc4_hdmi) 17588c2ecf20Sopenharmony_ci return -ENOMEM; 17598c2ecf20Sopenharmony_ci 17608c2ecf20Sopenharmony_ci dev_set_drvdata(dev, vc4_hdmi); 17618c2ecf20Sopenharmony_ci encoder = &vc4_hdmi->encoder.base.base; 17628c2ecf20Sopenharmony_ci vc4_hdmi->encoder.base.type = variant->encoder_type; 17638c2ecf20Sopenharmony_ci vc4_hdmi->encoder.base.pre_crtc_configure = vc4_hdmi_encoder_pre_crtc_configure; 17648c2ecf20Sopenharmony_ci vc4_hdmi->encoder.base.pre_crtc_enable = vc4_hdmi_encoder_pre_crtc_enable; 17658c2ecf20Sopenharmony_ci vc4_hdmi->encoder.base.post_crtc_enable = vc4_hdmi_encoder_post_crtc_enable; 17668c2ecf20Sopenharmony_ci vc4_hdmi->encoder.base.post_crtc_disable = vc4_hdmi_encoder_post_crtc_disable; 17678c2ecf20Sopenharmony_ci vc4_hdmi->encoder.base.post_crtc_powerdown = vc4_hdmi_encoder_post_crtc_powerdown; 17688c2ecf20Sopenharmony_ci vc4_hdmi->pdev = pdev; 17698c2ecf20Sopenharmony_ci vc4_hdmi->variant = variant; 17708c2ecf20Sopenharmony_ci 17718c2ecf20Sopenharmony_ci ret = variant->init_resources(vc4_hdmi); 17728c2ecf20Sopenharmony_ci if (ret) 17738c2ecf20Sopenharmony_ci return ret; 17748c2ecf20Sopenharmony_ci 17758c2ecf20Sopenharmony_ci ddc_node = of_parse_phandle(dev->of_node, "ddc", 0); 17768c2ecf20Sopenharmony_ci if (!ddc_node) { 17778c2ecf20Sopenharmony_ci DRM_ERROR("Failed to find ddc node in device tree\n"); 17788c2ecf20Sopenharmony_ci return -ENODEV; 17798c2ecf20Sopenharmony_ci } 17808c2ecf20Sopenharmony_ci 17818c2ecf20Sopenharmony_ci vc4_hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); 17828c2ecf20Sopenharmony_ci of_node_put(ddc_node); 17838c2ecf20Sopenharmony_ci if (!vc4_hdmi->ddc) { 17848c2ecf20Sopenharmony_ci DRM_DEBUG("Failed to get ddc i2c adapter by node\n"); 17858c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 17868c2ecf20Sopenharmony_ci } 17878c2ecf20Sopenharmony_ci 17888c2ecf20Sopenharmony_ci /* Only use the GPIO HPD pin if present in the DT, otherwise 17898c2ecf20Sopenharmony_ci * we'll use the HDMI core's register. 17908c2ecf20Sopenharmony_ci */ 17918c2ecf20Sopenharmony_ci if (of_find_property(dev->of_node, "hpd-gpios", &value)) { 17928c2ecf20Sopenharmony_ci enum of_gpio_flags hpd_gpio_flags; 17938c2ecf20Sopenharmony_ci 17948c2ecf20Sopenharmony_ci vc4_hdmi->hpd_gpio = of_get_named_gpio_flags(dev->of_node, 17958c2ecf20Sopenharmony_ci "hpd-gpios", 0, 17968c2ecf20Sopenharmony_ci &hpd_gpio_flags); 17978c2ecf20Sopenharmony_ci if (vc4_hdmi->hpd_gpio < 0) { 17988c2ecf20Sopenharmony_ci ret = vc4_hdmi->hpd_gpio; 17998c2ecf20Sopenharmony_ci goto err_put_ddc; 18008c2ecf20Sopenharmony_ci } 18018c2ecf20Sopenharmony_ci 18028c2ecf20Sopenharmony_ci vc4_hdmi->hpd_active_low = hpd_gpio_flags & OF_GPIO_ACTIVE_LOW; 18038c2ecf20Sopenharmony_ci } 18048c2ecf20Sopenharmony_ci 18058c2ecf20Sopenharmony_ci vc4_hdmi->disable_wifi_frequencies = 18068c2ecf20Sopenharmony_ci of_property_read_bool(dev->of_node, "wifi-2.4ghz-coexistence"); 18078c2ecf20Sopenharmony_ci 18088c2ecf20Sopenharmony_ci /* 18098c2ecf20Sopenharmony_ci * If we boot without any cable connected to the HDMI connector, 18108c2ecf20Sopenharmony_ci * the firmware will skip the HSM initialization and leave it 18118c2ecf20Sopenharmony_ci * with a rate of 0, resulting in a bus lockup when we're 18128c2ecf20Sopenharmony_ci * accessing the registers even if it's enabled. 18138c2ecf20Sopenharmony_ci * 18148c2ecf20Sopenharmony_ci * Let's put a sensible default at runtime_resume so that we 18158c2ecf20Sopenharmony_ci * don't end up in this situation. 18168c2ecf20Sopenharmony_ci */ 18178c2ecf20Sopenharmony_ci ret = clk_set_min_rate(vc4_hdmi->hsm_clock, HSM_MIN_CLOCK_FREQ); 18188c2ecf20Sopenharmony_ci if (ret) 18198c2ecf20Sopenharmony_ci goto err_put_ddc; 18208c2ecf20Sopenharmony_ci 18218c2ecf20Sopenharmony_ci if (vc4_hdmi->variant->reset) 18228c2ecf20Sopenharmony_ci vc4_hdmi->variant->reset(vc4_hdmi); 18238c2ecf20Sopenharmony_ci 18248c2ecf20Sopenharmony_ci if ((of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi0") || 18258c2ecf20Sopenharmony_ci of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1")) && 18268c2ecf20Sopenharmony_ci HDMI_READ(HDMI_VID_CTL) & VC4_HD_VID_CTL_ENABLE) { 18278c2ecf20Sopenharmony_ci clk_prepare_enable(vc4_hdmi->pixel_clock); 18288c2ecf20Sopenharmony_ci clk_prepare_enable(vc4_hdmi->hsm_clock); 18298c2ecf20Sopenharmony_ci clk_prepare_enable(vc4_hdmi->pixel_bvb_clock); 18308c2ecf20Sopenharmony_ci } 18318c2ecf20Sopenharmony_ci 18328c2ecf20Sopenharmony_ci pm_runtime_enable(dev); 18338c2ecf20Sopenharmony_ci 18348c2ecf20Sopenharmony_ci drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); 18358c2ecf20Sopenharmony_ci drm_encoder_helper_add(encoder, &vc4_hdmi_encoder_helper_funcs); 18368c2ecf20Sopenharmony_ci 18378c2ecf20Sopenharmony_ci ret = vc4_hdmi_connector_init(drm, vc4_hdmi); 18388c2ecf20Sopenharmony_ci if (ret) 18398c2ecf20Sopenharmony_ci goto err_destroy_encoder; 18408c2ecf20Sopenharmony_ci 18418c2ecf20Sopenharmony_ci ret = vc4_hdmi_cec_init(vc4_hdmi); 18428c2ecf20Sopenharmony_ci if (ret) 18438c2ecf20Sopenharmony_ci goto err_destroy_conn; 18448c2ecf20Sopenharmony_ci 18458c2ecf20Sopenharmony_ci ret = vc4_hdmi_audio_init(vc4_hdmi); 18468c2ecf20Sopenharmony_ci if (ret) 18478c2ecf20Sopenharmony_ci goto err_free_cec; 18488c2ecf20Sopenharmony_ci 18498c2ecf20Sopenharmony_ci vc4_debugfs_add_file(drm, variant->debugfs_name, 18508c2ecf20Sopenharmony_ci vc4_hdmi_debugfs_regs, 18518c2ecf20Sopenharmony_ci vc4_hdmi); 18528c2ecf20Sopenharmony_ci 18538c2ecf20Sopenharmony_ci return 0; 18548c2ecf20Sopenharmony_ci 18558c2ecf20Sopenharmony_cierr_free_cec: 18568c2ecf20Sopenharmony_ci vc4_hdmi_cec_exit(vc4_hdmi); 18578c2ecf20Sopenharmony_cierr_destroy_conn: 18588c2ecf20Sopenharmony_ci vc4_hdmi_connector_destroy(&vc4_hdmi->connector); 18598c2ecf20Sopenharmony_cierr_destroy_encoder: 18608c2ecf20Sopenharmony_ci drm_encoder_cleanup(encoder); 18618c2ecf20Sopenharmony_ci pm_runtime_disable(dev); 18628c2ecf20Sopenharmony_cierr_put_ddc: 18638c2ecf20Sopenharmony_ci put_device(&vc4_hdmi->ddc->dev); 18648c2ecf20Sopenharmony_ci 18658c2ecf20Sopenharmony_ci return ret; 18668c2ecf20Sopenharmony_ci} 18678c2ecf20Sopenharmony_ci 18688c2ecf20Sopenharmony_cistatic void vc4_hdmi_unbind(struct device *dev, struct device *master, 18698c2ecf20Sopenharmony_ci void *data) 18708c2ecf20Sopenharmony_ci{ 18718c2ecf20Sopenharmony_ci struct vc4_hdmi *vc4_hdmi; 18728c2ecf20Sopenharmony_ci 18738c2ecf20Sopenharmony_ci /* 18748c2ecf20Sopenharmony_ci * ASoC makes it a bit hard to retrieve a pointer to the 18758c2ecf20Sopenharmony_ci * vc4_hdmi structure. Registering the card will overwrite our 18768c2ecf20Sopenharmony_ci * device drvdata with a pointer to the snd_soc_card structure, 18778c2ecf20Sopenharmony_ci * which can then be used to retrieve whatever drvdata we want 18788c2ecf20Sopenharmony_ci * to associate. 18798c2ecf20Sopenharmony_ci * 18808c2ecf20Sopenharmony_ci * However, that doesn't fly in the case where we wouldn't 18818c2ecf20Sopenharmony_ci * register an ASoC card (because of an old DT that is missing 18828c2ecf20Sopenharmony_ci * the dmas properties for example), then the card isn't 18838c2ecf20Sopenharmony_ci * registered and the device drvdata wouldn't be set. 18848c2ecf20Sopenharmony_ci * 18858c2ecf20Sopenharmony_ci * We can deal with both cases by making sure a snd_soc_card 18868c2ecf20Sopenharmony_ci * pointer and a vc4_hdmi structure are pointing to the same 18878c2ecf20Sopenharmony_ci * memory address, so we can treat them indistinctly without any 18888c2ecf20Sopenharmony_ci * issue. 18898c2ecf20Sopenharmony_ci */ 18908c2ecf20Sopenharmony_ci BUILD_BUG_ON(offsetof(struct vc4_hdmi_audio, card) != 0); 18918c2ecf20Sopenharmony_ci BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0); 18928c2ecf20Sopenharmony_ci vc4_hdmi = dev_get_drvdata(dev); 18938c2ecf20Sopenharmony_ci 18948c2ecf20Sopenharmony_ci kfree(vc4_hdmi->hdmi_regset.regs); 18958c2ecf20Sopenharmony_ci kfree(vc4_hdmi->hd_regset.regs); 18968c2ecf20Sopenharmony_ci 18978c2ecf20Sopenharmony_ci vc4_hdmi_cec_exit(vc4_hdmi); 18988c2ecf20Sopenharmony_ci vc4_hdmi_connector_destroy(&vc4_hdmi->connector); 18998c2ecf20Sopenharmony_ci drm_encoder_cleanup(&vc4_hdmi->encoder.base.base); 19008c2ecf20Sopenharmony_ci 19018c2ecf20Sopenharmony_ci pm_runtime_disable(dev); 19028c2ecf20Sopenharmony_ci 19038c2ecf20Sopenharmony_ci put_device(&vc4_hdmi->ddc->dev); 19048c2ecf20Sopenharmony_ci} 19058c2ecf20Sopenharmony_ci 19068c2ecf20Sopenharmony_cistatic const struct component_ops vc4_hdmi_ops = { 19078c2ecf20Sopenharmony_ci .bind = vc4_hdmi_bind, 19088c2ecf20Sopenharmony_ci .unbind = vc4_hdmi_unbind, 19098c2ecf20Sopenharmony_ci}; 19108c2ecf20Sopenharmony_ci 19118c2ecf20Sopenharmony_cistatic int vc4_hdmi_dev_probe(struct platform_device *pdev) 19128c2ecf20Sopenharmony_ci{ 19138c2ecf20Sopenharmony_ci return component_add(&pdev->dev, &vc4_hdmi_ops); 19148c2ecf20Sopenharmony_ci} 19158c2ecf20Sopenharmony_ci 19168c2ecf20Sopenharmony_cistatic int vc4_hdmi_dev_remove(struct platform_device *pdev) 19178c2ecf20Sopenharmony_ci{ 19188c2ecf20Sopenharmony_ci component_del(&pdev->dev, &vc4_hdmi_ops); 19198c2ecf20Sopenharmony_ci return 0; 19208c2ecf20Sopenharmony_ci} 19218c2ecf20Sopenharmony_ci 19228c2ecf20Sopenharmony_cistatic const struct vc4_hdmi_variant bcm2835_variant = { 19238c2ecf20Sopenharmony_ci .encoder_type = VC4_ENCODER_TYPE_HDMI0, 19248c2ecf20Sopenharmony_ci .debugfs_name = "hdmi_regs", 19258c2ecf20Sopenharmony_ci .card_name = "vc4-hdmi", 19268c2ecf20Sopenharmony_ci .max_pixel_clock = 162000000, 19278c2ecf20Sopenharmony_ci .cec_available = true, 19288c2ecf20Sopenharmony_ci .registers = vc4_hdmi_fields, 19298c2ecf20Sopenharmony_ci .num_registers = ARRAY_SIZE(vc4_hdmi_fields), 19308c2ecf20Sopenharmony_ci 19318c2ecf20Sopenharmony_ci .init_resources = vc4_hdmi_init_resources, 19328c2ecf20Sopenharmony_ci .csc_setup = vc4_hdmi_csc_setup, 19338c2ecf20Sopenharmony_ci .reset = vc4_hdmi_reset, 19348c2ecf20Sopenharmony_ci .set_timings = vc4_hdmi_set_timings, 19358c2ecf20Sopenharmony_ci .phy_init = vc4_hdmi_phy_init, 19368c2ecf20Sopenharmony_ci .phy_disable = vc4_hdmi_phy_disable, 19378c2ecf20Sopenharmony_ci .phy_rng_enable = vc4_hdmi_phy_rng_enable, 19388c2ecf20Sopenharmony_ci .phy_rng_disable = vc4_hdmi_phy_rng_disable, 19398c2ecf20Sopenharmony_ci .channel_map = vc4_hdmi_channel_map, 19408c2ecf20Sopenharmony_ci}; 19418c2ecf20Sopenharmony_ci 19428c2ecf20Sopenharmony_cistatic const struct vc4_hdmi_variant bcm2711_hdmi0_variant = { 19438c2ecf20Sopenharmony_ci .encoder_type = VC4_ENCODER_TYPE_HDMI0, 19448c2ecf20Sopenharmony_ci .debugfs_name = "hdmi0_regs", 19458c2ecf20Sopenharmony_ci .card_name = "vc4-hdmi-0", 19468c2ecf20Sopenharmony_ci .max_pixel_clock = HDMI_14_MAX_TMDS_CLK, 19478c2ecf20Sopenharmony_ci .registers = vc5_hdmi_hdmi0_fields, 19488c2ecf20Sopenharmony_ci .num_registers = ARRAY_SIZE(vc5_hdmi_hdmi0_fields), 19498c2ecf20Sopenharmony_ci .phy_lane_mapping = { 19508c2ecf20Sopenharmony_ci PHY_LANE_0, 19518c2ecf20Sopenharmony_ci PHY_LANE_1, 19528c2ecf20Sopenharmony_ci PHY_LANE_2, 19538c2ecf20Sopenharmony_ci PHY_LANE_CK, 19548c2ecf20Sopenharmony_ci }, 19558c2ecf20Sopenharmony_ci .unsupported_odd_h_timings = true, 19568c2ecf20Sopenharmony_ci 19578c2ecf20Sopenharmony_ci .init_resources = vc5_hdmi_init_resources, 19588c2ecf20Sopenharmony_ci .csc_setup = vc5_hdmi_csc_setup, 19598c2ecf20Sopenharmony_ci .reset = vc5_hdmi_reset, 19608c2ecf20Sopenharmony_ci .set_timings = vc5_hdmi_set_timings, 19618c2ecf20Sopenharmony_ci .phy_init = vc5_hdmi_phy_init, 19628c2ecf20Sopenharmony_ci .phy_disable = vc5_hdmi_phy_disable, 19638c2ecf20Sopenharmony_ci .phy_rng_enable = vc5_hdmi_phy_rng_enable, 19648c2ecf20Sopenharmony_ci .phy_rng_disable = vc5_hdmi_phy_rng_disable, 19658c2ecf20Sopenharmony_ci .channel_map = vc5_hdmi_channel_map, 19668c2ecf20Sopenharmony_ci}; 19678c2ecf20Sopenharmony_ci 19688c2ecf20Sopenharmony_cistatic const struct vc4_hdmi_variant bcm2711_hdmi1_variant = { 19698c2ecf20Sopenharmony_ci .encoder_type = VC4_ENCODER_TYPE_HDMI1, 19708c2ecf20Sopenharmony_ci .debugfs_name = "hdmi1_regs", 19718c2ecf20Sopenharmony_ci .card_name = "vc4-hdmi-1", 19728c2ecf20Sopenharmony_ci .max_pixel_clock = HDMI_14_MAX_TMDS_CLK, 19738c2ecf20Sopenharmony_ci .registers = vc5_hdmi_hdmi1_fields, 19748c2ecf20Sopenharmony_ci .num_registers = ARRAY_SIZE(vc5_hdmi_hdmi1_fields), 19758c2ecf20Sopenharmony_ci .phy_lane_mapping = { 19768c2ecf20Sopenharmony_ci PHY_LANE_1, 19778c2ecf20Sopenharmony_ci PHY_LANE_0, 19788c2ecf20Sopenharmony_ci PHY_LANE_CK, 19798c2ecf20Sopenharmony_ci PHY_LANE_2, 19808c2ecf20Sopenharmony_ci }, 19818c2ecf20Sopenharmony_ci .unsupported_odd_h_timings = true, 19828c2ecf20Sopenharmony_ci 19838c2ecf20Sopenharmony_ci .init_resources = vc5_hdmi_init_resources, 19848c2ecf20Sopenharmony_ci .csc_setup = vc5_hdmi_csc_setup, 19858c2ecf20Sopenharmony_ci .reset = vc5_hdmi_reset, 19868c2ecf20Sopenharmony_ci .set_timings = vc5_hdmi_set_timings, 19878c2ecf20Sopenharmony_ci .phy_init = vc5_hdmi_phy_init, 19888c2ecf20Sopenharmony_ci .phy_disable = vc5_hdmi_phy_disable, 19898c2ecf20Sopenharmony_ci .phy_rng_enable = vc5_hdmi_phy_rng_enable, 19908c2ecf20Sopenharmony_ci .phy_rng_disable = vc5_hdmi_phy_rng_disable, 19918c2ecf20Sopenharmony_ci .channel_map = vc5_hdmi_channel_map, 19928c2ecf20Sopenharmony_ci}; 19938c2ecf20Sopenharmony_ci 19948c2ecf20Sopenharmony_cistatic const struct of_device_id vc4_hdmi_dt_match[] = { 19958c2ecf20Sopenharmony_ci { .compatible = "brcm,bcm2835-hdmi", .data = &bcm2835_variant }, 19968c2ecf20Sopenharmony_ci { .compatible = "brcm,bcm2711-hdmi0", .data = &bcm2711_hdmi0_variant }, 19978c2ecf20Sopenharmony_ci { .compatible = "brcm,bcm2711-hdmi1", .data = &bcm2711_hdmi1_variant }, 19988c2ecf20Sopenharmony_ci {} 19998c2ecf20Sopenharmony_ci}; 20008c2ecf20Sopenharmony_ci 20018c2ecf20Sopenharmony_cistatic const struct dev_pm_ops vc4_hdmi_pm_ops = { 20028c2ecf20Sopenharmony_ci SET_RUNTIME_PM_OPS(vc4_hdmi_runtime_suspend, 20038c2ecf20Sopenharmony_ci vc4_hdmi_runtime_resume, 20048c2ecf20Sopenharmony_ci NULL) 20058c2ecf20Sopenharmony_ci}; 20068c2ecf20Sopenharmony_ci 20078c2ecf20Sopenharmony_cistruct platform_driver vc4_hdmi_driver = { 20088c2ecf20Sopenharmony_ci .probe = vc4_hdmi_dev_probe, 20098c2ecf20Sopenharmony_ci .remove = vc4_hdmi_dev_remove, 20108c2ecf20Sopenharmony_ci .driver = { 20118c2ecf20Sopenharmony_ci .name = "vc4_hdmi", 20128c2ecf20Sopenharmony_ci .of_match_table = vc4_hdmi_dt_match, 20138c2ecf20Sopenharmony_ci .pm = &vc4_hdmi_pm_ops, 20148c2ecf20Sopenharmony_ci }, 20158c2ecf20Sopenharmony_ci}; 2016