Blob Blame History Raw
From: Chen-Yu Tsai <wens@csie.org>
Date: Tue, 10 Oct 2017 11:20:04 +0800
Subject: drm/sun4i: hdmi: Add support for controller hardware variants
Git-commit: 939d749ad6649c4123daf63a8bc053ea97ad2218
Patch-mainline: v4.15-rc1
References: FATE#326289 FATE#326079 FATE#326049 FATE#322398 FATE#326166

The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:

  - Need different initial values for the PLL related registers

  - Different behavior of the DDC and TMDS clocks

  - Different register layout for the DDC portion

  - Separate DDC parent clock on the A31

  - Explicit reset control

For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.

A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.

Regmap fields are used to deal with the different register layout
of the DDC block.

Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org

Acked-by: Petr Tesarik <ptesarik@suse.com>
---
 drivers/gpu/drm/sun4i/sun4i_hdmi.h          |   72 ++++++++
 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  |   38 +++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      |  112 +++++++++++--
 drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c      |  227 ++++++++++++++++++++--------
 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c |   17 +-
 5 files changed, 369 insertions(+), 97 deletions(-)

--- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -14,6 +14,7 @@
 
 #include <drm/drm_connector.h>
 #include <drm/drm_encoder.h>
+#include <linux/regmap.h>
 
 #define SUN4I_HDMI_CTRL_REG		0x004
 #define SUN4I_HDMI_CTRL_ENABLE			BIT(31)
@@ -157,6 +158,55 @@ enum sun4i_hdmi_pkt_type {
 	SUN4I_HDMI_PKT_END = 15,
 };
 
