All of lore.kernel.org
 help / color / mirror / Atom feed
From: Icenowy Zheng <icenowy@aosc.io>
To: Maxime Ripard <maxime.ripard@free-electrons.com>,
	Rob Herring <robh+dt@kernel.org>, Chen-Yu Tsai <wens@csie.org>,
	Jernej Skrabec <jernej.skrabec@siol.net>
Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org,
	linux-sunxi@googlegroups.com
Subject: [PATCH 11/13] [NOT FOR REVIEW NOW] drm: sun4i: Add a glue for the DesignWare HDMI controller in H3
Date: Tue,  1 Aug 2017 21:13:02 +0800	[thread overview]
Message-ID: <20170801131304.7741-12-icenowy@aosc.io> (raw)
In-Reply-To: <20170801131304.7741-1-icenowy@aosc.io>

From: Jernej Skrabec <jernej.skrabec@siol.net>

Allwinner H3 features DesignWare HDMI Transmitter paired with custom
PHY.

For now, only video is supported by the driver. However, audio and CEC
are also supported by the hardware.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 drivers/gpu/drm/sun4i/Kconfig         |   9 +
 drivers/gpu/drm/sun4i/Makefile        |   1 +
 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c | 462 ++++++++++++++++++++++++++++++++++
 3 files changed, 472 insertions(+)
 create mode 100644 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c

diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index 06f05302ee75..589502ffe31a 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -40,6 +40,15 @@ config DRM_SUN4I_BACKEND
 	  do some alpha blending and feed graphics to TCON. If M is
 	  selected the module will be called sun4i-backend.
 
+config DRM_SUN8I_DW_HDMI
+	tristate "Support for Allwinner version of DesignWare HDMI"
+	depends on DRM_SUN4I
+	select DRM_DW_HDMI
+	help
+	  Choose this option if you have an Allwinner SoC with the
+	  DesignWare HDMI controller with custom HDMI PHY. If M is
+	  selected the module will be called sun8i_dw_hdmi.
+
 config DRM_SUN8I_MIXER
 	tristate "Support for Allwinner Display Engine 2.0 Mixer"
 	default MACH_SUN8I
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 43c753cafc88..9c56173bf140 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_DRM_SUN4I)		+= sun4i_tv.o
 obj-$(CONFIG_DRM_SUN4I_BACKEND)		+= sun4i-backend.o
 obj-$(CONFIG_DRM_SUN4I_HDMI)	+= sun4i-drm-hdmi.o
 obj-$(CONFIG_DRM_SUN8I_MIXER)		+= sun8i-mixer.o
