11ccea77eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
23f68be7dSNeil Armstrong /*
33f68be7dSNeil Armstrong * Copyright (C) 2016 BayLibre, SAS
43f68be7dSNeil Armstrong * Author: Neil Armstrong <narmstrong@baylibre.com>
53f68be7dSNeil Armstrong * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
63f68be7dSNeil Armstrong */
73f68be7dSNeil Armstrong
866620f48SSam Ravnborg #include <linux/clk.h>
966620f48SSam Ravnborg #include <linux/component.h>
103f68be7dSNeil Armstrong #include <linux/kernel.h>
113f68be7dSNeil Armstrong #include <linux/module.h>
12722d4f06SRob Herring #include <linux/of.h>
133f68be7dSNeil Armstrong #include <linux/of_graph.h>
14722d4f06SRob Herring #include <linux/platform_device.h>
15161a803fSNeil Armstrong #include <linux/regulator/consumer.h>
1666620f48SSam Ravnborg #include <linux/reset.h>
173f68be7dSNeil Armstrong
1866620f48SSam Ravnborg #include <drm/bridge/dw_hdmi.h>
193f68be7dSNeil Armstrong #include <drm/drm_atomic_helper.h>
20f43aa584SNeil Armstrong #include <drm/drm_bridge.h>
2166620f48SSam Ravnborg #include <drm/drm_device.h>
22fcd70cd3SDaniel Vetter #include <drm/drm_edid.h>
23fcd70cd3SDaniel Vetter #include <drm/drm_probe_helper.h>
2466620f48SSam Ravnborg #include <drm/drm_print.h>
253f68be7dSNeil Armstrong
2666620f48SSam Ravnborg #include <linux/videodev2.h>
273f68be7dSNeil Armstrong
283f68be7dSNeil Armstrong #include "meson_drv.h"
293f68be7dSNeil Armstrong #include "meson_dw_hdmi.h"
303f68be7dSNeil Armstrong #include "meson_registers.h"
313f68be7dSNeil Armstrong
323f68be7dSNeil Armstrong #define DRIVER_NAME "meson-dw-hdmi"
333f68be7dSNeil Armstrong #define DRIVER_DESC "Amlogic Meson HDMI-TX DRM driver"
343f68be7dSNeil Armstrong
352021d5b7SNeil Armstrong /**
362021d5b7SNeil Armstrong * DOC: HDMI Output
372021d5b7SNeil Armstrong *
383f68be7dSNeil Armstrong * HDMI Output is composed of :
392021d5b7SNeil Armstrong *
403f68be7dSNeil Armstrong * - A Synopsys DesignWare HDMI Controller IP
413f68be7dSNeil Armstrong * - A TOP control block controlling the Clocks and PHY
423f68be7dSNeil Armstrong * - A custom HDMI PHY in order convert video to TMDS signal
432021d5b7SNeil Armstrong *
442021d5b7SNeil Armstrong * .. code::
452021d5b7SNeil Armstrong *
463f68be7dSNeil Armstrong * ___________________________________
473f68be7dSNeil Armstrong * | HDMI TOP |<= HPD
483f68be7dSNeil Armstrong * |___________________________________|
493f68be7dSNeil Armstrong * | | |
503f68be7dSNeil Armstrong * | Synopsys HDMI | HDMI PHY |=> TMDS
513f68be7dSNeil Armstrong * | Controller |________________|
523f68be7dSNeil Armstrong * |___________________________________|<=> DDC
533f68be7dSNeil Armstrong *
542021d5b7SNeil Armstrong *
553f68be7dSNeil Armstrong * The HDMI TOP block only supports HPD sensing.
563f68be7dSNeil Armstrong * The Synopsys HDMI Controller interrupt is routed
573f68be7dSNeil Armstrong * through the TOP Block interrupt.
583f68be7dSNeil Armstrong * Communication to the TOP Block and the Synopsys
593f68be7dSNeil Armstrong * HDMI Controller is done a pair of addr+read/write
603f68be7dSNeil Armstrong * registers.
613f68be7dSNeil Armstrong * The HDMI PHY is configured by registers in the
623f68be7dSNeil Armstrong * HHI register block.
633f68be7dSNeil Armstrong *
643f68be7dSNeil Armstrong * Pixel data arrives in 4:4:4 format from the VENC
653f68be7dSNeil Armstrong * block and the VPU HDMI mux selects either the ENCI
663f68be7dSNeil Armstrong * encoder for the 576i or 480i formats or the ENCP
673f68be7dSNeil Armstrong * encoder for all the other formats including
683f68be7dSNeil Armstrong * interlaced HD formats.
693f68be7dSNeil Armstrong * The VENC uses a DVI encoder on top of the ENCI
703f68be7dSNeil Armstrong * or ENCP encoders to generate DVI timings for the
713f68be7dSNeil Armstrong * HDMI controller.
723f68be7dSNeil Armstrong *
733f68be7dSNeil Armstrong * GXBB, GXL and GXM embeds the Synopsys DesignWare
743f68be7dSNeil Armstrong * HDMI TX IP version 2.01a with HDCP and I2C & S/PDIF
753f68be7dSNeil Armstrong * audio source interfaces.
763f68be7dSNeil Armstrong *
773f68be7dSNeil Armstrong * We handle the following features :
782021d5b7SNeil Armstrong *
793f68be7dSNeil Armstrong * - HPD Rise & Fall interrupt
803f68be7dSNeil Armstrong * - HDMI Controller Interrupt
813f68be7dSNeil Armstrong * - HDMI PHY Init for 480i to 1080p60
823f68be7dSNeil Armstrong * - VENC & HDMI Clock setup for 480i to 1080p60
833f68be7dSNeil Armstrong * - VENC Mode setup for 480i to 1080p60
843f68be7dSNeil Armstrong *
853f68be7dSNeil Armstrong * What is missing :
862021d5b7SNeil Armstrong *
873f68be7dSNeil Armstrong * - PHY, Clock and Mode setup for 2k && 4k modes
883f68be7dSNeil Armstrong * - SDDC Scrambling mode for HDMI 2.0a
893f68be7dSNeil Armstrong * - HDCP Setup
903f68be7dSNeil Armstrong * - CEC Management
913f68be7dSNeil Armstrong */
923f68be7dSNeil Armstrong
933f68be7dSNeil Armstrong /* TOP Block Communication Channel */
943f68be7dSNeil Armstrong #define HDMITX_TOP_ADDR_REG 0x0
953f68be7dSNeil Armstrong #define HDMITX_TOP_DATA_REG 0x4
963f68be7dSNeil Armstrong #define HDMITX_TOP_CTRL_REG 0x8
973b7c1237SNeil Armstrong #define HDMITX_TOP_G12A_OFFSET 0x8000
983f68be7dSNeil Armstrong
993f68be7dSNeil Armstrong /* Controller Communication Channel */
1003f68be7dSNeil Armstrong #define HDMITX_DWC_ADDR_REG 0x10
1013f68be7dSNeil Armstrong #define HDMITX_DWC_DATA_REG 0x14
1023f68be7dSNeil Armstrong #define HDMITX_DWC_CTRL_REG 0x18
1033f68be7dSNeil Armstrong
1043f68be7dSNeil Armstrong /* HHI Registers */
1053f68be7dSNeil Armstrong #define HHI_MEM_PD_REG0 0x100 /* 0x40 */
1063f68be7dSNeil Armstrong #define HHI_HDMI_CLK_CNTL 0x1cc /* 0x73 */
1073f68be7dSNeil Armstrong #define HHI_HDMI_PHY_CNTL0 0x3a0 /* 0xe8 */
1083f68be7dSNeil Armstrong #define HHI_HDMI_PHY_CNTL1 0x3a4 /* 0xe9 */
109*fa2d2e2dSJerome Brunet #define PHY_CNTL1_INIT 0x03900000
110*fa2d2e2dSJerome Brunet #define PHY_INVERT BIT(17)
1113f68be7dSNeil Armstrong #define HHI_HDMI_PHY_CNTL2 0x3a8 /* 0xea */
1123f68be7dSNeil Armstrong #define HHI_HDMI_PHY_CNTL3 0x3ac /* 0xeb */
1133b7c1237SNeil Armstrong #define HHI_HDMI_PHY_CNTL4 0x3b0 /* 0xec */
1143b7c1237SNeil Armstrong #define HHI_HDMI_PHY_CNTL5 0x3b4 /* 0xed */
1153f68be7dSNeil Armstrong
1163f68be7dSNeil Armstrong static DEFINE_SPINLOCK(reg_lock);
1173f68be7dSNeil Armstrong
1183f68be7dSNeil Armstrong enum meson_venc_source {
1193f68be7dSNeil Armstrong MESON_VENC_SOURCE_NONE = 0,
1203f68be7dSNeil Armstrong MESON_VENC_SOURCE_ENCI = 1,
1213f68be7dSNeil Armstrong MESON_VENC_SOURCE_ENCP = 2,
1223f68be7dSNeil Armstrong };
1233f68be7dSNeil Armstrong
1243b7c1237SNeil Armstrong struct meson_dw_hdmi;
1253b7c1237SNeil Armstrong
1263b7c1237SNeil Armstrong struct meson_dw_hdmi_data {
1273b7c1237SNeil Armstrong unsigned int (*top_read)(struct meson_dw_hdmi *dw_hdmi,
1283b7c1237SNeil Armstrong unsigned int addr);
1293b7c1237SNeil Armstrong void (*top_write)(struct meson_dw_hdmi *dw_hdmi,
1303b7c1237SNeil Armstrong unsigned int addr, unsigned int data);
1313b7c1237SNeil Armstrong unsigned int (*dwc_read)(struct meson_dw_hdmi *dw_hdmi,
1323b7c1237SNeil Armstrong unsigned int addr);
1333b7c1237SNeil Armstrong void (*dwc_write)(struct meson_dw_hdmi *dw_hdmi,
1343b7c1237SNeil Armstrong unsigned int addr, unsigned int data);
135*fa2d2e2dSJerome Brunet u32 cntl0_init;
136*fa2d2e2dSJerome Brunet u32 cntl1_init;
1373b7c1237SNeil Armstrong };
1383b7c1237SNeil Armstrong
1393f68be7dSNeil Armstrong struct meson_dw_hdmi {
1403f68be7dSNeil Armstrong struct dw_hdmi_plat_data dw_plat_data;
1413f68be7dSNeil Armstrong struct meson_drm *priv;
1423f68be7dSNeil Armstrong struct device *dev;
1433f68be7dSNeil Armstrong void __iomem *hdmitx;
1443b7c1237SNeil Armstrong const struct meson_dw_hdmi_data *data;
1453f68be7dSNeil Armstrong struct reset_control *hdmitx_apb;
1463f68be7dSNeil Armstrong struct reset_control *hdmitx_ctrl;
1473f68be7dSNeil Armstrong struct reset_control *hdmitx_phy;
1483f68be7dSNeil Armstrong u32 irq_stat;
149eea034afSJernej Skrabec struct dw_hdmi *hdmi;
150e67f6037SNeil Armstrong struct drm_bridge *bridge;
1513f68be7dSNeil Armstrong };
1523f68be7dSNeil Armstrong
dw_hdmi_is_compatible(struct meson_dw_hdmi * dw_hdmi,const char * compat)1533f68be7dSNeil Armstrong static inline int dw_hdmi_is_compatible(struct meson_dw_hdmi *dw_hdmi,
1543f68be7dSNeil Armstrong const char *compat)
1553f68be7dSNeil Armstrong {
1563f68be7dSNeil Armstrong return of_device_is_compatible(dw_hdmi->dev->of_node, compat);
1573f68be7dSNeil Armstrong }
1583f68be7dSNeil Armstrong
1593f68be7dSNeil Armstrong /* PHY (via TOP bridge) and Controller dedicated register interface */
1603f68be7dSNeil Armstrong
dw_hdmi_top_read(struct meson_dw_hdmi * dw_hdmi,unsigned int addr)1613f68be7dSNeil Armstrong static unsigned int dw_hdmi_top_read(struct meson_dw_hdmi *dw_hdmi,
1623f68be7dSNeil Armstrong unsigned int addr)
1633f68be7dSNeil Armstrong {
1643f68be7dSNeil Armstrong unsigned long flags;
1653f68be7dSNeil Armstrong unsigned int data;
1663f68be7dSNeil Armstrong
1673f68be7dSNeil Armstrong spin_lock_irqsave(®_lock, flags);
1683f68be7dSNeil Armstrong
1693f68be7dSNeil Armstrong /* ADDR must be written twice */
1703f68be7dSNeil Armstrong writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG);
1713f68be7dSNeil Armstrong writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG);
1723f68be7dSNeil Armstrong
1733f68be7dSNeil Armstrong /* Read needs a second DATA read */
1743f68be7dSNeil Armstrong data = readl(dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG);
1753f68be7dSNeil Armstrong data = readl(dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG);
1763f68be7dSNeil Armstrong
1773f68be7dSNeil Armstrong spin_unlock_irqrestore(®_lock, flags);
1783f68be7dSNeil Armstrong
1793f68be7dSNeil Armstrong return data;
1803f68be7dSNeil Armstrong }
1813f68be7dSNeil Armstrong
dw_hdmi_g12a_top_read(struct meson_dw_hdmi * dw_hdmi,unsigned int addr)1823b7c1237SNeil Armstrong static unsigned int dw_hdmi_g12a_top_read(struct meson_dw_hdmi *dw_hdmi,
1833b7c1237SNeil Armstrong unsigned int addr)
1843b7c1237SNeil Armstrong {
1853b7c1237SNeil Armstrong return readl(dw_hdmi->hdmitx + HDMITX_TOP_G12A_OFFSET + (addr << 2));
1863b7c1237SNeil Armstrong }
1873b7c1237SNeil Armstrong
dw_hdmi_top_write(struct meson_dw_hdmi * dw_hdmi,unsigned int addr,unsigned int data)1883f68be7dSNeil Armstrong static inline void dw_hdmi_top_write(struct meson_dw_hdmi *dw_hdmi,
1893f68be7dSNeil Armstrong unsigned int addr, unsigned int data)
1903f68be7dSNeil Armstrong {
1913f68be7dSNeil Armstrong unsigned long flags;
1923f68be7dSNeil Armstrong
1933f68be7dSNeil Armstrong spin_lock_irqsave(®_lock, flags);
1943f68be7dSNeil Armstrong
1953f68be7dSNeil Armstrong /* ADDR must be written twice */
1963f68be7dSNeil Armstrong writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG);
1973f68be7dSNeil Armstrong writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG);
1983f68be7dSNeil Armstrong
1993f68be7dSNeil Armstrong /* Write needs single DATA write */
2003f68be7dSNeil Armstrong writel(data, dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG);
2013f68be7dSNeil Armstrong
2023f68be7dSNeil Armstrong spin_unlock_irqrestore(®_lock, flags);
2033f68be7dSNeil Armstrong }
2043f68be7dSNeil Armstrong
dw_hdmi_g12a_top_write(struct meson_dw_hdmi * dw_hdmi,unsigned int addr,unsigned int data)2053b7c1237SNeil Armstrong static inline void dw_hdmi_g12a_top_write(struct meson_dw_hdmi *dw_hdmi,
2063b7c1237SNeil Armstrong unsigned int addr, unsigned int data)
2073b7c1237SNeil Armstrong {
2083b7c1237SNeil Armstrong writel(data, dw_hdmi->hdmitx + HDMITX_TOP_G12A_OFFSET + (addr << 2));
2093b7c1237SNeil Armstrong }
2103b7c1237SNeil Armstrong
2113f68be7dSNeil Armstrong /* Helper to change specific bits in PHY registers */
dw_hdmi_top_write_bits(struct meson_dw_hdmi * dw_hdmi,unsigned int addr,unsigned int mask,unsigned int val)2123f68be7dSNeil Armstrong static inline void dw_hdmi_top_write_bits(struct meson_dw_hdmi *dw_hdmi,
2133f68be7dSNeil Armstrong unsigned int addr,
2143f68be7dSNeil Armstrong unsigned int mask,
2153f68be7dSNeil Armstrong unsigned int val)
2163f68be7dSNeil Armstrong {
2173b7c1237SNeil Armstrong unsigned int data = dw_hdmi->data->top_read(dw_hdmi, addr);
2183f68be7dSNeil Armstrong
2193f68be7dSNeil Armstrong data &= ~mask;
2203f68be7dSNeil Armstrong data |= val;
2213f68be7dSNeil Armstrong
2223b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, addr, data);
2233f68be7dSNeil Armstrong }
2243f68be7dSNeil Armstrong
dw_hdmi_dwc_read(struct meson_dw_hdmi * dw_hdmi,unsigned int addr)2253f68be7dSNeil Armstrong static unsigned int dw_hdmi_dwc_read(struct meson_dw_hdmi *dw_hdmi,
2263f68be7dSNeil Armstrong unsigned int addr)
2273f68be7dSNeil Armstrong {
2283f68be7dSNeil Armstrong unsigned long flags;
2293f68be7dSNeil Armstrong unsigned int data;
2303f68be7dSNeil Armstrong
2313f68be7dSNeil Armstrong spin_lock_irqsave(®_lock, flags);
2323f68be7dSNeil Armstrong
2333f68be7dSNeil Armstrong /* ADDR must be written twice */
2343f68be7dSNeil Armstrong writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG);
2353f68be7dSNeil Armstrong writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG);
2363f68be7dSNeil Armstrong
2373f68be7dSNeil Armstrong /* Read needs a second DATA read */
2383f68be7dSNeil Armstrong data = readl(dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG);
2393f68be7dSNeil Armstrong data = readl(dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG);
2403f68be7dSNeil Armstrong
2413f68be7dSNeil Armstrong spin_unlock_irqrestore(®_lock, flags);
2423f68be7dSNeil Armstrong
2433f68be7dSNeil Armstrong return data;
2443f68be7dSNeil Armstrong }
2453f68be7dSNeil Armstrong
dw_hdmi_g12a_dwc_read(struct meson_dw_hdmi * dw_hdmi,unsigned int addr)2463b7c1237SNeil Armstrong static unsigned int dw_hdmi_g12a_dwc_read(struct meson_dw_hdmi *dw_hdmi,
2473b7c1237SNeil Armstrong unsigned int addr)
2483b7c1237SNeil Armstrong {
2493b7c1237SNeil Armstrong return readb(dw_hdmi->hdmitx + addr);
2503b7c1237SNeil Armstrong }
2513b7c1237SNeil Armstrong
dw_hdmi_dwc_write(struct meson_dw_hdmi * dw_hdmi,unsigned int addr,unsigned int data)2523f68be7dSNeil Armstrong static inline void dw_hdmi_dwc_write(struct meson_dw_hdmi *dw_hdmi,
2533f68be7dSNeil Armstrong unsigned int addr, unsigned int data)
2543f68be7dSNeil Armstrong {
2553f68be7dSNeil Armstrong unsigned long flags;
2563f68be7dSNeil Armstrong
2573f68be7dSNeil Armstrong spin_lock_irqsave(®_lock, flags);
2583f68be7dSNeil Armstrong
2593f68be7dSNeil Armstrong /* ADDR must be written twice */
2603f68be7dSNeil Armstrong writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG);
2613f68be7dSNeil Armstrong writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG);
2623f68be7dSNeil Armstrong
2633f68be7dSNeil Armstrong /* Write needs single DATA write */
2643f68be7dSNeil Armstrong writel(data, dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG);
2653f68be7dSNeil Armstrong
2663f68be7dSNeil Armstrong spin_unlock_irqrestore(®_lock, flags);
2673f68be7dSNeil Armstrong }
2683f68be7dSNeil Armstrong
dw_hdmi_g12a_dwc_write(struct meson_dw_hdmi * dw_hdmi,unsigned int addr,unsigned int data)2693b7c1237SNeil Armstrong static inline void dw_hdmi_g12a_dwc_write(struct meson_dw_hdmi *dw_hdmi,
2703b7c1237SNeil Armstrong unsigned int addr, unsigned int data)
2713b7c1237SNeil Armstrong {
2723b7c1237SNeil Armstrong writeb(data, dw_hdmi->hdmitx + addr);
2733b7c1237SNeil Armstrong }
2743b7c1237SNeil Armstrong
2753f68be7dSNeil Armstrong /* Helper to change specific bits in controller registers */
dw_hdmi_dwc_write_bits(struct meson_dw_hdmi * dw_hdmi,unsigned int addr,unsigned int mask,unsigned int val)2763f68be7dSNeil Armstrong static inline void dw_hdmi_dwc_write_bits(struct meson_dw_hdmi *dw_hdmi,
2773f68be7dSNeil Armstrong unsigned int addr,
2783f68be7dSNeil Armstrong unsigned int mask,
2793f68be7dSNeil Armstrong unsigned int val)
2803f68be7dSNeil Armstrong {
2813b7c1237SNeil Armstrong unsigned int data = dw_hdmi->data->dwc_read(dw_hdmi, addr);
2823f68be7dSNeil Armstrong
2833f68be7dSNeil Armstrong data &= ~mask;
2843f68be7dSNeil Armstrong data |= val;
2853f68be7dSNeil Armstrong
2863b7c1237SNeil Armstrong dw_hdmi->data->dwc_write(dw_hdmi, addr, data);
2873f68be7dSNeil Armstrong }
2883f68be7dSNeil Armstrong
2893f68be7dSNeil Armstrong /* Bridge */
2903f68be7dSNeil Armstrong
2913f68be7dSNeil Armstrong /* Setup PHY bandwidth modes */
meson_hdmi_phy_setup_mode(struct meson_dw_hdmi * dw_hdmi,const struct drm_display_mode * mode,bool mode_is_420)2923f68be7dSNeil Armstrong static void meson_hdmi_phy_setup_mode(struct meson_dw_hdmi *dw_hdmi,
293e67f6037SNeil Armstrong const struct drm_display_mode *mode,
294e67f6037SNeil Armstrong bool mode_is_420)
2953f68be7dSNeil Armstrong {
2963f68be7dSNeil Armstrong struct meson_drm *priv = dw_hdmi->priv;
2973f68be7dSNeil Armstrong unsigned int pixel_clock = mode->clock;
2983f68be7dSNeil Armstrong
2998496a217SNeil Armstrong /* For 420, pixel clock is half unlike venc clock */
300e67f6037SNeil Armstrong if (mode_is_420) pixel_clock /= 2;
3018496a217SNeil Armstrong
3023f68be7dSNeil Armstrong if (dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxl-dw-hdmi") ||
3033f68be7dSNeil Armstrong dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxm-dw-hdmi")) {
3043f68be7dSNeil Armstrong if (pixel_clock >= 371250) {
3053f68be7dSNeil Armstrong /* 5.94Gbps, 3.7125Gbps */
3063f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x333d3282);
3073f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2136315b);
3083f68be7dSNeil Armstrong } else if (pixel_clock >= 297000) {
3093f68be7dSNeil Armstrong /* 2.97Gbps */
3103f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33303382);
3113f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2036315b);
3123f68be7dSNeil Armstrong } else if (pixel_clock >= 148500) {
3133f68be7dSNeil Armstrong /* 1.485Gbps */
3143f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33303362);
3153f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2016315b);
3163f68be7dSNeil Armstrong } else {
3173f68be7dSNeil Armstrong /* 742.5Mbps, and below */
3183f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33604142);
3193f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x0016315b);
3203f68be7dSNeil Armstrong }
3213f68be7dSNeil Armstrong } else if (dw_hdmi_is_compatible(dw_hdmi,
3223f68be7dSNeil Armstrong "amlogic,meson-gxbb-dw-hdmi")) {
3233f68be7dSNeil Armstrong if (pixel_clock >= 371250) {
3243f68be7dSNeil Armstrong /* 5.94Gbps, 3.7125Gbps */
3253f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33353245);
3263f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2100115b);
3273f68be7dSNeil Armstrong } else if (pixel_clock >= 297000) {
3283f68be7dSNeil Armstrong /* 2.97Gbps */
3293f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33634283);
3303f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0xb000115b);
3313f68be7dSNeil Armstrong } else {
3323f68be7dSNeil Armstrong /* 1.485Gbps, and below */
3333f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33632122);
3343f68be7dSNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2000115b);
3353f68be7dSNeil Armstrong }
3363b7c1237SNeil Armstrong } else if (dw_hdmi_is_compatible(dw_hdmi,
3373b7c1237SNeil Armstrong "amlogic,meson-g12a-dw-hdmi")) {
3383b7c1237SNeil Armstrong if (pixel_clock >= 371250) {
3393b7c1237SNeil Armstrong /* 5.94Gbps, 3.7125Gbps */
3403b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x37eb65c4);
3413b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b);
3423b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x0000080b);
3433b7c1237SNeil Armstrong } else if (pixel_clock >= 297000) {
3443b7c1237SNeil Armstrong /* 2.97Gbps */
3453b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33eb6262);
3463b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b);
3473b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x00000003);
3483b7c1237SNeil Armstrong } else {
3493b7c1237SNeil Armstrong /* 1.485Gbps, and below */
3503b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33eb4242);
3513b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b);
3523b7c1237SNeil Armstrong regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x00000003);
3533b7c1237SNeil Armstrong }
3543f68be7dSNeil Armstrong }
3553f68be7dSNeil Armstrong }
3563f68be7dSNeil Armstrong
meson_dw_hdmi_phy_reset(struct meson_dw_hdmi * dw_hdmi)3575765916aSJernej Skrabec static inline void meson_dw_hdmi_phy_reset(struct meson_dw_hdmi *dw_hdmi)
3583f68be7dSNeil Armstrong {
3593f68be7dSNeil Armstrong struct meson_drm *priv = dw_hdmi->priv;
3603f68be7dSNeil Armstrong
3613f68be7dSNeil Armstrong /* Enable and software reset */
3623f68be7dSNeil Armstrong regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0xf);
3633f68be7dSNeil Armstrong
3643f68be7dSNeil Armstrong mdelay(2);
3653f68be7dSNeil Armstrong
3663f68be7dSNeil Armstrong /* Enable and unreset */
3673f68be7dSNeil Armstrong regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0xe);
3683f68be7dSNeil Armstrong
3693f68be7dSNeil Armstrong mdelay(2);
3703f68be7dSNeil Armstrong }
3713f68be7dSNeil Armstrong
dw_hdmi_phy_init(struct dw_hdmi * hdmi,void * data,const struct drm_display_info * display,const struct drm_display_mode * mode)3723f68be7dSNeil Armstrong static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data,
3737be390d4SLaurent Pinchart const struct drm_display_info *display,
37435a395f1SLaurent Pinchart const struct drm_display_mode *mode)
3753f68be7dSNeil Armstrong {
3763f68be7dSNeil Armstrong struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data;
377e67f6037SNeil Armstrong bool is_hdmi2_sink = display->hdmi.scdc.supported;
3783f68be7dSNeil Armstrong struct meson_drm *priv = dw_hdmi->priv;
3793f68be7dSNeil Armstrong unsigned int wr_clk =
3803f68be7dSNeil Armstrong readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING));
381e67f6037SNeil Armstrong bool mode_is_420 = false;
3823f68be7dSNeil Armstrong
383d7d8fb70SNeil Armstrong DRM_DEBUG_DRIVER("\"%s\" div%d\n", mode->name,
384d7d8fb70SNeil Armstrong mode->clock > 340000 ? 40 : 10);
3853f68be7dSNeil Armstrong
386e67f6037SNeil Armstrong if (drm_mode_is_420_only(display, mode) ||
3877ed40ff1SAdrián Larumbe (!is_hdmi2_sink && drm_mode_is_420_also(display, mode)) ||
3887ed40ff1SAdrián Larumbe dw_hdmi_bus_fmt_is_420(hdmi))
389e67f6037SNeil Armstrong mode_is_420 = true;
390e67f6037SNeil Armstrong
3918496a217SNeil Armstrong /* TMDS pattern setup */
392e67f6037SNeil Armstrong if (mode->clock > 340000 && !mode_is_420) {
3933b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01,
3943b7c1237SNeil Armstrong 0);
3953b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23,
396d7d8fb70SNeil Armstrong 0x03ff03ff);
397d7d8fb70SNeil Armstrong } else {
3983b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01,
399d7d8fb70SNeil Armstrong 0x001f001f);
4003b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23,
401d7d8fb70SNeil Armstrong 0x001f001f);
402d7d8fb70SNeil Armstrong }
4033f68be7dSNeil Armstrong
4043f68be7dSNeil Armstrong /* Load TMDS pattern */
4053b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1);
4063f68be7dSNeil Armstrong msleep(20);
4073b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2);
4083f68be7dSNeil Armstrong
4093f68be7dSNeil Armstrong /* Setup PHY parameters */
410e67f6037SNeil Armstrong meson_hdmi_phy_setup_mode(dw_hdmi, mode, mode_is_420);
4113f68be7dSNeil Armstrong
4123f68be7dSNeil Armstrong /* Disable clock, fifo, fifo_wr */
4133f68be7dSNeil Armstrong regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0);
4143f68be7dSNeil Armstrong
4157be390d4SLaurent Pinchart dw_hdmi_set_high_tmds_clock_ratio(hdmi, display);
416d7d8fb70SNeil Armstrong
4173f68be7dSNeil Armstrong msleep(100);
4183f68be7dSNeil Armstrong
4193f68be7dSNeil Armstrong /* Reset PHY 3 times in a row */
4205765916aSJernej Skrabec meson_dw_hdmi_phy_reset(dw_hdmi);
4215765916aSJernej Skrabec meson_dw_hdmi_phy_reset(dw_hdmi);
4225765916aSJernej Skrabec meson_dw_hdmi_phy_reset(dw_hdmi);
4233f68be7dSNeil Armstrong
4243f68be7dSNeil Armstrong /* Temporary Disable VENC video stream */
4253f68be7dSNeil Armstrong if (priv->venc.hdmi_use_enci)
4263f68be7dSNeil Armstrong writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN));
4273f68be7dSNeil Armstrong else
4283f68be7dSNeil Armstrong writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN));
4293f68be7dSNeil Armstrong
4303f68be7dSNeil Armstrong /* Temporary Disable HDMI video stream to HDMI-TX */
4313f68be7dSNeil Armstrong writel_bits_relaxed(0x3, 0,
4323f68be7dSNeil Armstrong priv->io_base + _REG(VPU_HDMI_SETTING));
4333f68be7dSNeil Armstrong writel_bits_relaxed(0xf << 8, 0,
4343f68be7dSNeil Armstrong priv->io_base + _REG(VPU_HDMI_SETTING));
4353f68be7dSNeil Armstrong
4363f68be7dSNeil Armstrong /* Re-Enable VENC video stream */
4373f68be7dSNeil Armstrong if (priv->venc.hdmi_use_enci)
4383f68be7dSNeil Armstrong writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN));
4393f68be7dSNeil Armstrong else
4403f68be7dSNeil Armstrong writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN));
4413f68be7dSNeil Armstrong
4423f68be7dSNeil Armstrong /* Push back HDMI clock settings */
4433f68be7dSNeil Armstrong writel_bits_relaxed(0xf << 8, wr_clk & (0xf << 8),
4443f68be7dSNeil Armstrong priv->io_base + _REG(VPU_HDMI_SETTING));
4453f68be7dSNeil Armstrong
4463f68be7dSNeil Armstrong /* Enable and Select HDMI video source for HDMI-TX */
4473f68be7dSNeil Armstrong if (priv->venc.hdmi_use_enci)
4483f68be7dSNeil Armstrong writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCI,
4493f68be7dSNeil Armstrong priv->io_base + _REG(VPU_HDMI_SETTING));
4503f68be7dSNeil Armstrong else
4513f68be7dSNeil Armstrong writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCP,
4523f68be7dSNeil Armstrong priv->io_base + _REG(VPU_HDMI_SETTING));
4533f68be7dSNeil Armstrong
4543f68be7dSNeil Armstrong return 0;
4553f68be7dSNeil Armstrong }
4563f68be7dSNeil Armstrong
dw_hdmi_phy_disable(struct dw_hdmi * hdmi,void * data)4573f68be7dSNeil Armstrong static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi,
4583f68be7dSNeil Armstrong void *data)
4593f68be7dSNeil Armstrong {
4603f68be7dSNeil Armstrong struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data;
4613f68be7dSNeil Armstrong struct meson_drm *priv = dw_hdmi->priv;
4623f68be7dSNeil Armstrong
4633f68be7dSNeil Armstrong DRM_DEBUG_DRIVER("\n");
4643f68be7dSNeil Armstrong
465*fa2d2e2dSJerome Brunet /* Fallback to init mode */
466*fa2d2e2dSJerome Brunet regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, dw_hdmi->data->cntl1_init);
467*fa2d2e2dSJerome Brunet regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, dw_hdmi->data->cntl0_init);
4683f68be7dSNeil Armstrong }
4693f68be7dSNeil Armstrong
dw_hdmi_read_hpd(struct dw_hdmi * hdmi,void * data)4703f68be7dSNeil Armstrong static enum drm_connector_status dw_hdmi_read_hpd(struct dw_hdmi *hdmi,
4713f68be7dSNeil Armstrong void *data)
4723f68be7dSNeil Armstrong {
4733f68be7dSNeil Armstrong struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data;
4743f68be7dSNeil Armstrong
4753b7c1237SNeil Armstrong return !!dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_STAT0) ?
4763f68be7dSNeil Armstrong connector_status_connected : connector_status_disconnected;
4773f68be7dSNeil Armstrong }
4783f68be7dSNeil Armstrong
dw_hdmi_setup_hpd(struct dw_hdmi * hdmi,void * data)4793f68be7dSNeil Armstrong static void dw_hdmi_setup_hpd(struct dw_hdmi *hdmi,
4803f68be7dSNeil Armstrong void *data)
4813f68be7dSNeil Armstrong {
4823f68be7dSNeil Armstrong struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data;
4833f68be7dSNeil Armstrong
4843f68be7dSNeil Armstrong /* Setup HPD Filter */
4853b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_HPD_FILTER,
4863f68be7dSNeil Armstrong (0xa << 12) | 0xa0);
4873f68be7dSNeil Armstrong
4883f68be7dSNeil Armstrong /* Clear interrupts */
4893b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR,
4903f68be7dSNeil Armstrong HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL);
4913f68be7dSNeil Armstrong
4923f68be7dSNeil Armstrong /* Unmask interrupts */
4933f68be7dSNeil Armstrong dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_INTR_MASKN,
4943f68be7dSNeil Armstrong HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL,
4953f68be7dSNeil Armstrong HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL);
4963f68be7dSNeil Armstrong }
4973f68be7dSNeil Armstrong
4983f68be7dSNeil Armstrong static const struct dw_hdmi_phy_ops meson_dw_hdmi_phy_ops = {
4993f68be7dSNeil Armstrong .init = dw_hdmi_phy_init,
5003f68be7dSNeil Armstrong .disable = dw_hdmi_phy_disable,
5013f68be7dSNeil Armstrong .read_hpd = dw_hdmi_read_hpd,
5023f68be7dSNeil Armstrong .setup_hpd = dw_hdmi_setup_hpd,
5033f68be7dSNeil Armstrong };
5043f68be7dSNeil Armstrong
dw_hdmi_top_irq(int irq,void * dev_id)5053f68be7dSNeil Armstrong static irqreturn_t dw_hdmi_top_irq(int irq, void *dev_id)
5063f68be7dSNeil Armstrong {
5073f68be7dSNeil Armstrong struct meson_dw_hdmi *dw_hdmi = dev_id;
5083f68be7dSNeil Armstrong u32 stat;
5093f68be7dSNeil Armstrong
5103b7c1237SNeil Armstrong stat = dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_INTR_STAT);
5113b7c1237SNeil Armstrong dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, stat);
5123f68be7dSNeil Armstrong
5133f68be7dSNeil Armstrong /* HPD Events, handle in the threaded interrupt handler */
5143f68be7dSNeil Armstrong if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) {
5153f68be7dSNeil Armstrong dw_hdmi->irq_stat = stat;
5163f68be7dSNeil Armstrong return IRQ_WAKE_THREAD;
5173f68be7dSNeil Armstrong }
5183f68be7dSNeil Armstrong
5193f68be7dSNeil Armstrong /* HDMI Controller Interrupt */
5203f68be7dSNeil Armstrong if (stat & 1)
5213f68be7dSNeil Armstrong return IRQ_NONE;
5223f68be7dSNeil Armstrong
5233f68be7dSNeil Armstrong /* TOFIX Handle HDCP Interrupts */
5243f68be7dSNeil Armstrong
5253f68be7dSNeil Armstrong return IRQ_HANDLED;
5263f68be7dSNeil Armstrong }
5273f68be7dSNeil Armstrong
5283f68be7dSNeil Armstrong /* Threaded interrupt handler to manage HPD events */
dw_hdmi_top_thread_irq(int irq,void * dev_id)5293f68be7dSNeil Armstrong static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id)
5303f68be7dSNeil Armstrong {
5313f68be7dSNeil Armstrong struct meson_dw_hdmi *dw_hdmi = dev_id;
5323f68be7dSNeil Armstrong u32 stat = dw_hdmi->irq_stat;
5333f68be7dSNeil Armstrong
5343f68be7dSNeil Armstrong /* HPD Events */
5353f68be7dSNeil Armstrong if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) {
5363f68be7dSNeil Armstrong bool hpd_connected = false;
5373f68be7dSNeil Armstrong
5383f68be7dSNeil Armstrong if (stat & HDMITX_TOP_INTR_HPD_RISE)
5393f68be7dSNeil Armstrong hpd_connected = true;
5403f68be7dSNeil Armstrong
541c32048d9SNeil Armstrong dw_hdmi_setup_rx_sense(dw_hdmi->hdmi, hpd_connected,
5423f68be7dSNeil Armstrong hpd_connected);
5433f68be7dSNeil Armstrong
544e67f6037SNeil Armstrong drm_helper_hpd_irq_event(dw_hdmi->bridge->dev);
545e67f6037SNeil Armstrong drm_bridge_hpd_notify(dw_hdmi->bridge,
546e67f6037SNeil Armstrong hpd_connected ? connector_status_connected
547e67f6037SNeil Armstrong : connector_status_disconnected);
5483f68be7dSNeil Armstrong }
5493f68be7dSNeil Armstrong
5503f68be7dSNeil Armstrong return IRQ_HANDLED;
5513f68be7dSNeil Armstrong }
5523f68be7dSNeil Armstrong
5533f68be7dSNeil Armstrong /* DW HDMI Regmap */
5543f68be7dSNeil Armstrong
meson_dw_hdmi_reg_read(void * context,unsigned int reg,unsigned int * result)5553f68be7dSNeil Armstrong static int meson_dw_hdmi_reg_read(void *context, unsigned int reg,
5563f68be7dSNeil Armstrong unsigned int *result)
5573f68be7dSNeil Armstrong {
5583b7c1237SNeil Armstrong struct meson_dw_hdmi *dw_hdmi = context;
5593b7c1237SNeil Armstrong
5603b7c1237SNeil Armstrong *result = dw_hdmi->data->dwc_read(dw_hdmi, reg);
5613f68be7dSNeil Armstrong
5623f68be7dSNeil Armstrong return 0;
5633f68be7dSNeil Armstrong
5643f68be7dSNeil Armstrong }
5653f68be7dSNeil Armstrong
meson_dw_hdmi_reg_write(void * context,unsigned int reg,unsigned int val)5663f68be7dSNeil Armstrong static int meson_dw_hdmi_reg_write(void *context, unsigned int reg,
5673f68be7dSNeil Armstrong unsigned int val)
5683f68be7dSNeil Armstrong {
5693b7c1237SNeil Armstrong struct meson_dw_hdmi *dw_hdmi = context;
5703b7c1237SNeil Armstrong
5713b7c1237SNeil Armstrong dw_hdmi->data->dwc_write(dw_hdmi, reg, val);
5723f68be7dSNeil Armstrong
5733f68be7dSNeil Armstrong return 0;
5743f68be7dSNeil Armstrong }
5753f68be7dSNeil Armstrong
5763f68be7dSNeil Armstrong static const struct regmap_config meson_dw_hdmi_regmap_config = {
5773f68be7dSNeil Armstrong .reg_bits = 32,
5783f68be7dSNeil Armstrong .val_bits = 8,
5793f68be7dSNeil Armstrong .reg_read = meson_dw_hdmi_reg_read,
5803f68be7dSNeil Armstrong .reg_write = meson_dw_hdmi_reg_write,
5813f68be7dSNeil Armstrong .max_register = 0x10000,
582995b278eSLyude Paul .fast_io = true,
5833f68be7dSNeil Armstrong };
5843f68be7dSNeil Armstrong
585*fa2d2e2dSJerome Brunet static const struct meson_dw_hdmi_data meson_dw_hdmi_gxbb_data = {
5863b7c1237SNeil Armstrong .top_read = dw_hdmi_top_read,
5873b7c1237SNeil Armstrong .top_write = dw_hdmi_top_write,
5883b7c1237SNeil Armstrong .dwc_read = dw_hdmi_dwc_read,
5893b7c1237SNeil Armstrong .dwc_write = dw_hdmi_dwc_write,
590*fa2d2e2dSJerome Brunet .cntl0_init = 0x0,
591*fa2d2e2dSJerome Brunet .cntl1_init = PHY_CNTL1_INIT | PHY_INVERT,
592*fa2d2e2dSJerome Brunet };
593*fa2d2e2dSJerome Brunet
594*fa2d2e2dSJerome Brunet static const struct meson_dw_hdmi_data meson_dw_hdmi_gxl_data = {
595*fa2d2e2dSJerome Brunet .top_read = dw_hdmi_top_read,
596*fa2d2e2dSJerome Brunet .top_write = dw_hdmi_top_write,
597*fa2d2e2dSJerome Brunet .dwc_read = dw_hdmi_dwc_read,
598*fa2d2e2dSJerome Brunet .dwc_write = dw_hdmi_dwc_write,
599*fa2d2e2dSJerome Brunet .cntl0_init = 0x0,
600*fa2d2e2dSJerome Brunet .cntl1_init = PHY_CNTL1_INIT,
6013b7c1237SNeil Armstrong };
6023b7c1237SNeil Armstrong
6033b7c1237SNeil Armstrong static const struct meson_dw_hdmi_data meson_dw_hdmi_g12a_data = {
6043b7c1237SNeil Armstrong .top_read = dw_hdmi_g12a_top_read,
6053b7c1237SNeil Armstrong .top_write = dw_hdmi_g12a_top_write,
6063b7c1237SNeil Armstrong .dwc_read = dw_hdmi_g12a_dwc_read,
6073b7c1237SNeil Armstrong .dwc_write = dw_hdmi_g12a_dwc_write,
608*fa2d2e2dSJerome Brunet .cntl0_init = 0x000b4242, /* Bandgap */
609*fa2d2e2dSJerome Brunet .cntl1_init = PHY_CNTL1_INIT,
6103b7c1237SNeil Armstrong };
6113b7c1237SNeil Armstrong
meson_dw_hdmi_init(struct meson_dw_hdmi * meson_dw_hdmi)6121374b837SNeil Armstrong static void meson_dw_hdmi_init(struct meson_dw_hdmi *meson_dw_hdmi)
6131374b837SNeil Armstrong {
6141374b837SNeil Armstrong struct meson_drm *priv = meson_dw_hdmi->priv;
6151374b837SNeil Armstrong
6161374b837SNeil Armstrong /* Enable clocks */
6171374b837SNeil Armstrong regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, 0xffff, 0x100);
6181374b837SNeil Armstrong
6191374b837SNeil Armstrong /* Bring HDMITX MEM output of power down */
6201374b837SNeil Armstrong regmap_update_bits(priv->hhi, HHI_MEM_PD_REG0, 0xff << 8, 0);
6211374b837SNeil Armstrong
6221374b837SNeil Armstrong /* Reset HDMITX APB & TX & PHY */
6231374b837SNeil Armstrong reset_control_reset(meson_dw_hdmi->hdmitx_apb);
6241374b837SNeil Armstrong reset_control_reset(meson_dw_hdmi->hdmitx_ctrl);
6251374b837SNeil Armstrong reset_control_reset(meson_dw_hdmi->hdmitx_phy);
6261374b837SNeil Armstrong
6271374b837SNeil Armstrong /* Enable APB3 fail on error */
6281374b837SNeil Armstrong if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {
6291374b837SNeil Armstrong writel_bits_relaxed(BIT(15), BIT(15),
6301374b837SNeil Armstrong meson_dw_hdmi->hdmitx + HDMITX_TOP_CTRL_REG);
6311374b837SNeil Armstrong writel_bits_relaxed(BIT(15), BIT(15),
6321374b837SNeil Armstrong meson_dw_hdmi->hdmitx + HDMITX_DWC_CTRL_REG);
6331374b837SNeil Armstrong }
6341374b837SNeil Armstrong
6351374b837SNeil Armstrong /* Bring out of reset */
6361374b837SNeil Armstrong meson_dw_hdmi->data->top_write(meson_dw_hdmi,
6371374b837SNeil Armstrong HDMITX_TOP_SW_RESET, 0);
6381374b837SNeil Armstrong
6391374b837SNeil Armstrong msleep(20);
6401374b837SNeil Armstrong
6411374b837SNeil Armstrong meson_dw_hdmi->data->top_write(meson_dw_hdmi,
6421374b837SNeil Armstrong HDMITX_TOP_CLK_CNTL, 0xff);
6431374b837SNeil Armstrong
644dbe2cf9fSJerome Brunet /* Enable normal output to PHY */
645dbe2cf9fSJerome Brunet meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12));
646dbe2cf9fSJerome Brunet
647dbe2cf9fSJerome Brunet /* Setup PHY */
648*fa2d2e2dSJerome Brunet regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, meson_dw_hdmi->data->cntl1_init);
649*fa2d2e2dSJerome Brunet regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, meson_dw_hdmi->data->cntl0_init);
650dbe2cf9fSJerome Brunet
6511374b837SNeil Armstrong /* Enable HDMI-TX Interrupt */
6521374b837SNeil Armstrong meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_INTR_STAT_CLR,
6531374b837SNeil Armstrong HDMITX_TOP_INTR_CORE);
6541374b837SNeil Armstrong
6551374b837SNeil Armstrong meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_INTR_MASKN,
6561374b837SNeil Armstrong HDMITX_TOP_INTR_CORE);
6571374b837SNeil Armstrong
6581374b837SNeil Armstrong }
6591374b837SNeil Armstrong
meson_disable_clk(void * data)6601dfeea90SMarc Zyngier static void meson_disable_clk(void *data)
6611dfeea90SMarc Zyngier {
6621dfeea90SMarc Zyngier clk_disable_unprepare(data);
6631dfeea90SMarc Zyngier }
6641dfeea90SMarc Zyngier
meson_enable_clk(struct device * dev,char * name)6651dfeea90SMarc Zyngier static int meson_enable_clk(struct device *dev, char *name)
6661dfeea90SMarc Zyngier {
6671dfeea90SMarc Zyngier struct clk *clk;
6681dfeea90SMarc Zyngier int ret;
6691dfeea90SMarc Zyngier
6701dfeea90SMarc Zyngier clk = devm_clk_get(dev, name);
6711dfeea90SMarc Zyngier if (IS_ERR(clk)) {
6721dfeea90SMarc Zyngier dev_err(dev, "Unable to get %s pclk\n", name);
6731dfeea90SMarc Zyngier return PTR_ERR(clk);
6741dfeea90SMarc Zyngier }
6751dfeea90SMarc Zyngier
6761dfeea90SMarc Zyngier ret = clk_prepare_enable(clk);
6771dfeea90SMarc Zyngier if (!ret)
6781dfeea90SMarc Zyngier ret = devm_add_action_or_reset(dev, meson_disable_clk, clk);
6791dfeea90SMarc Zyngier
6801dfeea90SMarc Zyngier return ret;
6811dfeea90SMarc Zyngier }
6821dfeea90SMarc Zyngier
meson_dw_hdmi_bind(struct device * dev,struct device * master,void * data)6833f68be7dSNeil Armstrong static int meson_dw_hdmi_bind(struct device *dev, struct device *master,
6843f68be7dSNeil Armstrong void *data)
6853f68be7dSNeil Armstrong {
6863f68be7dSNeil Armstrong struct platform_device *pdev = to_platform_device(dev);
6873b7c1237SNeil Armstrong const struct meson_dw_hdmi_data *match;
6883f68be7dSNeil Armstrong struct meson_dw_hdmi *meson_dw_hdmi;
6893f68be7dSNeil Armstrong struct drm_device *drm = data;
6903f68be7dSNeil Armstrong struct meson_drm *priv = drm->dev_private;
6913f68be7dSNeil Armstrong struct dw_hdmi_plat_data *dw_plat_data;
6923f68be7dSNeil Armstrong int irq;
6933f68be7dSNeil Armstrong int ret;
6943f68be7dSNeil Armstrong
6953f68be7dSNeil Armstrong DRM_DEBUG_DRIVER("\n");
6963f68be7dSNeil Armstrong
6973b7c1237SNeil Armstrong match = of_device_get_match_data(&pdev->dev);
6983b7c1237SNeil Armstrong if (!match) {
6993b7c1237SNeil Armstrong dev_err(&pdev->dev, "failed to get match data\n");
7003b7c1237SNeil Armstrong return -ENODEV;
7013b7c1237SNeil Armstrong }
7023b7c1237SNeil Armstrong
7033f68be7dSNeil Armstrong meson_dw_hdmi = devm_kzalloc(dev, sizeof(*meson_dw_hdmi),
7043f68be7dSNeil Armstrong GFP_KERNEL);
7053f68be7dSNeil Armstrong if (!meson_dw_hdmi)
7063f68be7dSNeil Armstrong return -ENOMEM;
7073f68be7dSNeil Armstrong
7083f68be7dSNeil Armstrong meson_dw_hdmi->priv = priv;
7093f68be7dSNeil Armstrong meson_dw_hdmi->dev = dev;
7103b7c1237SNeil Armstrong meson_dw_hdmi->data = match;
7113f68be7dSNeil Armstrong dw_plat_data = &meson_dw_hdmi->dw_plat_data;
7123f68be7dSNeil Armstrong
713429e8706SMatti Vaittinen ret = devm_regulator_get_enable_optional(dev, "hdmi");
7144028cbf8SMarek Szyprowski if (ret < 0 && ret != -ENODEV)
715161a803fSNeil Armstrong return ret;
716161a803fSNeil Armstrong
7173f68be7dSNeil Armstrong meson_dw_hdmi->hdmitx_apb = devm_reset_control_get_exclusive(dev,
7183f68be7dSNeil Armstrong "hdmitx_apb");
7193f68be7dSNeil Armstrong if (IS_ERR(meson_dw_hdmi->hdmitx_apb)) {
7203f68be7dSNeil Armstrong dev_err(dev, "Failed to get hdmitx_apb reset\n");
7213f68be7dSNeil Armstrong return PTR_ERR(meson_dw_hdmi->hdmitx_apb);
7223f68be7dSNeil Armstrong }
7233f68be7dSNeil Armstrong
7243f68be7dSNeil Armstrong meson_dw_hdmi->hdmitx_ctrl = devm_reset_control_get_exclusive(dev,
7253f68be7dSNeil Armstrong "hdmitx");
7263f68be7dSNeil Armstrong if (IS_ERR(meson_dw_hdmi->hdmitx_ctrl)) {
7273f68be7dSNeil Armstrong dev_err(dev, "Failed to get hdmitx reset\n");
7283f68be7dSNeil Armstrong return PTR_ERR(meson_dw_hdmi->hdmitx_ctrl);
7293f68be7dSNeil Armstrong }
7303f68be7dSNeil Armstrong
7313f68be7dSNeil Armstrong meson_dw_hdmi->hdmitx_phy = devm_reset_control_get_exclusive(dev,
7323f68be7dSNeil Armstrong "hdmitx_phy");
7333f68be7dSNeil Armstrong if (IS_ERR(meson_dw_hdmi->hdmitx_phy)) {
7343f68be7dSNeil Armstrong dev_err(dev, "Failed to get hdmitx_phy reset\n");
7353f68be7dSNeil Armstrong return PTR_ERR(meson_dw_hdmi->hdmitx_phy);
7363f68be7dSNeil Armstrong }
7373f68be7dSNeil Armstrong
738d4cb82aaSCai Huoqing meson_dw_hdmi->hdmitx = devm_platform_ioremap_resource(pdev, 0);
7393f68be7dSNeil Armstrong if (IS_ERR(meson_dw_hdmi->hdmitx))
7403f68be7dSNeil Armstrong return PTR_ERR(meson_dw_hdmi->hdmitx);
7413f68be7dSNeil Armstrong
7421dfeea90SMarc Zyngier ret = meson_enable_clk(dev, "isfr");
7431dfeea90SMarc Zyngier if (ret)
7441dfeea90SMarc Zyngier return ret;
7453f68be7dSNeil Armstrong
7462b6cb81bSMarc Zyngier ret = meson_enable_clk(dev, "iahb");
7472b6cb81bSMarc Zyngier if (ret)
7482b6cb81bSMarc Zyngier return ret;
7492b6cb81bSMarc Zyngier
7501dfeea90SMarc Zyngier ret = meson_enable_clk(dev, "venci");
7511dfeea90SMarc Zyngier if (ret)
7521dfeea90SMarc Zyngier return ret;
7533f68be7dSNeil Armstrong
7543f68be7dSNeil Armstrong dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi,
7553f68be7dSNeil Armstrong &meson_dw_hdmi_regmap_config);
7563f68be7dSNeil Armstrong if (IS_ERR(dw_plat_data->regm))
7573f68be7dSNeil Armstrong return PTR_ERR(dw_plat_data->regm);
7583f68be7dSNeil Armstrong
7593f68be7dSNeil Armstrong irq = platform_get_irq(pdev, 0);
760d918fe42SMarkus Elfring if (irq < 0)
7613f68be7dSNeil Armstrong return irq;
7623f68be7dSNeil Armstrong
7633f68be7dSNeil Armstrong ret = devm_request_threaded_irq(dev, irq, dw_hdmi_top_irq,
7643f68be7dSNeil Armstrong dw_hdmi_top_thread_irq, IRQF_SHARED,
7653f68be7dSNeil Armstrong "dw_hdmi_top_irq", meson_dw_hdmi);
7663f68be7dSNeil Armstrong if (ret) {
7673f68be7dSNeil Armstrong dev_err(dev, "Failed to request hdmi top irq\n");
7683f68be7dSNeil Armstrong return ret;
7693f68be7dSNeil Armstrong }
7703f68be7dSNeil Armstrong
7712b6cb81bSMarc Zyngier meson_dw_hdmi_init(meson_dw_hdmi);
7722b6cb81bSMarc Zyngier
7733f68be7dSNeil Armstrong /* Bridge / Connector */
7743f68be7dSNeil Armstrong
7759bc78d6dSLaurent Pinchart dw_plat_data->priv_data = meson_dw_hdmi;
7763f68be7dSNeil Armstrong dw_plat_data->phy_ops = &meson_dw_hdmi_phy_ops;
7773f68be7dSNeil Armstrong dw_plat_data->phy_name = "meson_dw_hdmi_phy";
7783f68be7dSNeil Armstrong dw_plat_data->phy_data = meson_dw_hdmi;
7793f68be7dSNeil Armstrong dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709;
7808496a217SNeil Armstrong dw_plat_data->ycbcr_420_allowed = true;
781a584e2c3SNeil Armstrong dw_plat_data->disable_cec = true;
7820af5e0b4SNeil Armstrong dw_plat_data->output_port = 1;
7833f68be7dSNeil Armstrong
784e2229fb7SJonas Karlman if (dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxl-dw-hdmi") ||
785e2229fb7SJonas Karlman dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxm-dw-hdmi") ||
786e2229fb7SJonas Karlman dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-g12a-dw-hdmi"))
787e2229fb7SJonas Karlman dw_plat_data->use_drm_infoframe = true;
788e2229fb7SJonas Karlman
789eea034afSJernej Skrabec platform_set_drvdata(pdev, meson_dw_hdmi);
790eea034afSJernej Skrabec
791e67f6037SNeil Armstrong meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, &meson_dw_hdmi->dw_plat_data);
792eea034afSJernej Skrabec if (IS_ERR(meson_dw_hdmi->hdmi))
793eea034afSJernej Skrabec return PTR_ERR(meson_dw_hdmi->hdmi);
7943f68be7dSNeil Armstrong
795e67f6037SNeil Armstrong meson_dw_hdmi->bridge = of_drm_find_bridge(pdev->dev.of_node);
796f43aa584SNeil Armstrong
7973f68be7dSNeil Armstrong DRM_DEBUG_DRIVER("HDMI controller initialized\n");
7983f68be7dSNeil Armstrong
7993f68be7dSNeil Armstrong return 0;
8003f68be7dSNeil Armstrong }
8013f68be7dSNeil Armstrong
meson_dw_hdmi_unbind(struct device * dev,struct device * master,void * data)8023f68be7dSNeil Armstrong static void meson_dw_hdmi_unbind(struct device *dev, struct device *master,
8033f68be7dSNeil Armstrong void *data)
8043f68be7dSNeil Armstrong {
805eea034afSJernej Skrabec struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev);
806eea034afSJernej Skrabec
807eea034afSJernej Skrabec dw_hdmi_unbind(meson_dw_hdmi->hdmi);
8083f68be7dSNeil Armstrong }
8093f68be7dSNeil Armstrong
8103f68be7dSNeil Armstrong static const struct component_ops meson_dw_hdmi_ops = {
8113f68be7dSNeil Armstrong .bind = meson_dw_hdmi_bind,
8123f68be7dSNeil Armstrong .unbind = meson_dw_hdmi_unbind,
8133f68be7dSNeil Armstrong };
8143f68be7dSNeil Armstrong
meson_dw_hdmi_pm_suspend(struct device * dev)8151374b837SNeil Armstrong static int __maybe_unused meson_dw_hdmi_pm_suspend(struct device *dev)
8161374b837SNeil Armstrong {
8171374b837SNeil Armstrong struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev);
8181374b837SNeil Armstrong
8191374b837SNeil Armstrong if (!meson_dw_hdmi)
8201374b837SNeil Armstrong return 0;
8211374b837SNeil Armstrong
8221374b837SNeil Armstrong /* Reset TOP */
8231374b837SNeil Armstrong meson_dw_hdmi->data->top_write(meson_dw_hdmi,
8241374b837SNeil Armstrong HDMITX_TOP_SW_RESET, 0);
8251374b837SNeil Armstrong
8261374b837SNeil Armstrong return 0;
8271374b837SNeil Armstrong }
8281374b837SNeil Armstrong
meson_dw_hdmi_pm_resume(struct device * dev)8291374b837SNeil Armstrong static int __maybe_unused meson_dw_hdmi_pm_resume(struct device *dev)
8301374b837SNeil Armstrong {
8311374b837SNeil Armstrong struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev);
8321374b837SNeil Armstrong
8331374b837SNeil Armstrong if (!meson_dw_hdmi)
8341374b837SNeil Armstrong return 0;
8351374b837SNeil Armstrong
8361374b837SNeil Armstrong meson_dw_hdmi_init(meson_dw_hdmi);
8371374b837SNeil Armstrong
8381374b837SNeil Armstrong dw_hdmi_resume(meson_dw_hdmi->hdmi);
8391374b837SNeil Armstrong
8401374b837SNeil Armstrong return 0;
8411374b837SNeil Armstrong }
8421374b837SNeil Armstrong
meson_dw_hdmi_probe(struct platform_device * pdev)8433f68be7dSNeil Armstrong static int meson_dw_hdmi_probe(struct platform_device *pdev)
8443f68be7dSNeil Armstrong {
8453f68be7dSNeil Armstrong return component_add(&pdev->dev, &meson_dw_hdmi_ops);
8463f68be7dSNeil Armstrong }
8473f68be7dSNeil Armstrong
meson_dw_hdmi_remove(struct platform_device * pdev)84838ca2d93SUwe Kleine-König static void meson_dw_hdmi_remove(struct platform_device *pdev)
8493f68be7dSNeil Armstrong {
8503f68be7dSNeil Armstrong component_del(&pdev->dev, &meson_dw_hdmi_ops);
8513f68be7dSNeil Armstrong }
8523f68be7dSNeil Armstrong
8531374b837SNeil Armstrong static const struct dev_pm_ops meson_dw_hdmi_pm_ops = {
8541374b837SNeil Armstrong SET_SYSTEM_SLEEP_PM_OPS(meson_dw_hdmi_pm_suspend,
8551374b837SNeil Armstrong meson_dw_hdmi_pm_resume)
8561374b837SNeil Armstrong };
8571374b837SNeil Armstrong
8583f68be7dSNeil Armstrong static const struct of_device_id meson_dw_hdmi_of_table[] = {
8593b7c1237SNeil Armstrong { .compatible = "amlogic,meson-gxbb-dw-hdmi",
860*fa2d2e2dSJerome Brunet .data = &meson_dw_hdmi_gxbb_data },
8613b7c1237SNeil Armstrong { .compatible = "amlogic,meson-gxl-dw-hdmi",
862*fa2d2e2dSJerome Brunet .data = &meson_dw_hdmi_gxl_data },
8633b7c1237SNeil Armstrong { .compatible = "amlogic,meson-gxm-dw-hdmi",
864*fa2d2e2dSJerome Brunet .data = &meson_dw_hdmi_gxl_data },
8653b7c1237SNeil Armstrong { .compatible = "amlogic,meson-g12a-dw-hdmi",
8663b7c1237SNeil Armstrong .data = &meson_dw_hdmi_g12a_data },
8673f68be7dSNeil Armstrong { }
8683f68be7dSNeil Armstrong };
8693f68be7dSNeil Armstrong MODULE_DEVICE_TABLE(of, meson_dw_hdmi_of_table);
8703f68be7dSNeil Armstrong
8713f68be7dSNeil Armstrong static struct platform_driver meson_dw_hdmi_platform_driver = {
8723f68be7dSNeil Armstrong .probe = meson_dw_hdmi_probe,
87338ca2d93SUwe Kleine-König .remove_new = meson_dw_hdmi_remove,
8743f68be7dSNeil Armstrong .driver = {
8753f68be7dSNeil Armstrong .name = DRIVER_NAME,
8763f68be7dSNeil Armstrong .of_match_table = meson_dw_hdmi_of_table,
8771374b837SNeil Armstrong .pm = &meson_dw_hdmi_pm_ops,
8783f68be7dSNeil Armstrong },
8793f68be7dSNeil Armstrong };
8803f68be7dSNeil Armstrong module_platform_driver(meson_dw_hdmi_platform_driver);
8813f68be7dSNeil Armstrong
8823f68be7dSNeil Armstrong MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
8833f68be7dSNeil Armstrong MODULE_DESCRIPTION(DRIVER_DESC);
8843f68be7dSNeil Armstrong MODULE_LICENSE("GPL");
885