+struct sun4i_hdmi_variant {
+	bool has_ddc_parent_clk;
+	bool has_reset_control;
+
+	u32 pad_ctrl0_init_val;
+	u32 pad_ctrl1_init_val;
+	u32 pll_ctrl_init_val;
+
+	struct reg_field ddc_clk_reg;
+	u8 ddc_clk_pre_divider;
+	u8 ddc_clk_m_offset;
+
+	u8 tmds_clk_div_offset;
+
+	/* Register fields for I2C adapter */
+	struct reg_field	field_ddc_en;
+	struct reg_field	field_ddc_start;
+	struct reg_field	field_ddc_reset;
+	struct reg_field	field_ddc_addr_reg;
+	struct reg_field	field_ddc_slave_addr;
+	struct reg_field	field_ddc_int_mask;
+	struct reg_field	field_ddc_int_status;
+	struct reg_field	field_ddc_fifo_clear;
+	struct reg_field	field_ddc_fifo_rx_thres;
+	struct reg_field	field_ddc_fifo_tx_thres;
+	struct reg_field	field_ddc_byte_count;
+	struct reg_field	field_ddc_cmd;
+	struct reg_field	field_ddc_sda_en;
+	struct reg_field	field_ddc_sck_en;
+
+	/* DDC FIFO register offset */
+	u32			ddc_fifo_reg;
+
+	/*
+	 * DDC FIFO threshold boundary conditions
+	 *
+	 * This is used to cope with the threshold boundary condition
+	 * being slightly different on sun5i and sun6i.
+	 *
+	 * On sun5i the threshold is exclusive, i.e. does not include,
+	 * the value of the threshold. ( > for RX; < for TX )
+	 * On sun6i the threshold is inclusive, i.e. includes, the
+	 * value of the threshold. ( >= for RX; <= for TX )
+	 */
+	bool			ddc_fifo_thres_incl;
+
+	bool			ddc_fifo_has_dir;
+};
+
 struct sun4i_hdmi {
 	struct drm_connector	connector;
 	struct drm_encoder	encoder;
@@ -165,9 +215,13 @@ struct sun4i_hdmi {
 	void __iomem		*base;
 	struct regmap		*regmap;
 
+	/* Reset control */
+	struct reset_control	*reset;
+
 	/* Parent clocks */
 	struct clk		*bus_clk;
 	struct clk		*mod_clk;
+	struct clk		*ddc_parent_clk;
 	struct clk		*pll0_clk;
 	struct clk		*pll1_clk;
 
@@ -177,9 +231,27 @@ struct sun4i_hdmi {
 
 	struct i2c_adapter	*i2c;
 
+	/* Regmap fields for I2C adapter */
+	struct regmap_field	*field_ddc_en;
+	struct regmap_field	*field_ddc_start;
+	struct regmap_field	*field_ddc_reset;
+	struct regmap_field	*field_ddc_addr_reg;
+	struct regmap_field	*field_ddc_slave_addr;
+	struct regmap_field	*field_ddc_int_mask;
+	struct regmap_field	*field_ddc_int_status;
+	struct regmap_field	*field_ddc_fifo_clear;
+	struct regmap_field	*field_ddc_fifo_rx_thres;
+	struct regmap_field	*field_ddc_fifo_tx_thres;
+	struct regmap_field	*field_ddc_byte_count;
+	struct regmap_field	*field_ddc_cmd;
+	struct regmap_field	*field_ddc_sda_en;
+	struct regmap_field	*field_ddc_sck_en;
+
 	struct sun4i_drv	*drv;
 
 	bool			hdmi_monitor;
+
+	const struct sun4i_hdmi_variant	*variant;
 };
 
 int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
@@ -11,6 +11,7 @@
  */
 
 #include <linux/clk-provider.h>
+#include <linux/regmap.h>
 
 #include "sun4i_tcon.h"
 #include "sun4i_hdmi.h"
@@ -18,6 +19,9 @@
 struct sun4i_ddc {
 	struct clk_hw		hw;
 	struct sun4i_hdmi	*hdmi;
+	struct regmap_field	*reg;
+	u8			pre_div;
+	u8			m_offset;
 };
 
 static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
@@ -27,6 +31,8 @@ static inline struct sun4i_ddc *hw_to_dd
 
 static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
 					    unsigned long parent_rate,
+					    const u8 pre_div,
+					    const u8 m_offset,
 					    u8 *m, u8 *n)
 {
 	unsigned long best_rate = 0;
@@ -36,7 +42,8 @@ static unsigned long sun4i_ddc_calc_divi
 		for (_n = 0; _n < 8; _n++) {
 			unsigned long tmp_rate;
 
-			tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
+			tmp_rate = (((parent_rate / pre_div) / 10) >> _n) /
+				(_m + m_offset);
 
 			if (tmp_rate > rate)
 				continue;
@@ -60,21 +67,25 @@ static unsigned long sun4i_ddc_calc_divi
 static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
 				 unsigned long *prate)
 {
-	return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
+	struct sun4i_ddc *ddc = hw_to_ddc(hw);
+
+	return sun4i_ddc_calc_divider(rate, *prate, ddc->pre_div,
+				      ddc->m_offset, NULL, NULL);
 }
 
 static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
 					    unsigned long parent_rate)
 {
 	struct sun4i_ddc *ddc = hw_to_ddc(hw);
-	u32 reg;
+	unsigned int reg;
 	u8 m, n;
 
-	reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
-	m = (reg >> 3) & 0x7;
+	regmap_field_read(ddc->reg, &reg);
+	m = (reg >> 3) & 0xf;
 	n = reg & 0x7;
 
-	return (((parent_rate / 2) / 10) >> n) / (m + 1);
+	return (((parent_rate / ddc->pre_div) / 10) >> n) /
+	       (m + ddc->m_offset);
 }
 
 static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -83,10 +94,12 @@ static int sun4i_ddc_set_rate(struct clk
 	struct sun4i_ddc *ddc = hw_to_ddc(hw);
 	u8 div_m, div_n;
 
-	sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
+	sun4i_ddc_calc_divider(rate, parent_rate, ddc->pre_div,
+			       ddc->m_offset, &div_m, &div_n);
 
-	writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
-	       ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+	regmap_field_write(ddc->reg,
+			   SUN4I_HDMI_DDC_CLK_M(div_m) |
+			   SUN4I_HDMI_DDC_CLK_N(div_n));
 
 	return 0;
 }
@@ -111,6 +124,11 @@ int sun4i_ddc_create(struct sun4i_hdmi *
 	if (!ddc)
 		return -ENOMEM;
 
+	ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					   hdmi->variant->ddc_clk_reg);
+	if (IS_ERR(ddc->reg))
+		return PTR_ERR(ddc->reg);
+
 	init.name = "hdmi-ddc";
 	init.ops = &sun4i_ddc_ops;
 	init.parent_names = &parent_name;
@@ -118,6 +136,8 @@ int sun4i_ddc_create(struct sun4i_hdmi *
 
 	ddc->hdmi = hdmi;
 	ddc->hw.init = &init;
+	ddc->pre_div = hdmi->variant->ddc_clk_pre_divider;
+	ddc->m_offset = hdmi->variant->ddc_clk_m_offset;
 
 	hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
 	if (IS_ERR(hdmi->ddc_clk))
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -20,9 +20,11 @@
 #include <linux/clk.h>
 #include <linux/component.h>
 #include <linux/iopoll.h>
+#include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/regmap.h>
+#include <linux/reset.h>
 
 #include "sun4i_backend.h"
 #include "sun4i_crtc.h"
@@ -268,6 +270,60 @@ static const struct cec_pin_ops sun4i_hd
 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
 };
 
+#define SUN4I_HDMI_PAD_CTRL1_MASK	(GENMASK(24, 7) | GENMASK(5, 0))
+#define SUN4I_HDMI_PLL_CTRL_MASK	(GENMASK(31, 8) | GENMASK(3, 0))
+
+static const struct sun4i_hdmi_variant sun5i_variant = {
+	.pad_ctrl0_init_val	= SUN4I_HDMI_PAD_CTRL0_TXEN |
+				  SUN4I_HDMI_PAD_CTRL0_CKEN |
+				  SUN4I_HDMI_PAD_CTRL0_PWENG |
+				  SUN4I_HDMI_PAD_CTRL0_PWEND |
+				  SUN4I_HDMI_PAD_CTRL0_PWENC |
+				  SUN4I_HDMI_PAD_CTRL0_LDODEN |
+				  SUN4I_HDMI_PAD_CTRL0_LDOCEN |
+				  SUN4I_HDMI_PAD_CTRL0_BIASEN,
+	.pad_ctrl1_init_val	= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
+				  SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
+				  SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
+				  SUN4I_HDMI_PAD_CTRL1_REG_DEN |
+				  SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
+				  SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
+				  SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
+				  SUN4I_HDMI_PAD_CTRL1_AMP_OPT,
+	.pll_ctrl_init_val	= SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
+				  SUN4I_HDMI_PLL_CTRL_CS(7) |
+				  SUN4I_HDMI_PLL_CTRL_CP_S(15) |
+				  SUN4I_HDMI_PLL_CTRL_S(7) |
+				  SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) |
+				  SUN4I_HDMI_PLL_CTRL_SDIV2 |
+				  SUN4I_HDMI_PLL_CTRL_LDO2_EN |
+				  SUN4I_HDMI_PLL_CTRL_LDO1_EN |
+				  SUN4I_HDMI_PLL_CTRL_HV_IS_33 |
+				  SUN4I_HDMI_PLL_CTRL_BWS |
+				  SUN4I_HDMI_PLL_CTRL_PLL_EN,
+
+	.ddc_clk_reg		= REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6),
+	.ddc_clk_pre_divider	= 2,
+	.ddc_clk_m_offset	= 1,
+
+	.field_ddc_en		= REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31),
+	.field_ddc_start	= REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30),
+	.field_ddc_reset	= REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0),
+	.field_ddc_addr_reg	= REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31),
+	.field_ddc_slave_addr	= REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6),
+	.field_ddc_int_status	= REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8),
+	.field_ddc_fifo_clear	= REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31),
+	.field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
+	.field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
+	.field_ddc_byte_count	= REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9),
+	.field_ddc_cmd		= REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2),
+	.field_ddc_sda_en	= REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9),
+	.field_ddc_sck_en	= REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8),
+
+	.ddc_fifo_reg		= SUN4I_HDMI_DDC_FIFO_DATA_REG,
+	.ddc_fifo_has_dir	= true,
+};
+
 static const struct regmap_config sun4i_hdmi_regmap_config = {
 	.reg_bits	= 32,
 	.val_bits	= 32,
@@ -293,6 +349,10 @@ static int sun4i_hdmi_bind(struct device
 	hdmi->dev = dev;
 	hdmi->drv = drv;
 
+	hdmi->variant = of_device_get_match_data(dev);
+	if (!hdmi->variant)
+		return -EINVAL;
+
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	hdmi->base = devm_ioremap_resource(dev, res);
 	if (IS_ERR(hdmi->base)) {
@@ -300,10 +360,25 @@ static int sun4i_hdmi_bind(struct device
 		return PTR_ERR(hdmi->base);
 	}
 
+	if (hdmi->variant->has_reset_control) {
+		hdmi->reset = devm_reset_control_get(dev, NULL);
+		if (IS_ERR(hdmi->reset)) {
+			dev_err(dev, "Couldn't get the HDMI reset control\n");
+			return PTR_ERR(hdmi->reset);
+		}
+
+		ret = reset_control_deassert(hdmi->reset);
+		if (ret) {
+			dev_err(dev, "Couldn't deassert HDMI reset\n");
+			return ret;
+		}
+	}
+
 	hdmi->bus_clk = devm_clk_get(dev, "ahb");
 	if (IS_ERR(hdmi->bus_clk)) {
 		dev_err(dev, "Couldn't get the HDMI bus clock\n");
-		return PTR_ERR(hdmi->bus_clk);
+		ret = PTR_ERR(hdmi->bus_clk);
+		goto err_assert_reset;
 	}
 	clk_prepare_enable(hdmi->bus_clk);
 
@@ -342,12 +417,19 @@ static int sun4i_hdmi_bind(struct device
 		goto err_disable_mod_clk;
 	}
 
+	if (hdmi->variant->has_ddc_parent_clk) {
+		hdmi->ddc_parent_clk = devm_clk_get(dev, "ddc");
+		if (IS_ERR(hdmi->ddc_parent_clk)) {
+			dev_err(dev, "Couldn't get the HDMI DDC clock\n");
+			return PTR_ERR(hdmi->ddc_parent_clk);
+		}
+	} else {
+		hdmi->ddc_parent_clk = hdmi->tmds_clk;
+	}
+
 	writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
 
-	writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
-	       SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
-	       SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
-	       SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
+	writel(hdmi->variant->pad_ctrl0_init_val,
 	       hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
 
 	/*
@@ -357,24 +439,12 @@ static int sun4i_hdmi_bind(struct device
 	 */
 	reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
 	reg &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
-	reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
-		SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
-		SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
-		SUN4I_HDMI_PAD_CTRL1_REG_DEN |
-		SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
-		SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
-		SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
-		SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
+	reg |= hdmi->variant->pad_ctrl1_init_val;
 	writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
 
 	reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
 	reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK;
-	reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
-		SUN4I_HDMI_PLL_CTRL_CP_S(15) | SUN4I_HDMI_PLL_CTRL_S(7) |
-		SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
-		SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
-		SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
-		SUN4I_HDMI_PLL_CTRL_PLL_EN;
+	reg |= hdmi->variant->pll_ctrl_init_val;
 	writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
 
 	ret = sun4i_hdmi_i2c_create(dev, hdmi);
@@ -444,6 +514,8 @@ err_disable_mod_clk:
 	clk_disable_unprepare(hdmi->mod_clk);
 err_disable_bus_clk:
 	clk_disable_unprepare(hdmi->bus_clk);
+err_assert_reset:
+	reset_control_assert(hdmi->reset);
 	return ret;
 }
 
@@ -478,7 +550,7 @@ static int sun4i_hdmi_remove(struct plat
 }
 
 static const struct of_device_id sun4i_hdmi_of_table[] = {
-	{ .compatible = "allwinner,sun5i-a10s-hdmi" },
+	{ .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c
@@ -25,8 +25,6 @@
 
 /* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */
 #define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX
-/* FIFO request bit is set when FIFO level is below TX_THRESHOLD during write */
-#define TX_THRESHOLD 1
 
 static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read)
 {
@@ -39,27 +37,36 @@ static int fifo_transfer(struct sun4i_hd
 			 SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
 			 SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE;
 	u32 reg;
+	/*
+	 * If threshold is inclusive, then the FIFO may only have
+	 * RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1.
+	 */
+	int read_len = RX_THRESHOLD +
+		(hdmi->variant->ddc_fifo_thres_incl ? 0 : 1);
 
-	/* Limit transfer length by FIFO threshold */
-	len = min_t(int, len, read ? (RX_THRESHOLD + 1) :
-			      (SUN4I_HDMI_DDC_FIFO_SIZE - TX_THRESHOLD + 1));
+	/*
+	 * Limit transfer length by FIFO threshold or FIFO size.
+	 * For TX the threshold is for an empty FIFO.
+	 */
+	len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE);
 
 	/* Wait until error, FIFO request bit set or transfer complete */
-	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG, reg,
-			       reg & mask, len * byte_time_ns, 100000))
+	if (regmap_field_read_poll_timeout(hdmi->field_ddc_int_status, reg,
+					   reg & mask, len * byte_time_ns,
+					   100000))
 		return -ETIMEDOUT;
 
 	if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK)
 		return -EIO;
 
 	if (read)
-		readsb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
+		readsb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len);
 	else
-		writesb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
+		writesb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len);
 
