Skip to content

Commit

Permalink
drm/sun4i: hdmi: Add support for controller hardware variants
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Chen-Yu Tsai authored and Maxime Ripard committed Oct 11, 2017
1 parent 68a48af commit 939d749
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 97 deletions.
72 changes: 72 additions & 0 deletions drivers/gpu/drm/sun4i/sun4i_hdmi.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>
#include <linux/regmap.h>

#include <media/cec-pin.h>

Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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);
Expand Down
38 changes: 29 additions & 9 deletions drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
*/

#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)
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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;
}
Expand All @@ -111,13 +124,20 @@ 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;
init.num_parents = 1;

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))
Expand Down
112 changes: 92 additions & 20 deletions drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -293,17 +349,36 @@ 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)) {
dev_err(dev, "Couldn't map the HDMI encoder registers\n");
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);

Expand Down Expand Up @@ -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);

/*
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 939d749

Please sign in to comment.