+obj-$(CONFIG_DRM_SUN8I_DW_HDMI)	+= sun8i_dw_hdmi.o
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
new file mode 100644
index 000000000000..fa1ecbcf08b8
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2017, Jernej Skrabec <jernej.skrabec@siol.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/drm_of.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/bridge/dw_hdmi.h>
+
+#include "sun4i_crtc.h"
+#include "sun4i_tcon.h"
+
+#define SUN8I_HDMI_PHY_REG_POL		0x0000
+
+#define SUN8I_HDMI_PHY_REG_READ_EN	0x0010
+#define SUN8I_HDMI_PHY_REG_READ_EN_MAGIC	0x54524545
+
+#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE	0x0014
+#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC	0x42494E47
+
+#define SUN8I_HDMI_PHY_REG_CTRL		0x0020
+#define SUN8I_HDMI_PHY_REG_UNK1		0x0024
+#define SUN8I_HDMI_PHY_REG_UNK2		0x0028
+#define SUN8I_HDMI_PHY_REG_PLL		0x002c
+#define SUN8I_HDMI_PHY_REG_CLK		0x0030
+#define SUN8I_HDMI_PHY_REG_UNK3		0x0034
+
+#define SUN8I_HDMI_PHY_REG_STATUS	0x0038
+#define SUN8I_HDMI_PHY_REG_STATUS_READY		BIT(7)
+#define SUN8I_HDMI_PHY_REG_STATUS_HPD		BIT(19)
+
+#define to_sun8i_dw_hdmi(x)	container_of(x, struct sun8i_dw_hdmi, x)
+#define set_bits(p, v)		writel(readl(p) | (v), p)
+
+struct sun8i_dw_hdmi {
+	struct clk *clk_ddc;
+	struct clk *clk_hdmi;
+	struct device *dev;
+	struct drm_encoder encoder;
+	void __iomem *phy_base;
+	struct dw_hdmi_plat_data plat_data;
+	struct reset_control *rst_ddc;
+	struct reset_control *rst_hdmi;
+};
+
+static u32 sun8i_dw_hdmi_get_divider(int clk_khz)
+{
+	/*
+	 * Due to missing documentaion of HDMI PHY, we know correct
+	 * settings only for following four PHY dividers. Select one
+	 * based on clock speed.
+	 */
+	if (clk_khz <= 27000)
+		return 11;
+	else if (clk_khz <= 74250)
+		return 4;
+	else if (clk_khz <= 148500)
+		return 2;
+	else
+		return 1;
+}
+
+static void sun8i_dw_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+
+	DRM_DEBUG_DRIVER("Disabling HDMI Output\n");
+
+	sun4i_tcon_channel_disable(tcon, 1);
+}
+
+static void sun8i_dw_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+
+	DRM_DEBUG_DRIVER("Enabling HDMI Output\n");
+
+	sun4i_tcon_channel_enable(tcon, 1);
+}
+
+static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+					   struct drm_display_mode *mode,
+					   struct drm_display_mode *adj_mode)
+{
+	struct sun8i_dw_hdmi *hdmi = to_sun8i_dw_hdmi(encoder);
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+	u32 div;
+
+	sun4i_tcon1_mode_set(tcon, mode);
+
+	div = sun8i_dw_hdmi_get_divider(mode->crtc_clock);
+	clk_set_rate(hdmi->clk_hdmi, mode->crtc_clock * 1000 * div);
+	clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+}
+
+static const struct drm_encoder_helper_funcs sun8i_dw_hdmi_encoder_helper_funcs = {
+	.mode_set = sun8i_dw_hdmi_encoder_mode_set,
+	.enable   = sun8i_dw_hdmi_encoder_enable,
+	.disable  = sun8i_dw_hdmi_encoder_disable,
+};
+
+static int sun8i_dw_hdmi_phy_init(struct dw_hdmi *hdmi_data, void *data,
+				  struct drm_display_mode *mode)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 div = sun8i_dw_hdmi_get_divider(mode->crtc_clock);
+	u32 val;
+
+	/*
+	 * Unfortunately, we don't know much about those magic
+	 * numbers. They are taken from Allwinner BSP driver.
+	 */
+
+	val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(val & ~0xf000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+
+	switch (div) {
+	case 1:
+		writel(0x30dc5fc0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x800863C0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(200);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		if (val < 0x3d)
+			set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+				 val + 2);
+		else
+			set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, 
+				 0x3f);
+		mdelay(100);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F8246B5, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 2:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x80084381, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063a800, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C485, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 4:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 11:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x8008430a, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	}
+
+	/*
+	 * Condition in original code is a bit weird. This is attempt
+	 * to make it more reasonable and it works. It could be that
+	 * bits and conditions are related and should be separated.
+	 */
+	if (!((mode->flags & DRM_MODE_FLAG_PHSYNC) &&
+	      (mode->flags & DRM_MODE_FLAG_PVSYNC))) {
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_POL, 0x300);
+	}
+
+	return 0;
+}
+
+static void sun8i_dw_hdmi_phy_disable(struct dw_hdmi *hdmi_data, void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+
+	writel(7, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+}
+
+static enum drm_connector_status sun8i_dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi_data,
+							    void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 reg_val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+
+	return !!(reg_val & SUN8I_HDMI_PHY_REG_STATUS_HPD) ?
+		connector_status_connected : connector_status_disconnected;
+}
+
+static const struct dw_hdmi_phy_ops sun8i_dw_hdmi_phy_ops = {
+	.init = &sun8i_dw_hdmi_phy_init,
+	.disable = &sun8i_dw_hdmi_phy_disable,
+	.read_hpd = &sun8i_dw_hdmi_phy_read_hpd,
+};
+
+static void sun8i_dw_hdmi_pre_init(void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 timeout = 20;
+	u32 val;
+
+	/*
+	 * HDMI PHY settings are taken as-is from Allwinner BSP code.
+	 * There is no documentation.
+	 */
+	writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(0));
+	udelay(5);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(16));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(1));
+	udelay(10);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(2));
+	udelay(5);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(3));
+	udelay(40);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(19));
+	udelay(100);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(18));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 7 << 4);
+
+	/* Note that Allwinner code doesn't fail in case of timeout */
+	while (!(readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS) &
+		SUN8I_HDMI_PHY_REG_STATUS_READY)) {
+		if (!timeout--) {
+			dev_warn(hdmi->dev, "HDMI PHY init timeout!\n");
+			break;
+		}
+		udelay(100);
+	}
+
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 0xf << 8);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(7));
+
+	writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+	writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+	mdelay(10);
+	writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+	mdelay(100);
+	val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+	val = (val & 0x1f800) >> 11;
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(31) | BIT(30));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+	writel(0x01FF0F7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(0x80639000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+	writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+
+	/* enable read access to HDMI controller */
+	writel(SUN8I_HDMI_PHY_REG_READ_EN_MAGIC,
+	       hdmi->phy_base + SUN8I_HDMI_PHY_REG_READ_EN);
+
+	/* descramble register offsets */
+	writel(SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC,
+	       hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNSCRAMBLE);
+}
+
+static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
+			      void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dw_hdmi_plat_data *plat_data;
+	struct drm_device *drm = data;
+	struct drm_encoder *encoder;
+	struct sun8i_dw_hdmi *hdmi;
+	struct resource *res;
+	int ret;
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	plat_data = &hdmi->plat_data;
+	hdmi->dev = &pdev->dev;
+	encoder = &hdmi->encoder;
+
+	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm,
+							     dev->of_node);
+	/*
+	 * If we failed to find the CRTC(s) which this encoder is
+	 * supposed to be connected to, it's because the CRTC has
+	 * not been registered yet.  Defer probing, and hope that
+	 * the required CRTC is added later.
+	 */
+	if (encoder->possible_crtcs == 0)
+		return -EPROBE_DEFER;
+
+	/* resource 0 is the memory region for the core controller */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	hdmi->phy_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->phy_base))
+		return PTR_ERR(hdmi->phy_base);
+
+	hdmi->clk_hdmi = devm_clk_get(dev, "isfr");
+	if (IS_ERR(hdmi->clk_hdmi)) {
+		dev_err(dev, "Could not get hdmi clock\n");
+		return PTR_ERR(hdmi->clk_hdmi);
+	}
+
+	hdmi->clk_ddc = devm_clk_get(dev, "iddc");
+	if (IS_ERR(hdmi->clk_ddc)) {
+		dev_err(dev, "Could not get ddc clock\n");
+		return PTR_ERR(hdmi->clk_ddc);
+	}
+
+	hdmi->rst_hdmi = devm_reset_control_get(dev, "hdmi");
+	if (IS_ERR(hdmi->rst_hdmi)) {
+		dev_err(dev, "Could not get hdmi reset control\n");
+		return PTR_ERR(hdmi->rst_hdmi);
+	}
+
+	hdmi->rst_ddc = devm_reset_control_get(dev, "ddc");
+	if (IS_ERR(hdmi->rst_ddc)) {
+		dev_err(dev, "Could not get dw-hdmi reset control\n");
+		return PTR_ERR(hdmi->rst_ddc);
+	}
+
+	ret = clk_prepare_enable(hdmi->clk_ddc);
+	if (ret) {
+		dev_err(dev, "Cannot enable DDC clock: %d\n", ret);
+		return ret;
+	}
+
+	ret = reset_control_deassert(hdmi->rst_hdmi);
+	if (ret) {
+		dev_err(dev, "Could not deassert hdmi reset control\n");
+		goto err_ddc_clk;
+	}
+
+	ret = reset_control_deassert(hdmi->rst_ddc);
+	if (ret) {
+		dev_err(dev, "Could not deassert ddc reset control\n");
+		goto err_assert_hdmi_reset;
+	}
+
+	drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs);
+	drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs,
+			 DRM_MODE_ENCODER_TMDS, NULL);
+
+	plat_data->pre_init = &sun8i_dw_hdmi_pre_init,
+	plat_data->pre_init_data = hdmi;
+	plat_data->phy_ops = &sun8i_dw_hdmi_phy_ops,
+	plat_data->phy_name = "sun8i_dw_hdmi_phy",
+	plat_data->phy_data = hdmi;
+
+	ret = dw_hdmi_bind(pdev, encoder, plat_data);
+
+	/*
+	 * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
+	 * which would have called the encoder cleanup.  Do it manually.
+	 */
+	if (ret)
+		goto cleanup_encoder;
+
+	return 0;
+
+cleanup_encoder:
+	drm_encoder_cleanup(encoder);
+	reset_control_assert(hdmi->rst_ddc);
+err_assert_hdmi_reset:
+	reset_control_assert(hdmi->rst_hdmi);
+err_ddc_clk:
+	clk_disable_unprepare(hdmi->clk_ddc);
+
+	return ret;
+}
+
+static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master,
+				 void *data)
+{
+	return dw_hdmi_unbind(dev);
+}
+
+static const struct component_ops sun8i_dw_hdmi_ops = {
+	.bind	= sun8i_dw_hdmi_bind,
+	.unbind	= sun8i_dw_hdmi_unbind,
+};
+
+static int sun8i_dw_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &sun8i_dw_hdmi_ops);
+}
+
+static int sun8i_dw_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &sun8i_dw_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = {
+	{ .compatible = "allwinner,h3-dw-hdmi" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids);
+
+struct platform_driver sun8i_dw_hdmi_pltfm_driver = {
+	.probe  = sun8i_dw_hdmi_probe,
+	.remove = sun8i_dw_hdmi_remove,
+	.driver = {
+		.name = "sun8i-dw-hdmi",
+		.of_match_table = sun8i_dw_hdmi_dt_ids,
+	},
+};
+module_platform_driver(sun8i_dw_hdmi_pltfm_driver);
+
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_DESCRIPTION("Allwinner H3 DW HDMI bridge");
+MODULE_LICENSE("GPL");
-- 
2.13.0

WARNING: multiple messages have this Message-ID (diff)
From: Icenowy Zheng <icenowy-h8G6r0blFSE@public.gmane.org>
To: Maxime Ripard
	<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>,
	Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>,
	Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>,
	Jernej Skrabec <jernej.skrabec-gGgVlfcn5nU@public.gmane.org>
Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-clk-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
Subject: [PATCH 11/13] [NOT FOR REVIEW NOW] drm: sun4i: Add a glue for the DesignWare HDMI controller in H3
Date: Tue,  1 Aug 2017 21:13:02 +0800	[thread overview]
Message-ID: <20170801131304.7741-12-icenowy@aosc.io> (raw)
In-Reply-To: <20170801131304.7741-1-icenowy-h8G6r0blFSE@public.gmane.org>

From: Jernej Skrabec <jernej.skrabec-gGgVlfcn5nU@public.gmane.org>

Allwinner H3 features DesignWare HDMI Transmitter paired with custom
PHY.

For now, only video is supported by the driver. However, audio and CEC
are also supported by the hardware.

Signed-off-by: Jernej Skrabec <jernej.skrabec-gGgVlfcn5nU@public.gmane.org>
---
 drivers/gpu/drm/sun4i/Kconfig         |   9 +
 drivers/gpu/drm/sun4i/Makefile        |   1 +
 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c | 462 ++++++++++++++++++++++++++++++++++
 3 files changed, 472 insertions(+)
 create mode 100644 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c

diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index 06f05302ee75..589502ffe31a 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -40,6 +40,15 @@ config DRM_SUN4I_BACKEND
 	  do some alpha blending and feed graphics to TCON. If M is
 	  selected the module will be called sun4i-backend.
 
+config DRM_SUN8I_DW_HDMI
+	tristate "Support for Allwinner version of DesignWare HDMI"
+	depends on DRM_SUN4I
+	select DRM_DW_HDMI
+	help
+	  Choose this option if you have an Allwinner SoC with the
+	  DesignWare HDMI controller with custom HDMI PHY. If M is
+	  selected the module will be called sun8i_dw_hdmi.
+
 config DRM_SUN8I_MIXER
 	tristate "Support for Allwinner Display Engine 2.0 Mixer"
 	default MACH_SUN8I
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 43c753cafc88..9c56173bf140 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_DRM_SUN4I)		+= sun4i_tv.o
 obj-$(CONFIG_DRM_SUN4I_BACKEND)		+= sun4i-backend.o
 obj-$(CONFIG_DRM_SUN4I_HDMI)	+= sun4i-drm-hdmi.o
 obj-$(CONFIG_DRM_SUN8I_MIXER)		+= sun8i-mixer.o
