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(&reg_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(&reg_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(&reg_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(&reg_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(&reg_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(&reg_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(&reg_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(&reg_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