#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))