-	/* Clear FIFO request bit */
-	writel(SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST,
-	       hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
+	/* Clear FIFO request bit by forcing a write to that bit */
+	regmap_field_force_write(hdmi->field_ddc_int_status,
+				 SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST);
 
 	return len;
 }
@@ -70,50 +77,52 @@ static int xfer_msg(struct sun4i_hdmi *h
 	u32 reg;
 
 	/* Set FIFO direction */
-	reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
-	reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
-	reg |= (msg->flags & I2C_M_RD) ?
-	       SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
-	       SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
-	writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	if (hdmi->variant->ddc_fifo_has_dir) {
+		reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+		reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
+		reg |= (msg->flags & I2C_M_RD) ?
+		       SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
+		       SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
+		writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	}
+
+	/* Clear address register (not cleared by soft reset) */
+	regmap_field_write(hdmi->field_ddc_addr_reg, 0);
 
 	/* Set I2C address */
-	writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr),
-	       hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
+	regmap_field_write(hdmi->field_ddc_slave_addr, msg->addr);
 
-	/* Set FIFO RX/TX thresholds and clear FIFO */
-	reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
-	reg |= SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR;
-	reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK;
-	reg |= SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(RX_THRESHOLD);
-	reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK;
-	reg |= SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(TX_THRESHOLD);
-	writel(reg, hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
-	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG,
-			       reg,
-			       !(reg & SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR),
-			       100, 2000))
+	/*
+	 * Set FIFO RX/TX thresholds and clear FIFO
+	 *
+	 * If threshold is inclusive, we can set the TX threshold to
+	 * 0 instead of 1.
+	 */
+	regmap_field_write(hdmi->field_ddc_fifo_tx_thres,
+			   hdmi->variant->ddc_fifo_thres_incl ? 0 : 1);
+	regmap_field_write(hdmi->field_ddc_fifo_rx_thres, RX_THRESHOLD);
+	regmap_field_write(hdmi->field_ddc_fifo_clear, 1);
+	if (regmap_field_read_poll_timeout(hdmi->field_ddc_fifo_clear,
+					   reg, !reg, 100, 2000))
 		return -EIO;
 
 	/* Set transfer length */