+obj-$(CONFIG_DRM_SUN8I_DW_HDMI)	+= sun8i_dw_hdmi.o
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
new file mode 100644
index 000000000000..fa1ecbcf08b8
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2017, Jernej Skrabec <jernej.skrabec-gGgVlfcn5nU@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/drm_of.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/bridge/dw_hdmi.h>
+
+#include "sun4i_crtc.h"
+#include "sun4i_tcon.h"
+
+#define SUN8I_HDMI_PHY_REG_POL		0x0000
+
+#define SUN8I_HDMI_PHY_REG_READ_EN	0x0010
+#define SUN8I_HDMI_PHY_REG_READ_EN_MAGIC	0x54524545
+
+#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE	0x0014
+#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC	0x42494E47
+
+#define SUN8I_HDMI_PHY_REG_CTRL		0x0020
+#define SUN8I_HDMI_PHY_REG_UNK1		0x0024
+#define SUN8I_HDMI_PHY_REG_UNK2		0x0028
+#define SUN8I_HDMI_PHY_REG_PLL		0x002c
+#define SUN8I_HDMI_PHY_REG_CLK		0x0030
+#define SUN8I_HDMI_PHY_REG_UNK3		0x0034
+
+#define SUN8I_HDMI_PHY_REG_STATUS	0x0038
+#define SUN8I_HDMI_PHY_REG_STATUS_READY		BIT(7)
+#define SUN8I_HDMI_PHY_REG_STATUS_HPD		BIT(19)
+
+#define to_sun8i_dw_hdmi(x)	container_of(x, struct sun8i_dw_hdmi, x)
+#define set_bits(p, v)		writel(readl(p) | (v), p)
+
+struct sun8i_dw_hdmi {
+	struct clk *clk_ddc;
+	struct clk *clk_hdmi;
+	struct device *dev;
+	struct drm_encoder encoder;
+	void __iomem *phy_base;
+	struct dw_hdmi_plat_data plat_data;
+	struct reset_control *rst_ddc;
+	struct reset_control *rst_hdmi;
+};
+
+static u32 sun8i_dw_hdmi_get_divider(int clk_khz)
+{
+	/*
+	 * Due to missing documentaion of HDMI PHY, we know correct
+	 * settings only for following four PHY dividers. Select one
+	 * based on clock speed.
+	 */
+	if (clk_khz <= 27000)
+		return 11;
+	else if (clk_khz <= 74250)
+		return 4;
+	else if (clk_khz <= 148500)
+		return 2;
+	else
+		return 1;
+}
+
+static void sun8i_dw_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+
+	DRM_DEBUG_DRIVER("Disabling HDMI Output\n");
+
+	sun4i_tcon_channel_disable(tcon, 1);
+}
+
+static void sun8i_dw_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+
+	DRM_DEBUG_DRIVER("Enabling HDMI Output\n");
+
+	sun4i_tcon_channel_enable(tcon, 1);
+}
+
+static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+					   struct drm_display_mode *mode,
+					   struct drm_display_mode *adj_mode)
+{
+	struct sun8i_dw_hdmi *hdmi = to_sun8i_dw_hdmi(encoder);
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+	u32 div;
+
+	sun4i_tcon1_mode_set(tcon, mode);
+
+	div = sun8i_dw_hdmi_get_divider(mode->crtc_clock);
+	clk_set_rate(hdmi->clk_hdmi, mode->crtc_clock * 1000 * div);
+	clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+}
+
+static const struct drm_encoder_helper_funcs sun8i_dw_hdmi_encoder_helper_funcs = {
+	.mode_set = sun8i_dw_hdmi_encoder_mode_set,
+	.enable   = sun8i_dw_hdmi_encoder_enable,
+	.disable  = sun8i_dw_hdmi_encoder_disable,
+};
+
+static int sun8i_dw_hdmi_phy_init(struct dw_hdmi *hdmi_data, void *data,
+				  struct drm_display_mode *mode)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 div = sun8i_dw_hdmi_get_divider(mode->crtc_clock);
+	u32 val;
+
+	/*
+	 * Unfortunately, we don't know much about those magic
+	 * numbers. They are taken from Allwinner BSP driver.
+	 */
+
+	val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(val & ~0xf000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+
+	switch (div) {
+	case 1:
+		writel(0x30dc5fc0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x800863C0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(200);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		if (val < 0x3d)
+			set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+				 val + 2);
+		else
+			set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, 
+				 0x3f);
+		mdelay(100);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F8246B5, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 2:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x80084381, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063a800, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C485, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 4:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 11:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x8008430a, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	}
+
+	/*
+	 * Condition in original code is a bit weird. This is attempt
+	 * to make it more reasonable and it works. It could be that
+	 * bits and conditions are related and should be separated.
+	 */
+	if (!((mode->flags & DRM_MODE_FLAG_PHSYNC) &&
+	      (mode->flags & DRM_MODE_FLAG_PVSYNC))) {
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_POL, 0x300);
+	}
+
+	return 0;
+}
+
+static void sun8i_dw_hdmi_phy_disable(struct dw_hdmi *hdmi_data, void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+
+	writel(7, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+}
+
+static enum drm_connector_status sun8i_dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi_data,
+							    void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 reg_val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+
+	return !!(reg_val & SUN8I_HDMI_PHY_REG_STATUS_HPD) ?
+		connector_status_connected : connector_status_disconnected;
+}
+
+static const struct dw_hdmi_phy_ops sun8i_dw_hdmi_phy_ops = {
+	.init = &sun8i_dw_hdmi_phy_init,
+	.disable = &sun8i_dw_hdmi_phy_disable,
+	.read_hpd = &sun8i_dw_hdmi_phy_read_hpd,
+};
+
+static void sun8i_dw_hdmi_pre_init(void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 timeout = 20;
+	u32 val;
+
+	/*
+	 * HDMI PHY settings are taken as-is from Allwinner BSP code.
+	 * There is no documentation.
+	 */
+	writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(0));
+	udelay(5);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(16));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(1));
+	udelay(10);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(2));
+	udelay(5);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(3));
+	udelay(40);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(19));
+	udelay(100);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(18));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 7 << 4);
+
+	/* Note that Allwinner code doesn't fail in case of timeout */
+	while (!(readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS) &
+		SUN8I_HDMI_PHY_REG_STATUS_READY)) {
+		if (!timeout--) {
+			dev_warn(hdmi->dev, "HDMI PHY init timeout!\n");
+			break;
+		}
+		udelay(100);
+	}
+
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 0xf << 8);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(7));
+
+	writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+	writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+	mdelay(10);
+	writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+	mdelay(100);
+	val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+	val = (val & 0x1f800) >> 11;
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(31) | BIT(30));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+	writel(0x01FF0F7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(0x80639000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+	writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+
+	/* enable read access to HDMI controller */
+	writel(SUN8I_HDMI_PHY_REG_READ_EN_MAGIC,
+	       hdmi->phy_base + SUN8I_HDMI_PHY_REG_READ_EN);
+
+	/* descramble register offsets */
+	writel(SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC,
+	       hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNSCRAMBLE);
+}
+
+static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
+			      void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dw_hdmi_plat_data *plat_data;
+	struct drm_device *drm = data;
+	struct drm_encoder *encoder;
+	struct sun8i_dw_hdmi *hdmi;
+	struct resource *res;
+	int ret;
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	plat_data = &hdmi->plat_data;
+	hdmi->dev = &pdev->dev;
+	encoder = &hdmi->encoder;
+
+	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm,
+							     dev->of_node);
+	/*
+	 * If we failed to find the CRTC(s) which this encoder is
+	 * supposed to be connected to, it's because the CRTC has
+	 * not been registered yet.  Defer probing, and hope that
+	 * the required CRTC is added later.
+	 */
+	if (encoder->possible_crtcs == 0)
+		return -EPROBE_DEFER;
+
+	/* resource 0 is the memory region for the core controller */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	hdmi->phy_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->phy_base))
+		return PTR_ERR(hdmi->phy_base);
+
+	hdmi->clk_hdmi = devm_clk_get(dev, "isfr");
+	if (IS_ERR(hdmi->clk_hdmi)) {
+		dev_err(dev, "Could not get hdmi clock\n");
+		return PTR_ERR(hdmi->clk_hdmi);
+	}
+
+	hdmi->clk_ddc = devm_clk_get(dev, "iddc");
+	if (IS_ERR(hdmi->clk_ddc)) {
+		dev_err(dev, "Could not get ddc clock\n");
+		return PTR_ERR(hdmi->clk_ddc);
+	}
+
+	hdmi->rst_hdmi = devm_reset_control_get(dev, "hdmi");
+	if (IS_ERR(hdmi->rst_hdmi)) {
+		dev_err(dev, "Could not get hdmi reset control\n");
+		return PTR_ERR(hdmi->rst_hdmi);
+	}
+
+	hdmi->rst_ddc = devm_reset_control_get(dev, "ddc");
+	if (IS_ERR(hdmi->rst_ddc)) {
+		dev_err(dev, "Could not get dw-hdmi reset control\n");
+		return PTR_ERR(hdmi->rst_ddc);
+	}
+
+	ret = clk_prepare_enable(hdmi->clk_ddc);
+	if (ret) {
+		dev_err(dev, "Cannot enable DDC clock: %d\n", ret);
+		return ret;
+	}
+
+	ret = reset_control_deassert(hdmi->rst_hdmi);
+	if (ret) {
+		dev_err(dev, "Could not deassert hdmi reset control\n");
+		goto err_ddc_clk;
+	}
+
+	ret = reset_control_deassert(hdmi->rst_ddc);
+	if (ret) {
+		dev_err(dev, "Could not deassert ddc reset control\n");
+		goto err_assert_hdmi_reset;
+	}
+
+	drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs);
+	drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs,
+			 DRM_MODE_ENCODER_TMDS, NULL);
+
+	plat_data->pre_init = &sun8i_dw_hdmi_pre_init,
+	plat_data->pre_init_data = hdmi;
+	plat_data->phy_ops = &sun8i_dw_hdmi_phy_ops,
+	plat_data->phy_name = "sun8i_dw_hdmi_phy",
+	plat_data->phy_data = hdmi;
+
+	ret = dw_hdmi_bind(pdev, encoder, plat_data);
+
+	/*
+	 * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
+	 * which would have called the encoder cleanup.  Do it manually.
+	 */
+	if (ret)
+		goto cleanup_encoder;
+
+	return 0;
+
+cleanup_encoder:
+	drm_encoder_cleanup(encoder);
+	reset_control_assert(hdmi->rst_ddc);
+err_assert_hdmi_reset:
+	reset_control_assert(hdmi->rst_hdmi);
+err_ddc_clk:
+	clk_disable_unprepare(hdmi->clk_ddc);
+
+	return ret;
+}
+
+static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master,
+				 void *data)
+{
+	return dw_hdmi_unbind(dev);
+}
+
+static const struct component_ops sun8i_dw_hdmi_ops = {
+	.bind	= sun8i_dw_hdmi_bind,
+	.unbind	= sun8i_dw_hdmi_unbind,
+};
+
+static int sun8i_dw_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &sun8i_dw_hdmi_ops);
+}
+
+static int sun8i_dw_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &sun8i_dw_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = {
+	{ .compatible = "allwinner,h3-dw-hdmi" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids);
+
+struct platform_driver sun8i_dw_hdmi_pltfm_driver = {
+	.probe  = sun8i_dw_hdmi_probe,
+	.remove = sun8i_dw_hdmi_remove,
+	.driver = {
+		.name = "sun8i-dw-hdmi",
+		.of_match_table = sun8i_dw_hdmi_dt_ids,
+	},
+};
+module_platform_driver(sun8i_dw_hdmi_pltfm_driver);
+
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec-gGgVlfcn5nU@public.gmane.org>");
+MODULE_DESCRIPTION("Allwinner H3 DW HDMI bridge");
+MODULE_LICENSE("GPL");
-- 
2.13.0

WARNING: multiple messages have this Message-ID (diff)
From: Icenowy Zheng <icenowy@aosc.io>
To: Maxime Ripard <maxime.ripard@free-electrons.com>,
	Rob Herring <robh+dt@kernel.org>, Chen-Yu Tsai <wens@csie.org>,
	Jernej Skrabec <jernej.skrabec@siol.net>
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	dri-devel@lists.freedesktop.org, linux-sunxi@googlegroups.com,
	linux-clk@vger.kernel.org, linux-arm-kernel@lists.infradead.org
Subject: [PATCH 11/13] [NOT FOR REVIEW NOW] drm: sun4i: Add a glue for the DesignWare HDMI controller in H3
Date: Tue,  1 Aug 2017 21:13:02 +0800	[thread overview]
Message-ID: <20170801131304.7741-12-icenowy@aosc.io> (raw)
In-Reply-To: <20170801131304.7741-1-icenowy@aosc.io>

From: Jernej Skrabec <jernej.skrabec@siol.net>

Allwinner H3 features DesignWare HDMI Transmitter paired with custom
PHY.

For now, only video is supported by the driver. However, audio and CEC
are also supported by the hardware.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 drivers/gpu/drm/sun4i/Kconfig         |   9 +
 drivers/gpu/drm/sun4i/Makefile        |   1 +
 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c | 462 ++++++++++++++++++++++++++++++++++
 3 files changed, 472 insertions(+)
 create mode 100644 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c

diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index 06f05302ee75..589502ffe31a 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -40,6 +40,15 @@ config DRM_SUN4I_BACKEND
 	  do some alpha blending and feed graphics to TCON. If M is
 	  selected the module will be called sun4i-backend.
 
+config DRM_SUN8I_DW_HDMI
+	tristate "Support for Allwinner version of DesignWare HDMI"
+	depends on DRM_SUN4I
+	select DRM_DW_HDMI
+	help
+	  Choose this option if you have an Allwinner SoC with the
+	  DesignWare HDMI controller with custom HDMI PHY. If M is
+	  selected the module will be called sun8i_dw_hdmi.
+
 config DRM_SUN8I_MIXER
 	tristate "Support for Allwinner Display Engine 2.0 Mixer"
 	default MACH_SUN8I
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 43c753cafc88..9c56173bf140 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_DRM_SUN4I)		+= sun4i_tv.o
 obj-$(CONFIG_DRM_SUN4I_BACKEND)		+= sun4i-backend.o
 obj-$(CONFIG_DRM_SUN4I_HDMI)	+= sun4i-drm-hdmi.o
 obj-$(CONFIG_DRM_SUN8I_MIXER)		+= sun8i-mixer.o
