diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
index aef157d3b659..3057e31219f6 100644
--- 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>
 
 #include <media/cec-pin.h>
 
@@ -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,10 +231,28 @@ 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;
 	struct cec_adapter	*cec_adap;
+
+	const struct sun4i_hdmi_variant	*variant;
 };
 
 int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
index 4692e8c345ed..04f85b1cf922 100644
--- 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_ddc(struct clk_hw *hw)
 
 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_divider(unsigned long rate,
 		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_divider(unsigned long rate,
 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_hw *hw, unsigned long rate,
 	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 *hdmi, struct clk *parent)
 	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 *hdmi, struct clk *parent)
 
 	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))
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index 5ab811cda00e..114cbe60b3e6 100644
--- 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_hdmi_cec_pin_ops = {
 };
 #endif
 
+#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 *dev, struct device *master,
 	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 *dev, struct device *master,
 		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 *dev, struct device *master,
 		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 *dev, struct device *master,
 	 */
 	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 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
 	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 platform_device *pdev)
 }
 
 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);
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c
index 2e42d09ab42e..58e9d37e8c17 100644
--- 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_hdmi *hdmi, u8 *buf, int len, bool read)
 			 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 *hdmi, struct i2c_msg *msg)
 	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);
-
-	/* 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))
+	regmap_field_write(hdmi->field_ddc_slave_addr, msg->addr);
+
+	/*
+	 * 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);
+	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 */
-	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);
+	/* 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 *hdmi, struct i2c_msg *msg)
 	}
 
 	/* 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 i2c_adapter *adap,
 			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_hdmi_i2c_algorithm = {
 	.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;
 
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
index e8d4c311b80d..1b6b37aefc38 100644
--- 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_tmds(struct clk_hw *hw)
 
 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_divider(unsigned long rate,
 	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_divider(unsigned long rate,
 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(struct clk_hw *hw,
 			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_rate(struct clk_hw *hw,
 		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 clk_hw *hw, unsigned long rate,
 	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 clk_hw *hw, unsigned long rate,
 
 	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 *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))