-	writel(msg->len, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
+	regmap_field_write(hdmi->field_ddc_byte_count, msg->len);
 
 	/* Set command */
-	writel(msg->flags & I2C_M_RD ?
-	       SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
-	       SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE,
-	       hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
-
-	/* Clear interrupt status bits */
-	writel(SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
-	       SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
-	       SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE,
-	       hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
+	regmap_field_write(hdmi->field_ddc_cmd,
+			   msg->flags & I2C_M_RD ?
+			   SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
+			   SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE);
+
+	/* Clear interrupt status bits by forcing a write */
+	regmap_field_force_write(hdmi->field_ddc_int_status,
+				 SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
+				 SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
+				 SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE);
 
 	/* Start command */
-	reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
-	writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
-	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	regmap_field_write(hdmi->field_ddc_start, 1);
 
 	/* Transfer bytes */
 	for (i = 0; i < msg->len; i += len) {
@@ -124,14 +133,12 @@ static int xfer_msg(struct sun4i_hdmi *h
 	}
 
 	/* Wait for command to finish */
-	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG,
-			       reg,
-			       !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
-			       100, 100000))
+	if (regmap_field_read_poll_timeout(hdmi->field_ddc_start,
+					   reg, !reg, 100, 100000))
 		return -EIO;
 
 	/* Check for errors */
-	reg = readl(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
+	regmap_field_read(hdmi->field_ddc_int_status, &reg);
 	if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) ||
 	    !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) {
 		return -EIO;
@@ -154,20 +161,21 @@ static int sun4i_hdmi_i2c_xfer(struct i2
 			return -EINVAL;
 	}
 
+	/* DDC clock needs to be enabled for the module to work */
+	clk_prepare_enable(hdmi->ddc_clk);
+	clk_set_rate(hdmi->ddc_clk, 100000);
+
 	/* Reset I2C controller */
-	writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
-	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
-	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
-			       !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
-			       100, 2000))
+	regmap_field_write(hdmi->field_ddc_en, 1);
+	regmap_field_write(hdmi->field_ddc_reset, 1);
+	if (regmap_field_read_poll_timeout(hdmi->field_ddc_reset,
+					   reg, !reg, 100, 2000)) {
+		clk_disable_unprepare(hdmi->ddc_clk);
 		return -EIO;
+	}
 