+obj-$(CONFIG_DRM_SUN8I_DW_HDMI)	+= sun8i_dw_hdmi.o
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
new file mode 100644
index 000000000000..fa1ecbcf08b8
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2017, Jernej Skrabec <jernej.skrabec@siol.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/drm_of.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/bridge/dw_hdmi.h>
+
+#include "sun4i_crtc.h"
+#include "sun4i_tcon.h"
+
+#define SUN8I_HDMI_PHY_REG_POL		0x0000
+
+#define SUN8I_HDMI_PHY_REG_READ_EN	0x0010
+#define SUN8I_HDMI_PHY_REG_READ_EN_MAGIC	0x54524545
+
+#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE	0x0014
+#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC	0x42494E47
+
+#define SUN8I_HDMI_PHY_REG_CTRL		0x0020
+#define SUN8I_HDMI_PHY_REG_UNK1		0x0024
+#define SUN8I_HDMI_PHY_REG_UNK2		0x0028
+#define SUN8I_HDMI_PHY_REG_PLL		0x002c
+#define SUN8I_HDMI_PHY_REG_CLK		0x0030
+#define SUN8I_HDMI_PHY_REG_UNK3		0x0034
+
+#define SUN8I_HDMI_PHY_REG_STATUS	0x0038
+#define SUN8I_HDMI_PHY_REG_STATUS_READY		BIT(7)
+#define SUN8I_HDMI_PHY_REG_STATUS_HPD		BIT(19)
+
+#define to_sun8i_dw_hdmi(x)	container_of(x, struct sun8i_dw_hdmi, x)
+#define set_bits(p, v)		writel(readl(p) | (v), p)
+
+struct sun8i_dw_hdmi {
+	struct clk *clk_ddc;
+	struct clk *clk_hdmi;
+	struct device *dev;
+	struct drm_encoder encoder;
+	void __iomem *phy_base;
+	struct dw_hdmi_plat_data plat_data;
+	struct reset_control *rst_ddc;
+	struct reset_control *rst_hdmi;
+};
+
+static u32 sun8i_dw_hdmi_get_divider(int clk_khz)
+{
+	/*
+	 * Due to missing documentaion of HDMI PHY, we know correct
+	 * settings only for following four PHY dividers. Select one
+	 * based on clock speed.
+	 */
+	if (clk_khz <= 27000)
+		return 11;
+	else if (clk_khz <= 74250)
+		return 4;
+	else if (clk_khz <= 148500)
+		return 2;
+	else
+		return 1;
+}
+
+static void sun8i_dw_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+
+	DRM_DEBUG_DRIVER("Disabling HDMI Output\n");
+
+	sun4i_tcon_channel_disable(tcon, 1);
+}
+
+static void sun8i_dw_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+
+	DRM_DEBUG_DRIVER("Enabling HDMI Output\n");
+
+	sun4i_tcon_channel_enable(tcon, 1);
+}
+
+static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+					   struct drm_display_mode *mode,
+					   struct drm_display_mode *adj_mode)
+{
+	struct sun8i_dw_hdmi *hdmi = to_sun8i_dw_hdmi(encoder);
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+	u32 div;
+
+	sun4i_tcon1_mode_set(tcon, mode);
+
+	div = sun8i_dw_hdmi_get_divider(mode->crtc_clock);
+	clk_set_rate(hdmi->clk_hdmi, mode->crtc_clock * 1000 * div);
+	clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+}
+
+static const struct drm_encoder_helper_funcs sun8i_dw_hdmi_encoder_helper_funcs = {
+	.mode_set = sun8i_dw_hdmi_encoder_mode_set,
+	.enable   = sun8i_dw_hdmi_encoder_enable,
+	.disable  = sun8i_dw_hdmi_encoder_disable,
+};
+
+static int sun8i_dw_hdmi_phy_init(struct dw_hdmi *hdmi_data, void *data,
+				  struct drm_display_mode *mode)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 div = sun8i_dw_hdmi_get_divider(mode->crtc_clock);
+	u32 val;
+
+	/*
+	 * Unfortunately, we don't know much about those magic
+	 * numbers. They are taken from Allwinner BSP driver.
+	 */
+
+	val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(val & ~0xf000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+
+	switch (div) {
+	case 1:
+		writel(0x30dc5fc0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x800863C0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(200);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		if (val < 0x3d)
+			set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+				 val + 2);
+		else
+			set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, 
+				 0x3f);
+		mdelay(100);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F8246B5, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 2:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x80084381, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063a800, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C485, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 4:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 11:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x8008430a, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	}
+
+	/*
+	 * Condition in original code is a bit weird. This is attempt
+	 * to make it more reasonable and it works. It could be that
+	 * bits and conditions are related and should be separated.
+	 */
+	if (!((mode->flags & DRM_MODE_FLAG_PHSYNC) &&
+	      (mode->flags & DRM_MODE_FLAG_PVSYNC))) {
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_POL, 0x300);
+	}
+
+	return 0;
+}
+
+static void sun8i_dw_hdmi_phy_disable(struct dw_hdmi *hdmi_data, void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+
+	writel(7, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+}
+
+static enum drm_connector_status sun8i_dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi_data,
+							    void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 reg_val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+
+	return !!(reg_val & SUN8I_HDMI_PHY_REG_STATUS_HPD) ?
+		connector_status_connected : connector_status_disconnected;
+}
+
+static const struct dw_hdmi_phy_ops sun8i_dw_hdmi_phy_ops = {
+	.init = &sun8i_dw_hdmi_phy_init,
+	.disable = &sun8i_dw_hdmi_phy_disable,
+	.read_hpd = &sun8i_dw_hdmi_phy_read_hpd,
+};
+
+static void sun8i_dw_hdmi_pre_init(void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 timeout = 20;
+	u32 val;
+
+	/*
+	 * HDMI PHY settings are taken as-is from Allwinner BSP code.
+	 * There is no documentation.
+	 */
+	writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(0));
+	udelay(5);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(16));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(1));
+	udelay(10);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(2));
+	udelay(5);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(3));
+	udelay(40);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(19));
+	udelay(100);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(18));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 7 << 4);
+
+	/* Note that Allwinner code doesn't fail in case of timeout */
+	while (!(readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS) &
+		SUN8I_HDMI_PHY_REG_STATUS_READY)) {
+		if (!timeout--) {
+			dev_warn(hdmi->dev, "HDMI PHY init timeout!\n");
+			break;
+		}
+		udelay(100);
+	}
+
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 0xf << 8);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(7));
+
+	writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+	writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+	mdelay(10);
+	writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+	mdelay(100);
+	val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+	val = (val & 0x1f800) >> 11;
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(31) | BIT(30));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+	writel(0x01FF0F7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(0x80639000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+	writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+
+	/* enable read access to HDMI controller */
+	writel(SUN8I_HDMI_PHY_REG_READ_EN_MAGIC,
+	       hdmi->phy_base + SUN8I_HDMI_PHY_REG_READ_EN);
+
+	/* descramble register offsets */
+	writel(SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC,
+	       hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNSCRAMBLE);
+}
+
+static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
+			      void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dw_hdmi_plat_data *plat_data;
+	struct drm_device *drm = data;
+	struct drm_encoder *encoder;
+	struct sun8i_dw_hdmi *hdmi;
+	struct resource *res;
+	int ret;
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	plat_data = &hdmi->plat_data;
+	hdmi->dev = &pdev->dev;
+	encoder = &hdmi->encoder;
+
+	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm,
+							     dev->of_node);
+	/*
+	 * If we failed to find the CRTC(s) which this encoder is
+	 * supposed to be connected to, it's because the CRTC has
+	 * not been registered yet.  Defer probing, and hope that
+	 * the required CRTC is added later.
+	 */
+	if (encoder->possible_crtcs == 0)
+		return -EPROBE_DEFER;
+
+	/* resource 0 is the memory region for the core controller */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	hdmi->phy_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->phy_base))
+		return PTR_ERR(hdmi->phy_base);
+
+	hdmi->clk_hdmi = devm_clk_get(dev, "isfr");
+	if (IS_ERR(hdmi->clk_hdmi)) {
+		dev_err(dev, "Could not get hdmi clock\n");
+		return PTR_ERR(hdmi->clk_hdmi);
+	}
+
+	hdmi->clk_ddc = devm_clk_get(dev, "iddc");
+	if (IS_ERR(hdmi->clk_ddc)) {
+		dev_err(dev, "Could not get ddc clock\n");
+		return PTR_ERR(hdmi->clk_ddc);
+	}
+
+	hdmi->rst_hdmi = devm_reset_control_get(dev, "hdmi");
+	if (IS_ERR(hdmi->rst_hdmi)) {
+		dev_err(dev, "Could not get hdmi reset control\n");
+		return PTR_ERR(hdmi->rst_hdmi);
+	}
+
+	hdmi->rst_ddc = devm_reset_control_get(dev, "ddc");
+	if (IS_ERR(hdmi->rst_ddc)) {
+		dev_err(dev, "Could not get dw-hdmi reset control\n");
+		return PTR_ERR(hdmi->rst_ddc);
+	}
+
+	ret = clk_prepare_enable(hdmi->clk_ddc);
+	if (ret) {
+		dev_err(dev, "Cannot enable DDC clock: %d\n", ret);
+		return ret;
+	}
+
+	ret = reset_control_deassert(hdmi->rst_hdmi);
+	if (ret) {
+		dev_err(dev, "Could not deassert hdmi reset control\n");
+		goto err_ddc_clk;
+	}
+
+	ret = reset_control_deassert(hdmi->rst_ddc);
+	if (ret) {
+		dev_err(dev, "Could not deassert ddc reset control\n");
+		goto err_assert_hdmi_reset;
+	}
+
+	drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs);
+	drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs,
+			 DRM_MODE_ENCODER_TMDS, NULL);
+
+	plat_data->pre_init = &sun8i_dw_hdmi_pre_init,
+	plat_data->pre_init_data = hdmi;
+	plat_data->phy_ops = &sun8i_dw_hdmi_phy_ops,
+	plat_data->phy_name = "sun8i_dw_hdmi_phy",
+	plat_data->phy_data = hdmi;
+
+	ret = dw_hdmi_bind(pdev, encoder, plat_data);
+
+	/*
+	 * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
+	 * which would have called the encoder cleanup.  Do it manually.
+	 */
+	if (ret)
+		goto cleanup_encoder;
+
+	return 0;
+
+cleanup_encoder:
+	drm_encoder_cleanup(encoder);
+	reset_control_assert(hdmi->rst_ddc);
+err_assert_hdmi_reset:
+	reset_control_assert(hdmi->rst_hdmi);
+err_ddc_clk:
+	clk_disable_unprepare(hdmi->clk_ddc);
+
+	return ret;
+}
+
+static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master,
+				 void *data)
+{
+	return dw_hdmi_unbind(dev);
+}
+
+static const struct component_ops sun8i_dw_hdmi_ops = {
+	.bind	= sun8i_dw_hdmi_bind,
+	.unbind	= sun8i_dw_hdmi_unbind,
+};
+
+static int sun8i_dw_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &sun8i_dw_hdmi_ops);
+}
+
+static int sun8i_dw_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &sun8i_dw_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = {
+	{ .compatible = "allwinner,h3-dw-hdmi" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids);
+
+struct platform_driver sun8i_dw_hdmi_pltfm_driver = {
+	.probe  = sun8i_dw_hdmi_probe,
+	.remove = sun8i_dw_hdmi_remove,
+	.driver = {
+		.name = "sun8i-dw-hdmi",
+		.of_match_table = sun8i_dw_hdmi_dt_ids,
+	},
+};
+module_platform_driver(sun8i_dw_hdmi_pltfm_driver);
+
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_DESCRIPTION("Allwinner H3 DW HDMI bridge");
+MODULE_LICENSE("GPL");
-- 
2.13.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

WARNING: multiple messages have this Message-ID (diff)
From: icenowy@aosc.io (Icenowy Zheng)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 11/13] [NOT FOR REVIEW NOW] drm: sun4i: Add a glue for the DesignWare HDMI controller in H3
Date: Tue,  1 Aug 2017 21:13:02 +0800	[thread overview]
Message-ID: <20170801131304.7741-12-icenowy@aosc.io> (raw)
In-Reply-To: <20170801131304.7741-1-icenowy@aosc.io>

