#include <drm/drm_connector.h>
 #include <drm/drm_encoder.h>
+#include <linux/regmap.h>
 
 #include <media/cec-pin.h>
 
        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;
        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;
 
 
        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);
 
  */
 
 #include <linux/clk-provider.h>
+#include <linux/regmap.h>
 
 #include "sun4i_tcon.h"
 #include "sun4i_hdmi.h"
 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)
 
 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;
                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;
 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, ®);
+       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,
        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;
 }
        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;
 
        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))
 
 #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"
 };
 #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,
        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)) {
                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);
 
                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);
 
        /*
         */
        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);
        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;
 }
 
 }
 
 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);
 
 
 /* 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)
 {
                         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;
 }
        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) {
        }
 
        /* 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, ®);
        if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) ||
            !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) {
                return -EIO;
                        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]);
        .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;
 
 
 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)
 
 static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
                                             unsigned long parent_rate,
+                                            u8 div_offset,
                                             u8 *div,
                                             bool *half)
 {
        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++) {
 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;
                        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;
 
                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;
 
        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;
 
        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;
 
        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))