-	writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
-	       SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
-	       hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
-
-	clk_prepare_enable(hdmi->ddc_clk);
-	clk_set_rate(hdmi->ddc_clk, 100000);
+	regmap_field_write(hdmi->field_ddc_sck_en, 1);
+	regmap_field_write(hdmi->field_ddc_sda_en, 1);
 
 	for (i = 0; i < num; i++) {
 		err = xfer_msg(hdmi, &msgs[i]);
@@ -191,12 +199,105 @@ static const struct i2c_algorithm sun4i_
 	.functionality	= sun4i_hdmi_i2c_func,
 };
 
+static int sun4i_hdmi_init_regmap_fields(struct sun4i_hdmi *hdmi)
+{
+	hdmi->field_ddc_en =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_en);
+	if (IS_ERR(hdmi->field_ddc_en))
+		return PTR_ERR(hdmi->field_ddc_en);
+
+	hdmi->field_ddc_start =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_start);
+	if (IS_ERR(hdmi->field_ddc_start))
+		return PTR_ERR(hdmi->field_ddc_start);
+
+	hdmi->field_ddc_reset =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_reset);
+	if (IS_ERR(hdmi->field_ddc_reset))
+		return PTR_ERR(hdmi->field_ddc_reset);
+
+	hdmi->field_ddc_addr_reg =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_addr_reg);
+	if (IS_ERR(hdmi->field_ddc_addr_reg))
+		return PTR_ERR(hdmi->field_ddc_addr_reg);
+
+	hdmi->field_ddc_slave_addr =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_slave_addr);
+	if (IS_ERR(hdmi->field_ddc_slave_addr))
+		return PTR_ERR(hdmi->field_ddc_slave_addr);
+
+	hdmi->field_ddc_int_mask =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_int_mask);
+	if (IS_ERR(hdmi->field_ddc_int_mask))
+		return PTR_ERR(hdmi->field_ddc_int_mask);
+
+	hdmi->field_ddc_int_status =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_int_status);
+	if (IS_ERR(hdmi->field_ddc_int_status))
+		return PTR_ERR(hdmi->field_ddc_int_status);
+
+	hdmi->field_ddc_fifo_clear =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_fifo_clear);
+	if (IS_ERR(hdmi->field_ddc_fifo_clear))
+		return PTR_ERR(hdmi->field_ddc_fifo_clear);
+
+	hdmi->field_ddc_fifo_rx_thres =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_fifo_rx_thres);
+	if (IS_ERR(hdmi->field_ddc_fifo_rx_thres))
+		return PTR_ERR(hdmi->field_ddc_fifo_rx_thres);
+
+	hdmi->field_ddc_fifo_tx_thres =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_fifo_tx_thres);
+	if (IS_ERR(hdmi->field_ddc_fifo_tx_thres))
+		return PTR_ERR(hdmi->field_ddc_fifo_tx_thres);
+
+	hdmi->field_ddc_byte_count =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_byte_count);
+	if (IS_ERR(hdmi->field_ddc_byte_count))
+		return PTR_ERR(hdmi->field_ddc_byte_count);
+
+	hdmi->field_ddc_cmd =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_cmd);
+	if (IS_ERR(hdmi->field_ddc_cmd))
+		return PTR_ERR(hdmi->field_ddc_cmd);
+
+	hdmi->field_ddc_sda_en =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_sda_en);
+	if (IS_ERR(hdmi->field_ddc_sda_en))
+		return PTR_ERR(hdmi->field_ddc_sda_en);
+
+	hdmi->field_ddc_sck_en =
+		devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+					hdmi->variant->field_ddc_sck_en);
+	if (IS_ERR(hdmi->field_ddc_sck_en))
+		return PTR_ERR(hdmi->field_ddc_sck_en);
+
+	return 0;
+}
+
 int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi)
 {
 	struct i2c_adapter *adap;
 	int ret = 0;
 
-	ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
+	ret = sun4i_ddc_create(hdmi, hdmi->ddc_parent_clk);
+	if (ret)
+		return ret;
+
+	ret = sun4i_hdmi_init_regmap_fields(hdmi);
 	if (ret)
 		return ret;
 
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
@@ -18,6 +18,8 @@
 struct sun4i_tmds {
 	struct clk_hw		hw;
 	struct sun4i_hdmi	*hdmi;
+
+	u8			div_offset;
 };
 
 static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
@@ -28,6 +30,7 @@ static inline struct sun4i_tmds *hw_to_t
 
 static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
 					     unsigned long parent_rate,
+					     u8 div_offset,
 					     u8 *div,
 					     bool *half)
 {
@@ -35,7 +38,7 @@ static unsigned long sun4i_tmds_calc_div
 	u8 best_m = 0, m;
 	bool is_double;
 
-	for (m = 1; m < 16; m++) {
+	for (m = div_offset ?: 1; m < (16 + div_offset); m++) {
 		u8 d;
 
 		for (d = 1; d < 3; d++) {
@@ -67,6 +70,7 @@ static unsigned long sun4i_tmds_calc_div
 static int sun4i_tmds_determine_rate(struct clk_hw *hw,
 				     struct clk_rate_request *req)
 {
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
 	struct clk_hw *parent = NULL;
 	unsigned long best_parent = 0;
 	unsigned long rate = req->rate;
@@ -85,7 +89,8 @@ static int sun4i_tmds_determine_rate(str
 			continue;
 
 		for (i = 1; i < 3; i++) {
-			for (j = 1; j < 16; j++) {
+			for (j = tmds->div_offset ?: 1;
+			     j < (16 + tmds->div_offset); j++) {
 				unsigned long ideal = rate * i * j;
 				unsigned long rounded;
 
@@ -129,7 +134,7 @@ static unsigned long sun4i_tmds_recalc_r
 		parent_rate /= 2;
 
 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
-	reg = (reg >> 4) & 0xf;
+	reg = ((reg >> 4) & 0xf) + tmds->div_offset;
 	if (!reg)
 		reg = 1;
 
@@ -144,7 +149,8 @@ static int sun4i_tmds_set_rate(struct cl
 	u32 reg;
 	u8 div;
 
-	sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
+	sun4i_tmds_calc_divider(rate, parent_rate, tmds->div_offset,
+				&div, &half);
 
 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
 	reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
@@ -154,7 +160,7 @@ static int sun4i_tmds_set_rate(struct cl
 
 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
 	reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
-	writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
+	writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div - tmds->div_offset),
 	       tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
 
 	return 0;
@@ -221,6 +227,7 @@ int sun4i_tmds_create(struct sun4i_hdmi
 
 	tmds->hdmi = hdmi;
 	tmds->hw.init = &init;
+	tmds->div_offset = hdmi->variant->tmds_clk_div_offset;
 
 	hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
 	if (IS_ERR(hdmi->tmds_clk))