From: Jernej Skrabec <jernej.skrabec@siol.net>

Allwinner H3 features DesignWare HDMI Transmitter paired with custom
PHY.

For now, only video is supported by the driver. However, audio and CEC
are also supported by the hardware.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 drivers/gpu/drm/sun4i/Kconfig         |   9 +
 drivers/gpu/drm/sun4i/Makefile        |   1 +
 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c | 462 ++++++++++++++++++++++++++++++++++
 3 files changed, 472 insertions(+)
 create mode 100644 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c

diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index 06f05302ee75..589502ffe31a 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -40,6 +40,15 @@ config DRM_SUN4I_BACKEND
 	  do some alpha blending and feed graphics to TCON. If M is
 	  selected the module will be called sun4i-backend.
 
+config DRM_SUN8I_DW_HDMI
+	tristate "Support for Allwinner version of DesignWare HDMI"
+	depends on DRM_SUN4I
+	select DRM_DW_HDMI
+	help
+	  Choose this option if you have an Allwinner SoC with the
+	  DesignWare HDMI controller with custom HDMI PHY. If M is
+	  selected the module will be called sun8i_dw_hdmi.
+
 config DRM_SUN8I_MIXER
 	tristate "Support for Allwinner Display Engine 2.0 Mixer"
 	default MACH_SUN8I
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 43c753cafc88..9c56173bf140 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_DRM_SUN4I)		+= sun4i_tv.o
 obj-$(CONFIG_DRM_SUN4I_BACKEND)		+= sun4i-backend.o
 obj-$(CONFIG_DRM_SUN4I_HDMI)	+= sun4i-drm-hdmi.o
 obj-$(CONFIG_DRM_SUN8I_MIXER)		+= sun8i-mixer.o
