162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2016 BayLibre, SAS 462306a36Sopenharmony_ci * Author: Neil Armstrong <narmstrong@baylibre.com> 562306a36Sopenharmony_ci * Copyright (C) 2015 Amlogic, Inc. All rights reserved. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/component.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci#include <linux/of_graph.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1662306a36Sopenharmony_ci#include <linux/reset.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <drm/bridge/dw_hdmi.h> 1962306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h> 2062306a36Sopenharmony_ci#include <drm/drm_bridge.h> 2162306a36Sopenharmony_ci#include <drm/drm_device.h> 2262306a36Sopenharmony_ci#include <drm/drm_edid.h> 2362306a36Sopenharmony_ci#include <drm/drm_probe_helper.h> 2462306a36Sopenharmony_ci#include <drm/drm_print.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include <linux/videodev2.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include "meson_drv.h" 2962306a36Sopenharmony_ci#include "meson_dw_hdmi.h" 3062306a36Sopenharmony_ci#include "meson_registers.h" 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define DRIVER_NAME "meson-dw-hdmi" 3362306a36Sopenharmony_ci#define DRIVER_DESC "Amlogic Meson HDMI-TX DRM driver" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/** 3662306a36Sopenharmony_ci * DOC: HDMI Output 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * HDMI Output is composed of : 3962306a36Sopenharmony_ci * 4062306a36Sopenharmony_ci * - A Synopsys DesignWare HDMI Controller IP 4162306a36Sopenharmony_ci * - A TOP control block controlling the Clocks and PHY 4262306a36Sopenharmony_ci * - A custom HDMI PHY in order convert video to TMDS signal 4362306a36Sopenharmony_ci * 4462306a36Sopenharmony_ci * .. code:: 4562306a36Sopenharmony_ci * 4662306a36Sopenharmony_ci * ___________________________________ 4762306a36Sopenharmony_ci * | HDMI TOP |<= HPD 4862306a36Sopenharmony_ci * |___________________________________| 4962306a36Sopenharmony_ci * | | | 5062306a36Sopenharmony_ci * | Synopsys HDMI | HDMI PHY |=> TMDS 5162306a36Sopenharmony_ci * | Controller |________________| 5262306a36Sopenharmony_ci * |___________________________________|<=> DDC 5362306a36Sopenharmony_ci * 5462306a36Sopenharmony_ci * 5562306a36Sopenharmony_ci * The HDMI TOP block only supports HPD sensing. 5662306a36Sopenharmony_ci * The Synopsys HDMI Controller interrupt is routed 5762306a36Sopenharmony_ci * through the TOP Block interrupt. 5862306a36Sopenharmony_ci * Communication to the TOP Block and the Synopsys 5962306a36Sopenharmony_ci * HDMI Controller is done a pair of addr+read/write 6062306a36Sopenharmony_ci * registers. 6162306a36Sopenharmony_ci * The HDMI PHY is configured by registers in the 6262306a36Sopenharmony_ci * HHI register block. 6362306a36Sopenharmony_ci * 6462306a36Sopenharmony_ci * Pixel data arrives in 4:4:4 format from the VENC 6562306a36Sopenharmony_ci * block and the VPU HDMI mux selects either the ENCI 6662306a36Sopenharmony_ci * encoder for the 576i or 480i formats or the ENCP 6762306a36Sopenharmony_ci * encoder for all the other formats including 6862306a36Sopenharmony_ci * interlaced HD formats. 6962306a36Sopenharmony_ci * The VENC uses a DVI encoder on top of the ENCI 7062306a36Sopenharmony_ci * or ENCP encoders to generate DVI timings for the 7162306a36Sopenharmony_ci * HDMI controller. 7262306a36Sopenharmony_ci * 7362306a36Sopenharmony_ci * GXBB, GXL and GXM embeds the Synopsys DesignWare 7462306a36Sopenharmony_ci * HDMI TX IP version 2.01a with HDCP and I2C & S/PDIF 7562306a36Sopenharmony_ci * audio source interfaces. 7662306a36Sopenharmony_ci * 7762306a36Sopenharmony_ci * We handle the following features : 7862306a36Sopenharmony_ci * 7962306a36Sopenharmony_ci * - HPD Rise & Fall interrupt 8062306a36Sopenharmony_ci * - HDMI Controller Interrupt 8162306a36Sopenharmony_ci * - HDMI PHY Init for 480i to 1080p60 8262306a36Sopenharmony_ci * - VENC & HDMI Clock setup for 480i to 1080p60 8362306a36Sopenharmony_ci * - VENC Mode setup for 480i to 1080p60 8462306a36Sopenharmony_ci * 8562306a36Sopenharmony_ci * What is missing : 8662306a36Sopenharmony_ci * 8762306a36Sopenharmony_ci * - PHY, Clock and Mode setup for 2k && 4k modes 8862306a36Sopenharmony_ci * - SDDC Scrambling mode for HDMI 2.0a 8962306a36Sopenharmony_ci * - HDCP Setup 9062306a36Sopenharmony_ci * - CEC Management 9162306a36Sopenharmony_ci */ 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/* TOP Block Communication Channel */ 9462306a36Sopenharmony_ci#define HDMITX_TOP_ADDR_REG 0x0 9562306a36Sopenharmony_ci#define HDMITX_TOP_DATA_REG 0x4 9662306a36Sopenharmony_ci#define HDMITX_TOP_CTRL_REG 0x8 9762306a36Sopenharmony_ci#define HDMITX_TOP_G12A_OFFSET 0x8000 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci/* Controller Communication Channel */ 10062306a36Sopenharmony_ci#define HDMITX_DWC_ADDR_REG 0x10 10162306a36Sopenharmony_ci#define HDMITX_DWC_DATA_REG 0x14 10262306a36Sopenharmony_ci#define HDMITX_DWC_CTRL_REG 0x18 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/* HHI Registers */ 10562306a36Sopenharmony_ci#define HHI_MEM_PD_REG0 0x100 /* 0x40 */ 10662306a36Sopenharmony_ci#define HHI_HDMI_CLK_CNTL 0x1cc /* 0x73 */ 10762306a36Sopenharmony_ci#define HHI_HDMI_PHY_CNTL0 0x3a0 /* 0xe8 */ 10862306a36Sopenharmony_ci#define HHI_HDMI_PHY_CNTL1 0x3a4 /* 0xe9 */ 10962306a36Sopenharmony_ci#define HHI_HDMI_PHY_CNTL2 0x3a8 /* 0xea */ 11062306a36Sopenharmony_ci#define HHI_HDMI_PHY_CNTL3 0x3ac /* 0xeb */ 11162306a36Sopenharmony_ci#define HHI_HDMI_PHY_CNTL4 0x3b0 /* 0xec */ 11262306a36Sopenharmony_ci#define HHI_HDMI_PHY_CNTL5 0x3b4 /* 0xed */ 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic DEFINE_SPINLOCK(reg_lock); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cienum meson_venc_source { 11762306a36Sopenharmony_ci MESON_VENC_SOURCE_NONE = 0, 11862306a36Sopenharmony_ci MESON_VENC_SOURCE_ENCI = 1, 11962306a36Sopenharmony_ci MESON_VENC_SOURCE_ENCP = 2, 12062306a36Sopenharmony_ci}; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistruct meson_dw_hdmi; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistruct meson_dw_hdmi_data { 12562306a36Sopenharmony_ci unsigned int (*top_read)(struct meson_dw_hdmi *dw_hdmi, 12662306a36Sopenharmony_ci unsigned int addr); 12762306a36Sopenharmony_ci void (*top_write)(struct meson_dw_hdmi *dw_hdmi, 12862306a36Sopenharmony_ci unsigned int addr, unsigned int data); 12962306a36Sopenharmony_ci unsigned int (*dwc_read)(struct meson_dw_hdmi *dw_hdmi, 13062306a36Sopenharmony_ci unsigned int addr); 13162306a36Sopenharmony_ci void (*dwc_write)(struct meson_dw_hdmi *dw_hdmi, 13262306a36Sopenharmony_ci unsigned int addr, unsigned int data); 13362306a36Sopenharmony_ci}; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistruct meson_dw_hdmi { 13662306a36Sopenharmony_ci struct dw_hdmi_plat_data dw_plat_data; 13762306a36Sopenharmony_ci struct meson_drm *priv; 13862306a36Sopenharmony_ci struct device *dev; 13962306a36Sopenharmony_ci void __iomem *hdmitx; 14062306a36Sopenharmony_ci const struct meson_dw_hdmi_data *data; 14162306a36Sopenharmony_ci struct reset_control *hdmitx_apb; 14262306a36Sopenharmony_ci struct reset_control *hdmitx_ctrl; 14362306a36Sopenharmony_ci struct reset_control *hdmitx_phy; 14462306a36Sopenharmony_ci u32 irq_stat; 14562306a36Sopenharmony_ci struct dw_hdmi *hdmi; 14662306a36Sopenharmony_ci struct drm_bridge *bridge; 14762306a36Sopenharmony_ci}; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic inline int dw_hdmi_is_compatible(struct meson_dw_hdmi *dw_hdmi, 15062306a36Sopenharmony_ci const char *compat) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci return of_device_is_compatible(dw_hdmi->dev->of_node, compat); 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci/* PHY (via TOP bridge) and Controller dedicated register interface */ 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic unsigned int dw_hdmi_top_read(struct meson_dw_hdmi *dw_hdmi, 15862306a36Sopenharmony_ci unsigned int addr) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci unsigned long flags; 16162306a36Sopenharmony_ci unsigned int data; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci spin_lock_irqsave(®_lock, flags); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* ADDR must be written twice */ 16662306a36Sopenharmony_ci writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); 16762306a36Sopenharmony_ci writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* Read needs a second DATA read */ 17062306a36Sopenharmony_ci data = readl(dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); 17162306a36Sopenharmony_ci data = readl(dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci spin_unlock_irqrestore(®_lock, flags); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci return data; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic unsigned int dw_hdmi_g12a_top_read(struct meson_dw_hdmi *dw_hdmi, 17962306a36Sopenharmony_ci unsigned int addr) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci return readl(dw_hdmi->hdmitx + HDMITX_TOP_G12A_OFFSET + (addr << 2)); 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic inline void dw_hdmi_top_write(struct meson_dw_hdmi *dw_hdmi, 18562306a36Sopenharmony_ci unsigned int addr, unsigned int data) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci unsigned long flags; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci spin_lock_irqsave(®_lock, flags); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* ADDR must be written twice */ 19262306a36Sopenharmony_ci writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); 19362306a36Sopenharmony_ci writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci /* Write needs single DATA write */ 19662306a36Sopenharmony_ci writel(data, dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci spin_unlock_irqrestore(®_lock, flags); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic inline void dw_hdmi_g12a_top_write(struct meson_dw_hdmi *dw_hdmi, 20262306a36Sopenharmony_ci unsigned int addr, unsigned int data) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci writel(data, dw_hdmi->hdmitx + HDMITX_TOP_G12A_OFFSET + (addr << 2)); 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci/* Helper to change specific bits in PHY registers */ 20862306a36Sopenharmony_cistatic inline void dw_hdmi_top_write_bits(struct meson_dw_hdmi *dw_hdmi, 20962306a36Sopenharmony_ci unsigned int addr, 21062306a36Sopenharmony_ci unsigned int mask, 21162306a36Sopenharmony_ci unsigned int val) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci unsigned int data = dw_hdmi->data->top_read(dw_hdmi, addr); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci data &= ~mask; 21662306a36Sopenharmony_ci data |= val; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, addr, data); 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic unsigned int dw_hdmi_dwc_read(struct meson_dw_hdmi *dw_hdmi, 22262306a36Sopenharmony_ci unsigned int addr) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci unsigned long flags; 22562306a36Sopenharmony_ci unsigned int data; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci spin_lock_irqsave(®_lock, flags); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci /* ADDR must be written twice */ 23062306a36Sopenharmony_ci writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); 23162306a36Sopenharmony_ci writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci /* Read needs a second DATA read */ 23462306a36Sopenharmony_ci data = readl(dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); 23562306a36Sopenharmony_ci data = readl(dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci spin_unlock_irqrestore(®_lock, flags); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci return data; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic unsigned int dw_hdmi_g12a_dwc_read(struct meson_dw_hdmi *dw_hdmi, 24362306a36Sopenharmony_ci unsigned int addr) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci return readb(dw_hdmi->hdmitx + addr); 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistatic inline void dw_hdmi_dwc_write(struct meson_dw_hdmi *dw_hdmi, 24962306a36Sopenharmony_ci unsigned int addr, unsigned int data) 25062306a36Sopenharmony_ci{ 25162306a36Sopenharmony_ci unsigned long flags; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci spin_lock_irqsave(®_lock, flags); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci /* ADDR must be written twice */ 25662306a36Sopenharmony_ci writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); 25762306a36Sopenharmony_ci writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* Write needs single DATA write */ 26062306a36Sopenharmony_ci writel(data, dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci spin_unlock_irqrestore(®_lock, flags); 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic inline void dw_hdmi_g12a_dwc_write(struct meson_dw_hdmi *dw_hdmi, 26662306a36Sopenharmony_ci unsigned int addr, unsigned int data) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci writeb(data, dw_hdmi->hdmitx + addr); 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci/* Helper to change specific bits in controller registers */ 27262306a36Sopenharmony_cistatic inline void dw_hdmi_dwc_write_bits(struct meson_dw_hdmi *dw_hdmi, 27362306a36Sopenharmony_ci unsigned int addr, 27462306a36Sopenharmony_ci unsigned int mask, 27562306a36Sopenharmony_ci unsigned int val) 27662306a36Sopenharmony_ci{ 27762306a36Sopenharmony_ci unsigned int data = dw_hdmi->data->dwc_read(dw_hdmi, addr); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci data &= ~mask; 28062306a36Sopenharmony_ci data |= val; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci dw_hdmi->data->dwc_write(dw_hdmi, addr, data); 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci/* Bridge */ 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci/* Setup PHY bandwidth modes */ 28862306a36Sopenharmony_cistatic void meson_hdmi_phy_setup_mode(struct meson_dw_hdmi *dw_hdmi, 28962306a36Sopenharmony_ci const struct drm_display_mode *mode, 29062306a36Sopenharmony_ci bool mode_is_420) 29162306a36Sopenharmony_ci{ 29262306a36Sopenharmony_ci struct meson_drm *priv = dw_hdmi->priv; 29362306a36Sopenharmony_ci unsigned int pixel_clock = mode->clock; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci /* For 420, pixel clock is half unlike venc clock */ 29662306a36Sopenharmony_ci if (mode_is_420) pixel_clock /= 2; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci if (dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || 29962306a36Sopenharmony_ci dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxm-dw-hdmi")) { 30062306a36Sopenharmony_ci if (pixel_clock >= 371250) { 30162306a36Sopenharmony_ci /* 5.94Gbps, 3.7125Gbps */ 30262306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x333d3282); 30362306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2136315b); 30462306a36Sopenharmony_ci } else if (pixel_clock >= 297000) { 30562306a36Sopenharmony_ci /* 2.97Gbps */ 30662306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33303382); 30762306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2036315b); 30862306a36Sopenharmony_ci } else if (pixel_clock >= 148500) { 30962306a36Sopenharmony_ci /* 1.485Gbps */ 31062306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33303362); 31162306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2016315b); 31262306a36Sopenharmony_ci } else { 31362306a36Sopenharmony_ci /* 742.5Mbps, and below */ 31462306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33604142); 31562306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x0016315b); 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci } else if (dw_hdmi_is_compatible(dw_hdmi, 31862306a36Sopenharmony_ci "amlogic,meson-gxbb-dw-hdmi")) { 31962306a36Sopenharmony_ci if (pixel_clock >= 371250) { 32062306a36Sopenharmony_ci /* 5.94Gbps, 3.7125Gbps */ 32162306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33353245); 32262306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2100115b); 32362306a36Sopenharmony_ci } else if (pixel_clock >= 297000) { 32462306a36Sopenharmony_ci /* 2.97Gbps */ 32562306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33634283); 32662306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0xb000115b); 32762306a36Sopenharmony_ci } else { 32862306a36Sopenharmony_ci /* 1.485Gbps, and below */ 32962306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33632122); 33062306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2000115b); 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci } else if (dw_hdmi_is_compatible(dw_hdmi, 33362306a36Sopenharmony_ci "amlogic,meson-g12a-dw-hdmi")) { 33462306a36Sopenharmony_ci if (pixel_clock >= 371250) { 33562306a36Sopenharmony_ci /* 5.94Gbps, 3.7125Gbps */ 33662306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x37eb65c4); 33762306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b); 33862306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x0000080b); 33962306a36Sopenharmony_ci } else if (pixel_clock >= 297000) { 34062306a36Sopenharmony_ci /* 2.97Gbps */ 34162306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33eb6262); 34262306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b); 34362306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x00000003); 34462306a36Sopenharmony_ci } else { 34562306a36Sopenharmony_ci /* 1.485Gbps, and below */ 34662306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33eb4242); 34762306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b); 34862306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x00000003); 34962306a36Sopenharmony_ci } 35062306a36Sopenharmony_ci } 35162306a36Sopenharmony_ci} 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic inline void meson_dw_hdmi_phy_reset(struct meson_dw_hdmi *dw_hdmi) 35462306a36Sopenharmony_ci{ 35562306a36Sopenharmony_ci struct meson_drm *priv = dw_hdmi->priv; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci /* Enable and software reset */ 35862306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0xf); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci mdelay(2); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci /* Enable and unreset */ 36362306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0xe); 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci mdelay(2); 36662306a36Sopenharmony_ci} 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_cistatic int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, 36962306a36Sopenharmony_ci const struct drm_display_info *display, 37062306a36Sopenharmony_ci const struct drm_display_mode *mode) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; 37362306a36Sopenharmony_ci bool is_hdmi2_sink = display->hdmi.scdc.supported; 37462306a36Sopenharmony_ci struct meson_drm *priv = dw_hdmi->priv; 37562306a36Sopenharmony_ci unsigned int wr_clk = 37662306a36Sopenharmony_ci readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING)); 37762306a36Sopenharmony_ci bool mode_is_420 = false; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci DRM_DEBUG_DRIVER("\"%s\" div%d\n", mode->name, 38062306a36Sopenharmony_ci mode->clock > 340000 ? 40 : 10); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci if (drm_mode_is_420_only(display, mode) || 38362306a36Sopenharmony_ci (!is_hdmi2_sink && drm_mode_is_420_also(display, mode)) || 38462306a36Sopenharmony_ci dw_hdmi_bus_fmt_is_420(hdmi)) 38562306a36Sopenharmony_ci mode_is_420 = true; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci /* Enable clocks */ 38862306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, 0xffff, 0x100); 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci /* Bring HDMITX MEM output of power down */ 39162306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_MEM_PD_REG0, 0xff << 8, 0); 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci /* Bring out of reset */ 39462306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_SW_RESET, 0); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci /* Enable internal pixclk, tmds_clk, spdif_clk, i2s_clk, cecclk */ 39762306a36Sopenharmony_ci dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_CLK_CNTL, 39862306a36Sopenharmony_ci 0x3, 0x3); 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci /* Enable cec_clk and hdcp22_tmdsclk_en */ 40162306a36Sopenharmony_ci dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_CLK_CNTL, 40262306a36Sopenharmony_ci 0x3 << 4, 0x3 << 4); 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci /* Enable normal output to PHY */ 40562306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12)); 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci /* TMDS pattern setup */ 40862306a36Sopenharmony_ci if (mode->clock > 340000 && !mode_is_420) { 40962306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 41062306a36Sopenharmony_ci 0); 41162306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, 41262306a36Sopenharmony_ci 0x03ff03ff); 41362306a36Sopenharmony_ci } else { 41462306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 41562306a36Sopenharmony_ci 0x001f001f); 41662306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, 41762306a36Sopenharmony_ci 0x001f001f); 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci /* Load TMDS pattern */ 42162306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1); 42262306a36Sopenharmony_ci msleep(20); 42362306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci /* Setup PHY parameters */ 42662306a36Sopenharmony_ci meson_hdmi_phy_setup_mode(dw_hdmi, mode, mode_is_420); 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci /* Setup PHY */ 42962306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 43062306a36Sopenharmony_ci 0xffff << 16, 0x0390 << 16); 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci /* BIT_INVERT */ 43362306a36Sopenharmony_ci if (dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || 43462306a36Sopenharmony_ci dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxm-dw-hdmi") || 43562306a36Sopenharmony_ci dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-g12a-dw-hdmi")) 43662306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 43762306a36Sopenharmony_ci BIT(17), 0); 43862306a36Sopenharmony_ci else 43962306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 44062306a36Sopenharmony_ci BIT(17), BIT(17)); 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci /* Disable clock, fifo, fifo_wr */ 44362306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0); 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci dw_hdmi_set_high_tmds_clock_ratio(hdmi, display); 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci msleep(100); 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci /* Reset PHY 3 times in a row */ 45062306a36Sopenharmony_ci meson_dw_hdmi_phy_reset(dw_hdmi); 45162306a36Sopenharmony_ci meson_dw_hdmi_phy_reset(dw_hdmi); 45262306a36Sopenharmony_ci meson_dw_hdmi_phy_reset(dw_hdmi); 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci /* Temporary Disable VENC video stream */ 45562306a36Sopenharmony_ci if (priv->venc.hdmi_use_enci) 45662306a36Sopenharmony_ci writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN)); 45762306a36Sopenharmony_ci else 45862306a36Sopenharmony_ci writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci /* Temporary Disable HDMI video stream to HDMI-TX */ 46162306a36Sopenharmony_ci writel_bits_relaxed(0x3, 0, 46262306a36Sopenharmony_ci priv->io_base + _REG(VPU_HDMI_SETTING)); 46362306a36Sopenharmony_ci writel_bits_relaxed(0xf << 8, 0, 46462306a36Sopenharmony_ci priv->io_base + _REG(VPU_HDMI_SETTING)); 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci /* Re-Enable VENC video stream */ 46762306a36Sopenharmony_ci if (priv->venc.hdmi_use_enci) 46862306a36Sopenharmony_ci writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN)); 46962306a36Sopenharmony_ci else 47062306a36Sopenharmony_ci writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci /* Push back HDMI clock settings */ 47362306a36Sopenharmony_ci writel_bits_relaxed(0xf << 8, wr_clk & (0xf << 8), 47462306a36Sopenharmony_ci priv->io_base + _REG(VPU_HDMI_SETTING)); 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci /* Enable and Select HDMI video source for HDMI-TX */ 47762306a36Sopenharmony_ci if (priv->venc.hdmi_use_enci) 47862306a36Sopenharmony_ci writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCI, 47962306a36Sopenharmony_ci priv->io_base + _REG(VPU_HDMI_SETTING)); 48062306a36Sopenharmony_ci else 48162306a36Sopenharmony_ci writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCP, 48262306a36Sopenharmony_ci priv->io_base + _REG(VPU_HDMI_SETTING)); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci return 0; 48562306a36Sopenharmony_ci} 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_cistatic void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, 48862306a36Sopenharmony_ci void *data) 48962306a36Sopenharmony_ci{ 49062306a36Sopenharmony_ci struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; 49162306a36Sopenharmony_ci struct meson_drm *priv = dw_hdmi->priv; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci DRM_DEBUG_DRIVER("\n"); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0); 49662306a36Sopenharmony_ci} 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_cistatic enum drm_connector_status dw_hdmi_read_hpd(struct dw_hdmi *hdmi, 49962306a36Sopenharmony_ci void *data) 50062306a36Sopenharmony_ci{ 50162306a36Sopenharmony_ci struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci return !!dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_STAT0) ? 50462306a36Sopenharmony_ci connector_status_connected : connector_status_disconnected; 50562306a36Sopenharmony_ci} 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_cistatic void dw_hdmi_setup_hpd(struct dw_hdmi *hdmi, 50862306a36Sopenharmony_ci void *data) 50962306a36Sopenharmony_ci{ 51062306a36Sopenharmony_ci struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci /* Setup HPD Filter */ 51362306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_HPD_FILTER, 51462306a36Sopenharmony_ci (0xa << 12) | 0xa0); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci /* Clear interrupts */ 51762306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, 51862306a36Sopenharmony_ci HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL); 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci /* Unmask interrupts */ 52162306a36Sopenharmony_ci dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_INTR_MASKN, 52262306a36Sopenharmony_ci HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL, 52362306a36Sopenharmony_ci HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL); 52462306a36Sopenharmony_ci} 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_cistatic const struct dw_hdmi_phy_ops meson_dw_hdmi_phy_ops = { 52762306a36Sopenharmony_ci .init = dw_hdmi_phy_init, 52862306a36Sopenharmony_ci .disable = dw_hdmi_phy_disable, 52962306a36Sopenharmony_ci .read_hpd = dw_hdmi_read_hpd, 53062306a36Sopenharmony_ci .setup_hpd = dw_hdmi_setup_hpd, 53162306a36Sopenharmony_ci}; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_cistatic irqreturn_t dw_hdmi_top_irq(int irq, void *dev_id) 53462306a36Sopenharmony_ci{ 53562306a36Sopenharmony_ci struct meson_dw_hdmi *dw_hdmi = dev_id; 53662306a36Sopenharmony_ci u32 stat; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci stat = dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_INTR_STAT); 53962306a36Sopenharmony_ci dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, stat); 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci /* HPD Events, handle in the threaded interrupt handler */ 54262306a36Sopenharmony_ci if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { 54362306a36Sopenharmony_ci dw_hdmi->irq_stat = stat; 54462306a36Sopenharmony_ci return IRQ_WAKE_THREAD; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci /* HDMI Controller Interrupt */ 54862306a36Sopenharmony_ci if (stat & 1) 54962306a36Sopenharmony_ci return IRQ_NONE; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci /* TOFIX Handle HDCP Interrupts */ 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci return IRQ_HANDLED; 55462306a36Sopenharmony_ci} 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci/* Threaded interrupt handler to manage HPD events */ 55762306a36Sopenharmony_cistatic irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) 55862306a36Sopenharmony_ci{ 55962306a36Sopenharmony_ci struct meson_dw_hdmi *dw_hdmi = dev_id; 56062306a36Sopenharmony_ci u32 stat = dw_hdmi->irq_stat; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci /* HPD Events */ 56362306a36Sopenharmony_ci if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { 56462306a36Sopenharmony_ci bool hpd_connected = false; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci if (stat & HDMITX_TOP_INTR_HPD_RISE) 56762306a36Sopenharmony_ci hpd_connected = true; 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci dw_hdmi_setup_rx_sense(dw_hdmi->hdmi, hpd_connected, 57062306a36Sopenharmony_ci hpd_connected); 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci drm_helper_hpd_irq_event(dw_hdmi->bridge->dev); 57362306a36Sopenharmony_ci drm_bridge_hpd_notify(dw_hdmi->bridge, 57462306a36Sopenharmony_ci hpd_connected ? connector_status_connected 57562306a36Sopenharmony_ci : connector_status_disconnected); 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci return IRQ_HANDLED; 57962306a36Sopenharmony_ci} 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci/* DW HDMI Regmap */ 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_cistatic int meson_dw_hdmi_reg_read(void *context, unsigned int reg, 58462306a36Sopenharmony_ci unsigned int *result) 58562306a36Sopenharmony_ci{ 58662306a36Sopenharmony_ci struct meson_dw_hdmi *dw_hdmi = context; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci *result = dw_hdmi->data->dwc_read(dw_hdmi, reg); 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci return 0; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci} 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_cistatic int meson_dw_hdmi_reg_write(void *context, unsigned int reg, 59562306a36Sopenharmony_ci unsigned int val) 59662306a36Sopenharmony_ci{ 59762306a36Sopenharmony_ci struct meson_dw_hdmi *dw_hdmi = context; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci dw_hdmi->data->dwc_write(dw_hdmi, reg, val); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci return 0; 60262306a36Sopenharmony_ci} 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_cistatic const struct regmap_config meson_dw_hdmi_regmap_config = { 60562306a36Sopenharmony_ci .reg_bits = 32, 60662306a36Sopenharmony_ci .val_bits = 8, 60762306a36Sopenharmony_ci .reg_read = meson_dw_hdmi_reg_read, 60862306a36Sopenharmony_ci .reg_write = meson_dw_hdmi_reg_write, 60962306a36Sopenharmony_ci .max_register = 0x10000, 61062306a36Sopenharmony_ci .fast_io = true, 61162306a36Sopenharmony_ci}; 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_cistatic const struct meson_dw_hdmi_data meson_dw_hdmi_gx_data = { 61462306a36Sopenharmony_ci .top_read = dw_hdmi_top_read, 61562306a36Sopenharmony_ci .top_write = dw_hdmi_top_write, 61662306a36Sopenharmony_ci .dwc_read = dw_hdmi_dwc_read, 61762306a36Sopenharmony_ci .dwc_write = dw_hdmi_dwc_write, 61862306a36Sopenharmony_ci}; 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_cistatic const struct meson_dw_hdmi_data meson_dw_hdmi_g12a_data = { 62162306a36Sopenharmony_ci .top_read = dw_hdmi_g12a_top_read, 62262306a36Sopenharmony_ci .top_write = dw_hdmi_g12a_top_write, 62362306a36Sopenharmony_ci .dwc_read = dw_hdmi_g12a_dwc_read, 62462306a36Sopenharmony_ci .dwc_write = dw_hdmi_g12a_dwc_write, 62562306a36Sopenharmony_ci}; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_cistatic void meson_dw_hdmi_init(struct meson_dw_hdmi *meson_dw_hdmi) 62862306a36Sopenharmony_ci{ 62962306a36Sopenharmony_ci struct meson_drm *priv = meson_dw_hdmi->priv; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci /* Enable clocks */ 63262306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, 0xffff, 0x100); 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci /* Bring HDMITX MEM output of power down */ 63562306a36Sopenharmony_ci regmap_update_bits(priv->hhi, HHI_MEM_PD_REG0, 0xff << 8, 0); 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci /* Reset HDMITX APB & TX & PHY */ 63862306a36Sopenharmony_ci reset_control_reset(meson_dw_hdmi->hdmitx_apb); 63962306a36Sopenharmony_ci reset_control_reset(meson_dw_hdmi->hdmitx_ctrl); 64062306a36Sopenharmony_ci reset_control_reset(meson_dw_hdmi->hdmitx_phy); 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci /* Enable APB3 fail on error */ 64362306a36Sopenharmony_ci if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { 64462306a36Sopenharmony_ci writel_bits_relaxed(BIT(15), BIT(15), 64562306a36Sopenharmony_ci meson_dw_hdmi->hdmitx + HDMITX_TOP_CTRL_REG); 64662306a36Sopenharmony_ci writel_bits_relaxed(BIT(15), BIT(15), 64762306a36Sopenharmony_ci meson_dw_hdmi->hdmitx + HDMITX_DWC_CTRL_REG); 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci /* Bring out of reset */ 65162306a36Sopenharmony_ci meson_dw_hdmi->data->top_write(meson_dw_hdmi, 65262306a36Sopenharmony_ci HDMITX_TOP_SW_RESET, 0); 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci msleep(20); 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci meson_dw_hdmi->data->top_write(meson_dw_hdmi, 65762306a36Sopenharmony_ci HDMITX_TOP_CLK_CNTL, 0xff); 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci /* Enable HDMI-TX Interrupt */ 66062306a36Sopenharmony_ci meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, 66162306a36Sopenharmony_ci HDMITX_TOP_INTR_CORE); 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_INTR_MASKN, 66462306a36Sopenharmony_ci HDMITX_TOP_INTR_CORE); 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci} 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_cistatic void meson_disable_clk(void *data) 66962306a36Sopenharmony_ci{ 67062306a36Sopenharmony_ci clk_disable_unprepare(data); 67162306a36Sopenharmony_ci} 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_cistatic int meson_enable_clk(struct device *dev, char *name) 67462306a36Sopenharmony_ci{ 67562306a36Sopenharmony_ci struct clk *clk; 67662306a36Sopenharmony_ci int ret; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci clk = devm_clk_get(dev, name); 67962306a36Sopenharmony_ci if (IS_ERR(clk)) { 68062306a36Sopenharmony_ci dev_err(dev, "Unable to get %s pclk\n", name); 68162306a36Sopenharmony_ci return PTR_ERR(clk); 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci ret = clk_prepare_enable(clk); 68562306a36Sopenharmony_ci if (!ret) 68662306a36Sopenharmony_ci ret = devm_add_action_or_reset(dev, meson_disable_clk, clk); 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci return ret; 68962306a36Sopenharmony_ci} 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_cistatic int meson_dw_hdmi_bind(struct device *dev, struct device *master, 69262306a36Sopenharmony_ci void *data) 69362306a36Sopenharmony_ci{ 69462306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 69562306a36Sopenharmony_ci const struct meson_dw_hdmi_data *match; 69662306a36Sopenharmony_ci struct meson_dw_hdmi *meson_dw_hdmi; 69762306a36Sopenharmony_ci struct drm_device *drm = data; 69862306a36Sopenharmony_ci struct meson_drm *priv = drm->dev_private; 69962306a36Sopenharmony_ci struct dw_hdmi_plat_data *dw_plat_data; 70062306a36Sopenharmony_ci int irq; 70162306a36Sopenharmony_ci int ret; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci DRM_DEBUG_DRIVER("\n"); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci match = of_device_get_match_data(&pdev->dev); 70662306a36Sopenharmony_ci if (!match) { 70762306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to get match data\n"); 70862306a36Sopenharmony_ci return -ENODEV; 70962306a36Sopenharmony_ci } 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci meson_dw_hdmi = devm_kzalloc(dev, sizeof(*meson_dw_hdmi), 71262306a36Sopenharmony_ci GFP_KERNEL); 71362306a36Sopenharmony_ci if (!meson_dw_hdmi) 71462306a36Sopenharmony_ci return -ENOMEM; 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci meson_dw_hdmi->priv = priv; 71762306a36Sopenharmony_ci meson_dw_hdmi->dev = dev; 71862306a36Sopenharmony_ci meson_dw_hdmi->data = match; 71962306a36Sopenharmony_ci dw_plat_data = &meson_dw_hdmi->dw_plat_data; 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci ret = devm_regulator_get_enable_optional(dev, "hdmi"); 72262306a36Sopenharmony_ci if (ret < 0 && ret != -ENODEV) 72362306a36Sopenharmony_ci return ret; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci meson_dw_hdmi->hdmitx_apb = devm_reset_control_get_exclusive(dev, 72662306a36Sopenharmony_ci "hdmitx_apb"); 72762306a36Sopenharmony_ci if (IS_ERR(meson_dw_hdmi->hdmitx_apb)) { 72862306a36Sopenharmony_ci dev_err(dev, "Failed to get hdmitx_apb reset\n"); 72962306a36Sopenharmony_ci return PTR_ERR(meson_dw_hdmi->hdmitx_apb); 73062306a36Sopenharmony_ci } 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci meson_dw_hdmi->hdmitx_ctrl = devm_reset_control_get_exclusive(dev, 73362306a36Sopenharmony_ci "hdmitx"); 73462306a36Sopenharmony_ci if (IS_ERR(meson_dw_hdmi->hdmitx_ctrl)) { 73562306a36Sopenharmony_ci dev_err(dev, "Failed to get hdmitx reset\n"); 73662306a36Sopenharmony_ci return PTR_ERR(meson_dw_hdmi->hdmitx_ctrl); 73762306a36Sopenharmony_ci } 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_ci meson_dw_hdmi->hdmitx_phy = devm_reset_control_get_exclusive(dev, 74062306a36Sopenharmony_ci "hdmitx_phy"); 74162306a36Sopenharmony_ci if (IS_ERR(meson_dw_hdmi->hdmitx_phy)) { 74262306a36Sopenharmony_ci dev_err(dev, "Failed to get hdmitx_phy reset\n"); 74362306a36Sopenharmony_ci return PTR_ERR(meson_dw_hdmi->hdmitx_phy); 74462306a36Sopenharmony_ci } 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci meson_dw_hdmi->hdmitx = devm_platform_ioremap_resource(pdev, 0); 74762306a36Sopenharmony_ci if (IS_ERR(meson_dw_hdmi->hdmitx)) 74862306a36Sopenharmony_ci return PTR_ERR(meson_dw_hdmi->hdmitx); 74962306a36Sopenharmony_ci 75062306a36Sopenharmony_ci ret = meson_enable_clk(dev, "isfr"); 75162306a36Sopenharmony_ci if (ret) 75262306a36Sopenharmony_ci return ret; 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci ret = meson_enable_clk(dev, "iahb"); 75562306a36Sopenharmony_ci if (ret) 75662306a36Sopenharmony_ci return ret; 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci ret = meson_enable_clk(dev, "venci"); 75962306a36Sopenharmony_ci if (ret) 76062306a36Sopenharmony_ci return ret; 76162306a36Sopenharmony_ci 76262306a36Sopenharmony_ci dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi, 76362306a36Sopenharmony_ci &meson_dw_hdmi_regmap_config); 76462306a36Sopenharmony_ci if (IS_ERR(dw_plat_data->regm)) 76562306a36Sopenharmony_ci return PTR_ERR(dw_plat_data->regm); 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 76862306a36Sopenharmony_ci if (irq < 0) 76962306a36Sopenharmony_ci return irq; 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci ret = devm_request_threaded_irq(dev, irq, dw_hdmi_top_irq, 77262306a36Sopenharmony_ci dw_hdmi_top_thread_irq, IRQF_SHARED, 77362306a36Sopenharmony_ci "dw_hdmi_top_irq", meson_dw_hdmi); 77462306a36Sopenharmony_ci if (ret) { 77562306a36Sopenharmony_ci dev_err(dev, "Failed to request hdmi top irq\n"); 77662306a36Sopenharmony_ci return ret; 77762306a36Sopenharmony_ci } 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci meson_dw_hdmi_init(meson_dw_hdmi); 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_ci /* Bridge / Connector */ 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci dw_plat_data->priv_data = meson_dw_hdmi; 78462306a36Sopenharmony_ci dw_plat_data->phy_ops = &meson_dw_hdmi_phy_ops; 78562306a36Sopenharmony_ci dw_plat_data->phy_name = "meson_dw_hdmi_phy"; 78662306a36Sopenharmony_ci dw_plat_data->phy_data = meson_dw_hdmi; 78762306a36Sopenharmony_ci dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709; 78862306a36Sopenharmony_ci dw_plat_data->ycbcr_420_allowed = true; 78962306a36Sopenharmony_ci dw_plat_data->disable_cec = true; 79062306a36Sopenharmony_ci dw_plat_data->output_port = 1; 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci if (dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || 79362306a36Sopenharmony_ci dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxm-dw-hdmi") || 79462306a36Sopenharmony_ci dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-g12a-dw-hdmi")) 79562306a36Sopenharmony_ci dw_plat_data->use_drm_infoframe = true; 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci platform_set_drvdata(pdev, meson_dw_hdmi); 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, &meson_dw_hdmi->dw_plat_data); 80062306a36Sopenharmony_ci if (IS_ERR(meson_dw_hdmi->hdmi)) 80162306a36Sopenharmony_ci return PTR_ERR(meson_dw_hdmi->hdmi); 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci meson_dw_hdmi->bridge = of_drm_find_bridge(pdev->dev.of_node); 80462306a36Sopenharmony_ci 80562306a36Sopenharmony_ci DRM_DEBUG_DRIVER("HDMI controller initialized\n"); 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci return 0; 80862306a36Sopenharmony_ci} 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_cistatic void meson_dw_hdmi_unbind(struct device *dev, struct device *master, 81162306a36Sopenharmony_ci void *data) 81262306a36Sopenharmony_ci{ 81362306a36Sopenharmony_ci struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci dw_hdmi_unbind(meson_dw_hdmi->hdmi); 81662306a36Sopenharmony_ci} 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_cistatic const struct component_ops meson_dw_hdmi_ops = { 81962306a36Sopenharmony_ci .bind = meson_dw_hdmi_bind, 82062306a36Sopenharmony_ci .unbind = meson_dw_hdmi_unbind, 82162306a36Sopenharmony_ci}; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_cistatic int __maybe_unused meson_dw_hdmi_pm_suspend(struct device *dev) 82462306a36Sopenharmony_ci{ 82562306a36Sopenharmony_ci struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); 82662306a36Sopenharmony_ci 82762306a36Sopenharmony_ci if (!meson_dw_hdmi) 82862306a36Sopenharmony_ci return 0; 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci /* Reset TOP */ 83162306a36Sopenharmony_ci meson_dw_hdmi->data->top_write(meson_dw_hdmi, 83262306a36Sopenharmony_ci HDMITX_TOP_SW_RESET, 0); 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci return 0; 83562306a36Sopenharmony_ci} 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_cistatic int __maybe_unused meson_dw_hdmi_pm_resume(struct device *dev) 83862306a36Sopenharmony_ci{ 83962306a36Sopenharmony_ci struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); 84062306a36Sopenharmony_ci 84162306a36Sopenharmony_ci if (!meson_dw_hdmi) 84262306a36Sopenharmony_ci return 0; 84362306a36Sopenharmony_ci 84462306a36Sopenharmony_ci meson_dw_hdmi_init(meson_dw_hdmi); 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci dw_hdmi_resume(meson_dw_hdmi->hdmi); 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci return 0; 84962306a36Sopenharmony_ci} 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_cistatic int meson_dw_hdmi_probe(struct platform_device *pdev) 85262306a36Sopenharmony_ci{ 85362306a36Sopenharmony_ci return component_add(&pdev->dev, &meson_dw_hdmi_ops); 85462306a36Sopenharmony_ci} 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_cistatic void meson_dw_hdmi_remove(struct platform_device *pdev) 85762306a36Sopenharmony_ci{ 85862306a36Sopenharmony_ci component_del(&pdev->dev, &meson_dw_hdmi_ops); 85962306a36Sopenharmony_ci} 86062306a36Sopenharmony_ci 86162306a36Sopenharmony_cistatic const struct dev_pm_ops meson_dw_hdmi_pm_ops = { 86262306a36Sopenharmony_ci SET_SYSTEM_SLEEP_PM_OPS(meson_dw_hdmi_pm_suspend, 86362306a36Sopenharmony_ci meson_dw_hdmi_pm_resume) 86462306a36Sopenharmony_ci}; 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_cistatic const struct of_device_id meson_dw_hdmi_of_table[] = { 86762306a36Sopenharmony_ci { .compatible = "amlogic,meson-gxbb-dw-hdmi", 86862306a36Sopenharmony_ci .data = &meson_dw_hdmi_gx_data }, 86962306a36Sopenharmony_ci { .compatible = "amlogic,meson-gxl-dw-hdmi", 87062306a36Sopenharmony_ci .data = &meson_dw_hdmi_gx_data }, 87162306a36Sopenharmony_ci { .compatible = "amlogic,meson-gxm-dw-hdmi", 87262306a36Sopenharmony_ci .data = &meson_dw_hdmi_gx_data }, 87362306a36Sopenharmony_ci { .compatible = "amlogic,meson-g12a-dw-hdmi", 87462306a36Sopenharmony_ci .data = &meson_dw_hdmi_g12a_data }, 87562306a36Sopenharmony_ci { } 87662306a36Sopenharmony_ci}; 87762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, meson_dw_hdmi_of_table); 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_cistatic struct platform_driver meson_dw_hdmi_platform_driver = { 88062306a36Sopenharmony_ci .probe = meson_dw_hdmi_probe, 88162306a36Sopenharmony_ci .remove_new = meson_dw_hdmi_remove, 88262306a36Sopenharmony_ci .driver = { 88362306a36Sopenharmony_ci .name = DRIVER_NAME, 88462306a36Sopenharmony_ci .of_match_table = meson_dw_hdmi_of_table, 88562306a36Sopenharmony_ci .pm = &meson_dw_hdmi_pm_ops, 88662306a36Sopenharmony_ci }, 88762306a36Sopenharmony_ci}; 88862306a36Sopenharmony_cimodule_platform_driver(meson_dw_hdmi_platform_driver); 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_ciMODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 89162306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 89262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 893