+obj-$(CONFIG_DRM_SUN8I_DW_HDMI)	+= sun8i_dw_hdmi.o
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
new file mode 100644
index 000000000000..fa1ecbcf08b8
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2017, Jernej Skrabec <jernej.skrabec@siol.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/drm_of.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/bridge/dw_hdmi.h>
+
+#include "sun4i_crtc.h"
+#include "sun4i_tcon.h"
+
+#define SUN8I_HDMI_PHY_REG_POL		0x0000
+
+#define SUN8I_HDMI_PHY_REG_READ_EN	0x0010
+#define SUN8I_HDMI_PHY_REG_READ_EN_MAGIC	0x54524545
+
+#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE	0x0014
+#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC	0x42494E47
+
+#define SUN8I_HDMI_PHY_REG_CTRL		0x0020
+#define SUN8I_HDMI_PHY_REG_UNK1		0x0024
+#define SUN8I_HDMI_PHY_REG_UNK2		0x0028
+#define SUN8I_HDMI_PHY_REG_PLL		0x002c
+#define SUN8I_HDMI_PHY_REG_CLK		0x0030
+#define SUN8I_HDMI_PHY_REG_UNK3		0x0034
+
+#define SUN8I_HDMI_PHY_REG_STATUS	0x0038
+#define SUN8I_HDMI_PHY_REG_STATUS_READY		BIT(7)
+#define SUN8I_HDMI_PHY_REG_STATUS_HPD		BIT(19)
+
+#define to_sun8i_dw_hdmi(x)	container_of(x, struct sun8i_dw_hdmi, x)
+#define set_bits(p, v)		writel(readl(p) | (v), p)
+
+struct sun8i_dw_hdmi {
+	struct clk *clk_ddc;
+	struct clk *clk_hdmi;
+	struct device *dev;
+	struct drm_encoder encoder;
+	void __iomem *phy_base;
+	struct dw_hdmi_plat_data plat_data;
+	struct reset_control *rst_ddc;
+	struct reset_control *rst_hdmi;
+};
+
+static u32 sun8i_dw_hdmi_get_divider(int clk_khz)
+{
+	/*
+	 * Due to missing documentaion of HDMI PHY, we know correct
+	 * settings only for following four PHY dividers. Select one
+	 * based on clock speed.
+	 */
+	if (clk_khz <= 27000)
+		return 11;
+	else if (clk_khz <= 74250)
+		return 4;
+	else if (clk_khz <= 148500)
+		return 2;
+	else
+		return 1;
+}
+
+static void sun8i_dw_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+
+	DRM_DEBUG_DRIVER("Disabling HDMI Output\n");
+
+	sun4i_tcon_channel_disable(tcon, 1);
+}
+
+static void sun8i_dw_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+
+	DRM_DEBUG_DRIVER("Enabling HDMI Output\n");
+
+	sun4i_tcon_channel_enable(tcon, 1);
+}
+
+static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+					   struct drm_display_mode *mode,
+					   struct drm_display_mode *adj_mode)
+{
+	struct sun8i_dw_hdmi *hdmi = to_sun8i_dw_hdmi(encoder);
+	struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
+	struct sun4i_tcon *tcon = crtc->tcon;
+	u32 div;
+
+	sun4i_tcon1_mode_set(tcon, mode);
+
+	div = sun8i_dw_hdmi_get_divider(mode->crtc_clock);
+	clk_set_rate(hdmi->clk_hdmi, mode->crtc_clock * 1000 * div);
+	clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+}
+
+static const struct drm_encoder_helper_funcs sun8i_dw_hdmi_encoder_helper_funcs = {
+	.mode_set = sun8i_dw_hdmi_encoder_mode_set,
+	.enable   = sun8i_dw_hdmi_encoder_enable,
+	.disable  = sun8i_dw_hdmi_encoder_disable,
+};
+
+static int sun8i_dw_hdmi_phy_init(struct dw_hdmi *hdmi_data, void *data,
+				  struct drm_display_mode *mode)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 div = sun8i_dw_hdmi_get_divider(mode->crtc_clock);
+	u32 val;
+
+	/*
+	 * Unfortunately, we don't know much about those magic
+	 * numbers. They are taken from Allwinner BSP driver.
+	 */
+
+	val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(val & ~0xf000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+
+	switch (div) {
+	case 1:
+		writel(0x30dc5fc0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x800863C0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(200);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		if (val < 0x3d)
+			set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+				 val + 2);
+		else
+			set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, 
+				 0x3f);
+		mdelay(100);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F8246B5, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 2:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x80084381, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063a800, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C485, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 4:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	case 11:
+		writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+		writel(0x8008430a, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+		mdelay(10);
+		writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+		mdelay(100);
+		val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+		val = (val & 0x1f800) >> 11;
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL,
+			 BIT(31) | BIT(30));
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+		writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+		writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+		writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+		break;
+	}
+
+	/*
+	 * Condition in original code is a bit weird. This is attempt
+	 * to make it more reasonable and it works. It could be that
+	 * bits and conditions are related and should be separated.
+	 */
+	if (!((mode->flags & DRM_MODE_FLAG_PHSYNC) &&
+	      (mode->flags & DRM_MODE_FLAG_PVSYNC))) {
+		set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_POL, 0x300);
+	}
+
+	return 0;
+}
+
+static void sun8i_dw_hdmi_phy_disable(struct dw_hdmi *hdmi_data, void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+
+	writel(7, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+}
+
+static enum drm_connector_status sun8i_dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi_data,
+							    void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 reg_val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+
+	return !!(reg_val & SUN8I_HDMI_PHY_REG_STATUS_HPD) ?
+		connector_status_connected : connector_status_disconnected;
+}
+
+static const struct dw_hdmi_phy_ops sun8i_dw_hdmi_phy_ops = {
+	.init = &sun8i_dw_hdmi_phy_init,
+	.disable = &sun8i_dw_hdmi_phy_disable,
+	.read_hpd = &sun8i_dw_hdmi_phy_read_hpd,
+};
+
+static void sun8i_dw_hdmi_pre_init(void *data)
+{
+	struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data;
+	u32 timeout = 20;
+	u32 val;
+
+	/*
+	 * HDMI PHY settings are taken as-is from Allwinner BSP code.
+	 * There is no documentation.
+	 */
+	writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(0));
+	udelay(5);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(16));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(1));
+	udelay(10);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(2));
+	udelay(5);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(3));
+	udelay(40);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(19));
+	udelay(100);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(18));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 7 << 4);
+
+	/* Note that Allwinner code doesn't fail in case of timeout */
+	while (!(readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS) &
+		SUN8I_HDMI_PHY_REG_STATUS_READY)) {
+		if (!timeout--) {
+			dev_warn(hdmi->dev, "HDMI PHY init timeout!\n");
+			break;
+		}
+		udelay(100);
+	}
+
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 0xf << 8);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(7));
+
+	writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL);
+	writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK);
+	mdelay(10);
+	writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3);
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25));
+	mdelay(100);
+	val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS);
+	val = (val & 0x1f800) >> 11;
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(31) | BIT(30));
+	set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val);
+	writel(0x01FF0F7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL);
+	writel(0x80639000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1);
+	writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2);
+
+	/* enable read access to HDMI controller */
+	writel(SUN8I_HDMI_PHY_REG_READ_EN_MAGIC,
+	       hdmi->phy_base + SUN8I_HDMI_PHY_REG_READ_EN);
+
+	/* descramble register offsets */
+	writel(SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC,
+	       hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNSCRAMBLE);
+}
+
+static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
+			      void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dw_hdmi_plat_data *plat_data;
+	struct drm_device *drm = data;
+	struct drm_encoder *encoder;
+	struct sun8i_dw_hdmi *hdmi;
+	struct resource *res;
+	int ret;
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	plat_data = &hdmi->plat_data;
+	hdmi->dev = &pdev->dev;
+	encoder = &hdmi->encoder;
+
+	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm,
+							     dev->of_node);
+	/*
+	 * If we failed to find the CRTC(s) which this encoder is
+	 * supposed to be connected to, it's because the CRTC has
+	 * not been registered yet.  Defer probing, and hope that
+	 * the required CRTC is added later.
+	 */
+	if (encoder->possible_crtcs == 0)
+		return -EPROBE_DEFER;
+
+	/* resource 0 is the memory region for the core controller */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	hdmi->phy_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->phy_base))
+		return PTR_ERR(hdmi->phy_base);
+
+	hdmi->clk_hdmi = devm_clk_get(dev, "isfr");
+	if (IS_ERR(hdmi->clk_hdmi)) {
+		dev_err(dev, "Could not get hdmi clock\n");
+		return PTR_ERR(hdmi->clk_hdmi);
+	}
+
+	hdmi->clk_ddc = devm_clk_get(dev, "iddc");
+	if (IS_ERR(hdmi->clk_ddc)) {
+		dev_err(dev, "Could not get ddc clock\n");
+		return PTR_ERR(hdmi->clk_ddc);
+	}
+
+	hdmi->rst_hdmi = devm_reset_control_get(dev, "hdmi");
+	if (IS_ERR(hdmi->rst_hdmi)) {
+		dev_err(dev, "Could not get hdmi reset control\n");
+		return PTR_ERR(hdmi->rst_hdmi);
+	}
+
+	hdmi->rst_ddc = devm_reset_control_get(dev, "ddc");
+	if (IS_ERR(hdmi->rst_ddc)) {
+		dev_err(dev, "Could not get dw-hdmi reset control\n");
+		return PTR_ERR(hdmi->rst_ddc);
+	}
+
+	ret = clk_prepare_enable(hdmi->clk_ddc);
+	if (ret) {
+		dev_err(dev, "Cannot enable DDC clock: %d\n", ret);
+		return ret;
+	}
+
+	ret = reset_control_deassert(hdmi->rst_hdmi);
+	if (ret) {
+		dev_err(dev, "Could not deassert hdmi reset control\n");
+		goto err_ddc_clk;
+	}
+
+	ret = reset_control_deassert(hdmi->rst_ddc);
+	if (ret) {
+		dev_err(dev, "Could not deassert ddc reset control\n");
+		goto err_assert_hdmi_reset;
+	}
+
+	drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs);
+	drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs,
+			 DRM_MODE_ENCODER_TMDS, NULL);
+
+	plat_data->pre_init = &sun8i_dw_hdmi_pre_init,
+	plat_data->pre_init_data = hdmi;
+	plat_data->phy_ops = &sun8i_dw_hdmi_phy_ops,
+	plat_data->phy_name = "sun8i_dw_hdmi_phy",
+	plat_data->phy_data = hdmi;
+
+	ret = dw_hdmi_bind(pdev, encoder, plat_data);
+
+	/*
+	 * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
+	 * which would have called the encoder cleanup.  Do it manually.
+	 */
+	if (ret)
+		goto cleanup_encoder;
+
+	return 0;
+
+cleanup_encoder:
+	drm_encoder_cleanup(encoder);
+	reset_control_assert(hdmi->rst_ddc);
+err_assert_hdmi_reset:
+	reset_control_assert(hdmi->rst_hdmi);
+err_ddc_clk:
+	clk_disable_unprepare(hdmi->clk_ddc);
+
+	return ret;
+}
+
+static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master,
+				 void *data)
+{
+	return dw_hdmi_unbind(dev);
+}
+
+static const struct component_ops sun8i_dw_hdmi_ops = {
+	.bind	= sun8i_dw_hdmi_bind,
+	.unbind	= sun8i_dw_hdmi_unbind,
+};
+
+static int sun8i_dw_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &sun8i_dw_hdmi_ops);
+}
+
+static int sun8i_dw_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &sun8i_dw_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = {
+	{ .compatible = "allwinner,h3-dw-hdmi" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids);
+
+struct platform_driver sun8i_dw_hdmi_pltfm_driver = {
+	.probe  = sun8i_dw_hdmi_probe,
+	.remove = sun8i_dw_hdmi_remove,
+	.driver = {
+		.name = "sun8i-dw-hdmi",
+		.of_match_table = sun8i_dw_hdmi_dt_ids,
+	},
+};
+module_platform_driver(sun8i_dw_hdmi_pltfm_driver);
+
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_DESCRIPTION("Allwinner H3 DW HDMI bridge");
+MODULE_LICENSE("GPL");
-- 
2.13.0

  parent reply	other threads:[~2017-08-01 13:15 UTC|newest]

Thread overview: 108+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-08-01 13:12 [PATCH 00/13] Allwinner H3 DE2 basical support Icenowy Zheng
2017-08-01 13:12 ` Icenowy Zheng
2017-08-01 13:12 ` Icenowy Zheng
2017-08-01 13:12 ` [PATCH 01/13] dt-bindings: update the binding for Allwinner H3 DE2 support Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-02  4:53   ` [linux-sunxi] " Jernej Škrabec
2017-08-02  4:53     ` Jernej Škrabec
2017-08-02  4:53     ` Jernej Škrabec
2017-08-02  5:02     ` [linux-sunxi] " icenowy
2017-08-02  5:02       ` icenowy at aosc.io
2017-08-02  5:02       ` icenowy-h8G6r0blFSE
2017-08-02 19:06       ` [linux-sunxi] " Jernej Škrabec
2017-08-02 19:06         ` Jernej Škrabec
2017-08-02 19:06         ` Jernej Škrabec
2017-08-02 22:49         ` [linux-sunxi] " Icenowy Zheng
2017-08-02 22:49           ` Icenowy Zheng
2017-08-02 22:49           ` Icenowy Zheng
2017-08-10  0:21         ` [linux-sunxi] " Rob Herring
2017-08-10  0:21           ` Rob Herring
2017-08-10  0:21           ` Rob Herring
2017-08-16 21:46           ` [linux-sunxi] " Jernej Škrabec
2017-08-16 21:46             ` Jernej Škrabec
2017-08-10  0:18   ` Rob Herring
2017-08-10  0:18     ` Rob Herring
2017-08-10  0:18     ` Rob Herring
2017-08-01 13:12 ` [PATCH 02/13] drm: sun4i: add support for H3 mixers Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12 ` [PATCH 03/13] drm: sun4i: add support for H3's TCON Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-04  3:56   ` [linux-sunxi] " Chen-Yu Tsai
2017-08-04  3:56     ` Chen-Yu Tsai
2017-08-04  3:56     ` Chen-Yu Tsai
2017-08-04  3:56     ` Chen-Yu Tsai
2017-08-01 13:12 ` [PATCH 04/13] drm: sun4i: add compatible for H3 display engine Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12 ` [PATCH 05/13] clk: sunxi-ng: allow CLK_DE to set CLK_PLL_DE for H3 Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12 ` [PATCH 06/13] clk: sunxi-ng: export " Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12 ` [PATCH 07/13] ARM: sun8i: h3: add display engine pipeline barebone Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-02  4:47   ` [linux-sunxi] " Jernej Škrabec
2017-08-02  4:47     ` Jernej Škrabec
2017-08-02  5:07     ` icenowy
2017-08-02  5:07       ` icenowy at aosc.io
2017-08-02  5:07       ` icenowy-h8G6r0blFSE
2017-08-21  8:30       ` Maxime Ripard
2017-08-21  8:30         ` Maxime Ripard
2017-08-01 13:12 ` [PATCH 08/13] [NOT FOR REVIEW NOW] drm: bridge: Enable polling hpd event in dw_hdmi Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:12   ` Icenowy Zheng
2017-08-01 13:13 ` [PATCH 09/13] [NOT FOR REVIEW NOW] drm: bridge: Add a pre_init function for the dw_hdmi driver Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13 ` [PATCH 10/13] [NOT FOR REVIEW NOW] clk: sunxi: Add CLK_SET_RATE_PARENT flag for H3 HDMI clock Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-04  4:15   ` [linux-sunxi] " Chen-Yu Tsai
2017-08-04  4:15     ` Chen-Yu Tsai
2017-08-04  4:15     ` Chen-Yu Tsai
2017-08-04  4:16     ` Icenowy Zheng
2017-08-04  4:16       ` Icenowy Zheng
2017-08-04  4:16       ` Icenowy Zheng
2017-08-04  4:29       ` Chen-Yu Tsai
2017-08-04  4:29         ` Chen-Yu Tsai
2017-08-04  8:59         ` Jernej Škrabec
2017-08-04  8:59           ` Jernej Škrabec
2017-08-04  9:03           ` Icenowy Zheng
2017-08-04  9:03             ` Icenowy Zheng
2017-08-04  9:39             ` Chen-Yu Tsai
2017-08-04  9:39               ` Chen-Yu Tsai
2017-08-04  9:27           ` Chen-Yu Tsai
2017-08-04  9:27             ` Chen-Yu Tsai
2017-08-04 13:49             ` Jernej Škrabec
2017-08-04 13:49               ` Jernej Škrabec
2017-08-04 14:16               ` Chen-Yu Tsai
2017-08-04 14:16                 ` Chen-Yu Tsai
2017-08-01 13:13 ` Icenowy Zheng [this message]
2017-08-01 13:13   ` [PATCH 11/13] [NOT FOR REVIEW NOW] drm: sun4i: Add a glue for the DesignWare HDMI controller in H3 Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13 ` [PATCH 12/13] [NOT FOR REVIEW NOW] ARM: sun8i: h3: enable DesignWare HDMI controller Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13 ` [PATCH 13/13] [NOT FOR REVIEW NOW] ARM: sun8i: h3: enable HDMI output on Orange Pi PC Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-01 13:13   ` Icenowy Zheng
2017-08-02  4:46 ` [linux-sunxi] [PATCH 00/13] Allwinner H3 DE2 basical support Chen-Yu Tsai
2017-08-02  4:46   ` Chen-Yu Tsai
2017-08-02  4:46   ` Chen-Yu Tsai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170801131304.7741-12-icenowy@aosc.io \
    --to=icenowy@aosc.io \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jernej.skrabec@siol.net \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sunxi@googlegroups.com \
    --cc=maxime.ripard@free-electrons.com \
    --cc=robh+dt@kernel.org \
    --cc=wens@csie.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.