Re: [linux-sunxi] [PATCH v2 18/20] drm/sun4i: Add HDMI support

From: Chen-Yu Tsai
Date: Thu May 04 2017 - 03:56:34 EST


On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@xxxxxxxxxxxxxxxxxx> wrote:
> The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
> controller.
>
> That HDMI controller is able to do audio and CEC, but those have been left
> out for now.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> ---
> drivers/gpu/drm/sun4i/Kconfig | 9 +-
> drivers/gpu/drm/sun4i/Makefile | 6 +-
> drivers/gpu/drm/sun4i/sun4i_hdmi.h | 157 +++++++-
> drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 127 +++++-
> drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 493 +++++++++++++++++++++-
> drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
> 6 files changed, 1017 insertions(+), 0 deletions(-)
> create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
> create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>
> diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
> index a4b357db8856..35299c4e2594 100644
> --- a/drivers/gpu/drm/sun4i/Kconfig
> +++ b/drivers/gpu/drm/sun4i/Kconfig
> @@ -12,3 +12,12 @@ config DRM_SUN4I
> Choose this option if you have an Allwinner SoC with a
> Display Engine. If M is selected the module will be called
> sun4i-drm.
> +
> +if DRM_SUN4I
> +config DRM_SUN4I_HDMI
> + tristate "Allwinner A10 HDMI Controller Support"
> + help
> + Choose this option if you have an Allwinner SoC with an HDMI
> + controller.
> +
> +endif
> diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
> index 59b757350a1f..c09bf8093710 100644
> --- a/drivers/gpu/drm/sun4i/Makefile
> +++ b/drivers/gpu/drm/sun4i/Makefile
> @@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o
> obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o
> obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o
> obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o
> +
> +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
> +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
> +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
> +
> +obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
> new file mode 100644
> index 000000000000..40d57b195b48
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
> @@ -0,0 +1,157 @@
> +/*
> + * Copyright (C) 2016 Maxime Ripard
> + *
> + * Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#ifndef _SUN4I_HDMI_H_
> +#define _SUN4I_HDMI_H_
> +
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +
> +#define SUN4I_HDMI_CTRL_REG 0x004
> +#define SUN4I_HDMI_CTRL_ENABLE BIT(31)
> +
> +#define SUN4I_HDMI_IRQ_REG 0x008
> +#define SUN4I_HDMI_IRQ_STA_MASK 0x73
> +#define SUN4I_HDMI_IRQ_STA_FIFO_OF BIT(1)
> +#define SUN4I_HDMI_IRQ_STA_FIFO_UF BIT(0)
> +
> +#define SUN4I_HDMI_HPD_REG 0x00c
> +#define SUN4I_HDMI_HPD_HIGH BIT(0)
> +
> +#define SUN4I_HDMI_VID_CTRL_REG 0x010
> +#define SUN4I_HDMI_VID_CTRL_ENABLE BIT(31)
> +#define SUN4I_HDMI_VID_CTRL_HDMI_MODE BIT(30)
> +
> +#define SUN4I_HDMI_VID_TIMING_ACT_REG 0x014
> +#define SUN4I_HDMI_VID_TIMING_BP_REG 0x018
> +#define SUN4I_HDMI_VID_TIMING_FP_REG 0x01c
> +#define SUN4I_HDMI_VID_TIMING_SPW_REG 0x020
> +
> +#define SUN4I_HDMI_VID_TIMING_X(x) ((((x) - 1) & GENMASK(11, 0)))
> +#define SUN4I_HDMI_VID_TIMING_Y(y) ((((y) - 1) & GENMASK(11, 0)) << 16)
> +
> +#define SUN4I_HDMI_VID_TIMING_POL_REG 0x024
> +#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK (0x3e0 << 16)
> +#define SUN4I_HDMI_VID_TIMING_POL_VSYNC BIT(1)
> +#define SUN4I_HDMI_VID_TIMING_POL_HSYNC BIT(0)
> +
> +#define SUN4I_HDMI_AVI_INFOFRAME_REG(n) (0x080 + (n))
> +
> +#define SUN4I_HDMI_PAD_CTRL0_REG 0x200
> +#define SUN4I_HDMI_PAD_CTRL0_BIASEN BIT(31)
> +#define SUN4I_HDMI_PAD_CTRL0_LDOCEN BIT(30)
> +#define SUN4I_HDMI_PAD_CTRL0_LDODEN BIT(29)
> +#define SUN4I_HDMI_PAD_CTRL0_PWENC BIT(28)
> +#define SUN4I_HDMI_PAD_CTRL0_PWEND BIT(27)
> +#define SUN4I_HDMI_PAD_CTRL0_PWENG BIT(26)
> +#define SUN4I_HDMI_PAD_CTRL0_CKEN BIT(25)
> +#define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23)
> +
> +#define SUN4I_HDMI_PAD_CTRL1_REG 0x204
> +#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT BIT(23)
> +#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT BIT(22)
> +#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT BIT(20)
> +#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT BIT(19)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_DEN BIT(15)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK BIT(14)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10)
> +#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK BIT(6)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n) (((n) & 7) << 3)
> +
> +#define SUN4I_HDMI_PLL_CTRL_REG 0x208
> +#define SUN4I_HDMI_PLL_CTRL_PLL_EN BIT(31)
> +#define SUN4I_HDMI_PLL_CTRL_BWS BIT(30)
> +#define SUN4I_HDMI_PLL_CTRL_HV_IS_33 BIT(29)
> +#define SUN4I_HDMI_PLL_CTRL_LDO1_EN BIT(28)
> +#define SUN4I_HDMI_PLL_CTRL_LDO2_EN BIT(27)
> +#define SUN4I_HDMI_PLL_CTRL_SDIV2 BIT(25)
> +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n) (((n) & 7) << 20)
> +#define SUN4I_HDMI_PLL_CTRL_S(n) (((n) & 7) << 16)
> +#define SUN4I_HDMI_PLL_CTRL_CP_S(n) (((n) & 0xf) << 12)
> +#define SUN4I_HDMI_PLL_CTRL_CS(n) (((n) & 0xf) << 8)
> +#define SUN4I_HDMI_PLL_CTRL_DIV(n) (((n) & 0xf) << 4)
> +#define SUN4I_HDMI_PLL_CTRL_DIV_MASK GENMASK(7, 4)
> +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n) ((n) & 0xf)
> +
> +#define SUN4I_HDMI_PLL_DBG0_REG 0x20c
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n) (((n) & 1) << 21)
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21)
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21
> +
> +#define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n)))
> +#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4))
> +
> +#define SUN4I_HDMI_UNKNOWN_REG 0x300
> +#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC BIT(27)
> +
> +#define SUN4I_HDMI_DDC_CTRL_REG 0x500
> +#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31)
> +#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30)
> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8)
> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8)
> +#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0)
> +
> +#define SUN4I_HDMI_DDC_ADDR_REG 0x504
> +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24)
> +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16)
> +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8)
> +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff)
> +
> +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510
> +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31)
> +
> +#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518
> +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c
> +
> +#define SUN4I_HDMI_DDC_CMD_REG 0x520
> +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6
> +
> +#define SUN4I_HDMI_DDC_CLK_REG 0x528
> +#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3)
> +#define SUN4I_HDMI_DDC_CLK_N(n) ((n) & 0x7)
> +
> +#define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x540
> +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9)
> +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE BIT(8)
> +
> +#define SUN4I_HDMI_DDC_FIFO_SIZE 16
> +
> +enum sun4i_hdmi_pkt_type {
> + SUN4I_HDMI_PKT_AVI = 2,
> + SUN4I_HDMI_PKT_END = 15,
> +};
> +
> +struct sun4i_hdmi {
> + struct drm_connector connector;
> + struct drm_encoder encoder;
> + struct device *dev;
> +
> + void __iomem *base;
> +
> + /* Parent clocks */
> + struct clk *bus_clk;
> + struct clk *mod_clk;
> + struct clk *pll0_clk;
> + struct clk *pll1_clk;
> +
> + /* And the clocks we create */
> + struct clk *ddc_clk;
> + struct clk *tmds_clk;
> +
> + struct sun4i_drv *drv;
> +
> + bool hdmi_monitor;
> +};
> +
> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
> +
> +#endif /* _SUN4I_HDMI_H_ */
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> new file mode 100644
> index 000000000000..4692e8c345ed
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> @@ -0,0 +1,127 @@
> +/*
> + * Copyright (C) 2016 Free Electrons
> + * Copyright (C) 2016 NextThing Co
> + *
> + * Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun4i_tcon.h"
> +#include "sun4i_hdmi.h"
> +
> +struct sun4i_ddc {
> + struct clk_hw hw;
> + struct sun4i_hdmi *hdmi;
> +};
> +
> +static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
> +{
> + return container_of(hw, struct sun4i_ddc, hw);
> +}
> +
> +static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
> + unsigned long parent_rate,
> + u8 *m, u8 *n)
> +{
> + unsigned long best_rate = 0;
> + u8 best_m = 0, best_n = 0, _m, _n;
> +
> + for (_m = 0; _m < 8; _m++) {
> + for (_n = 0; _n < 8; _n++) {
> + unsigned long tmp_rate;
> +
> + tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
> +
> + if (tmp_rate > rate)
> + continue;
> +
> + if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
> + best_rate = tmp_rate;
> + best_m = _m;
> + best_n = _n;
> + }
> + }
> + }
> +
> + if (m && n) {
> + *m = best_m;
> + *n = best_n;
> + }
> +
> + return best_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);
> +}
> +
> +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;
> + u8 m, n;
> +
> + reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
> + m = (reg >> 3) & 0x7;
> + n = reg & 0x7;
> +
> + return (((parent_rate / 2) / 10) >> n) / (m + 1);
> +}
> +
> +static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_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);
> +
> + writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
> + ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops sun4i_ddc_ops = {
> + .recalc_rate = sun4i_ddc_recalc_rate,
> + .round_rate = sun4i_ddc_round_rate,
> + .set_rate = sun4i_ddc_set_rate,
> +};
> +
> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
> +{
> + struct clk_init_data init;
> + struct sun4i_ddc *ddc;
> + const char *parent_name;
> +
> + parent_name = __clk_get_name(parent);
> + if (!parent_name)
> + return -ENODEV;
> +
> + ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
> + if (!ddc)
> + return -ENOMEM;
> +
> + 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;
> +
> + hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
> + if (IS_ERR(hdmi->ddc_clk))
> + return PTR_ERR(hdmi->ddc_clk);
> +
> + return 0;
> +}
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> new file mode 100644
> index 000000000000..9bec23164f53
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> @@ -0,0 +1,493 @@
> +/*
> + * Copyright (C) 2016 Maxime Ripard
> + *
> + * Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/iopoll.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "sun4i_backend.h"
> +#include "sun4i_drv.h"
> +#include "sun4i_hdmi.h"
> +#include "sun4i_tcon.h"
> +
> +#define DDC_SEGMENT_ADDR 0x30
> +
> +static inline struct sun4i_hdmi *
> +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
> +{
> + return container_of(encoder, struct sun4i_hdmi,
> + encoder);
> +}
> +
> +static inline struct sun4i_hdmi *
> +drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
> +{
> + return container_of(connector, struct sun4i_hdmi,
> + connector);
> +}
> +
> +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
> + struct drm_display_mode *mode)
> +{
> + struct hdmi_avi_infoframe frame;
> + u8 buffer[17];
> + int i, ret;
> +
> + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
> + if (ret < 0) {
> + DRM_ERROR("Failed to get infoframes from mode\n");
> + return ret;
> + }
> +
> + ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
> + if (ret < 0) {
> + DRM_ERROR("Failed to pack infoframes\n");
> + return ret;
> + }
> +
> + for (i = 0; i < sizeof(buffer); i++)
> + writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
> +
> + return 0;
> +}
> +
> +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state)
> +{
> + struct drm_display_mode *mode = &crtc_state->mode;
> +
> + if (mode->flags & DRM_MODE_FLAG_DBLCLK)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static void sun4i_hdmi_disable(struct drm_encoder *encoder)
> +{
> + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> + struct sun4i_drv *drv = hdmi->drv;
> + struct sun4i_tcon *tcon = drv->tcon;

I assume you didn't take into consideration the latest patches I sent
that remove this field.

Instead you'll want to do drm_crtc_to_sun4i_crtc(encoder->crtc)->tcon.

> + u32 val;
> +
> + DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
> +
> + val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> + val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
> + writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +
> + sun4i_tcon_channel_disable(tcon, 1);
> +}
> +
> +static void sun4i_hdmi_enable(struct drm_encoder *encoder)
> +{
> + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
> + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> + struct sun4i_drv *drv = hdmi->drv;
> + struct sun4i_tcon *tcon = drv->tcon;
> + u32 val = 0;
> +
> + DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
> +
> + sun4i_tcon_channel_enable(tcon, 1);
> +
> + sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
> + val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
> + val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
> + writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
> +
> + val = SUN4I_HDMI_VID_CTRL_ENABLE;
> + if (hdmi->hdmi_monitor)
> + val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
> +
> + writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +}
> +
> +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
> + struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted_mode)
> +{
> + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> + struct sun4i_drv *drv = hdmi->drv;
> + struct sun4i_tcon *tcon = drv->tcon;
> + unsigned int x, y;
> + u32 val;
> +
> + sun4i_tcon1_mode_set(tcon, mode);
> + sun4i_tcon_set_mux(tcon, 1, encoder);
> +
> + clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
> + clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
> + clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
> +
> + /* Set input sync enable */
> + writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
> + hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
> +
> + /* Setup timing registers */
> + writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
> + SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),

I can't figure out whether we should use mode->crtc_* or not...

> + hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
> +
> + x = mode->htotal - mode->hsync_start;
> + y = mode->vtotal - mode->vsync_start;
> + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> + hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
> +
> + x = mode->hsync_start - mode->hdisplay;
> + y = mode->vsync_start - mode->vdisplay;
> + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> + hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
> +
> + x = mode->hsync_end - mode->hsync_start;
> + y = mode->vsync_end - mode->vsync_start;
> + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> + hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
> +
> + val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
> + if (mode->flags & DRM_MODE_FLAG_PHSYNC)
> + val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
> +
> + if (mode->flags & DRM_MODE_FLAG_PVSYNC)
> + val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
> +
> + writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
> +}
> +
> +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
> + .atomic_check = sun4i_hdmi_atomic_check,
> + .disable = sun4i_hdmi_disable,
> + .enable = sun4i_hdmi_enable,
> + .mode_set = sun4i_hdmi_mode_set,
> +};
> +
> +static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
> + .destroy = drm_encoder_cleanup,
> +};
> +
> +static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
> + unsigned int blk, unsigned int offset,
> + u8 *buf, unsigned int count)
> +{
> + unsigned long reg;
> + int i;
> +
> + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> + reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
> + writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
> + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +
> + writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
> + SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
> + SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
> + SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
> + hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
> +
> + reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
> + writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
> + hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
> +
> + writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
> + writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
> + hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
> +
> + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> + writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
> + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +
> + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
> + !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
> + 100, 100000))
> + return -EIO;
> +
> + for (i = 0; i < count; i++)
> + buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
> +
> + return 0;
> +}
> +
> +static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
> + size_t length)
> +{
> + struct sun4i_hdmi *hdmi = data;
> + int retry = 2, i;
> +
> + do {
> + for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
> + unsigned char offset = blk * EDID_LENGTH + i;
> + unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
> + length - i);
> + int ret;
> +
> + ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
> + buf + i, count);
> + if (ret)
> + return ret;
> + }
> + } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
> +
> + return 0;
> +}
> +
> +static int sun4i_hdmi_get_modes(struct drm_connector *connector)
> +{
> + struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
> + unsigned long reg;
> + struct edid *edid;
> + int ret;
> +
> + /* 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))
> + 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_set_rate(hdmi->ddc_clk, 100000);

You might want to explicitly enable the DDC clk around the following call.

> +
> + edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
> + if (!edid)
> + return 0;
> +
> + hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
> + DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
> + hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
> +
> + drm_mode_connector_update_edid_property(connector, edid);
> + ret = drm_add_edid_modes(connector, edid);
> + kfree(edid);
> +
> + return ret;
> +}
> +
> +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
> + .get_modes = sun4i_hdmi_get_modes,
> +};
> +
> +static enum drm_connector_status
> +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
> +{
> + struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
> + unsigned long reg;
> +
> + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
> + reg & SUN4I_HDMI_HPD_HIGH,
> + 0, 500000))
> + return connector_status_disconnected;
> +
> + return connector_status_connected;
> +}
> +
> +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
> + .dpms = drm_atomic_helper_connector_dpms,
> + .detect = sun4i_hdmi_connector_detect,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = drm_connector_cleanup,
> + .reset = drm_atomic_helper_connector_reset,
> + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int sun4i_hdmi_bind(struct device *dev, struct device *master,
> + void *data)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct drm_device *drm = data;
> + struct sun4i_drv *drv = drm->dev_private;
> + struct sun4i_hdmi *hdmi;
> + struct resource *res;
> + u32 reg;
> + int ret;
> +
> + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
> + if (!hdmi)
> + return -ENOMEM;
> + dev_set_drvdata(dev, hdmi);
> + hdmi->dev = dev;
> + hdmi->drv = drv;
> +
> + 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);
> + }
> +
> + 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);
> + }
> + clk_prepare_enable(hdmi->bus_clk);
> +
> + hdmi->mod_clk = devm_clk_get(dev, "mod");
> + if (IS_ERR(hdmi->mod_clk)) {
> + dev_err(dev, "Couldn't get the HDMI mod clock\n");
> + return PTR_ERR(hdmi->mod_clk);
> + }
> + clk_prepare_enable(hdmi->mod_clk);
> +
> + hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
> + if (IS_ERR(hdmi->pll0_clk)) {
> + dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
> + return PTR_ERR(hdmi->pll0_clk);
> + }
> +
> + hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
> + if (IS_ERR(hdmi->pll1_clk)) {
> + dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
> + return PTR_ERR(hdmi->pll1_clk);
> + }
> +
> + ret = sun4i_tmds_create(hdmi);
> + if (ret) {
> + dev_err(dev, "Couldn't create the TMDS clock\n");
> + return ret;
> + }
> +
> + 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,
> + hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
> +
> + /*
> + * We can't just initialize the register there, we need to
> + * protect the clock bits that have already been read out and
> + * cached by the clock framework.
> + */
> + reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> + reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;

Nit: just use the &= short hand.

> + 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;
> + writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +
> + reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> + reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;

Same here.

> + reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
> + SUN4I_HDMI_PLL_CTRL_CP_S(239) | 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;
> + writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +
> + ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
> + if (ret) {
> + dev_err(dev, "Couldn't create the DDC clock\n");
> + return ret;
> + }
> +
> + drm_encoder_helper_add(&hdmi->encoder,
> + &sun4i_hdmi_helper_funcs);
> + ret = drm_encoder_init(drm,
> + &hdmi->encoder,
> + &sun4i_hdmi_funcs,
> + DRM_MODE_ENCODER_TMDS,
> + NULL);
> + if (ret) {
> + dev_err(dev, "Couldn't initialise the HDMI encoder\n");
> + return ret;
> + }
> +
> + hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
> + dev->of_node);
> + if (!hdmi->encoder.possible_crtcs)
> + return ret;

You need a valid error return value here. Maybe -EPROBE_DEFER?

> +
> + drm_connector_helper_add(&hdmi->connector,
> + &sun4i_hdmi_connector_helper_funcs);

/* There is no HPD interrupt, so we need to poll the controller */
hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
DRM_CONNECTOR_POLL_DISCONNECT;

> + ret = drm_connector_init(drm, &hdmi->connector,
> + &sun4i_hdmi_connector_funcs,
> + DRM_MODE_CONNECTOR_HDMIA);
> + if (ret) {
> + dev_err(dev,
> + "Couldn't initialise the HDMI connector\n");
> + goto err_cleanup_connector;
> + }
> +
> + drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
> +
> + return 0;
> +
> +err_cleanup_connector:
> + drm_encoder_cleanup(&hdmi->encoder);
> + return ret;
> +}
> +
> +static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
> + void *data)
> +{
> + struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
> +
> + drm_connector_cleanup(&hdmi->connector);
> + drm_encoder_cleanup(&hdmi->encoder);
> +}
> +
> +static const struct component_ops sun4i_hdmi_ops = {
> + .bind = sun4i_hdmi_bind,
> + .unbind = sun4i_hdmi_unbind,
> +};
> +
> +static int sun4i_hdmi_probe(struct platform_device *pdev)
> +{
> + return component_add(&pdev->dev, &sun4i_hdmi_ops);
> +}
> +
> +static int sun4i_hdmi_remove(struct platform_device *pdev)
> +{
> + component_del(&pdev->dev, &sun4i_hdmi_ops);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id sun4i_hdmi_of_table[] = {
> + { .compatible = "allwinner,sun5i-a10s-hdmi" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
> +
> +static struct platform_driver sun4i_hdmi_driver = {
> + .probe = sun4i_hdmi_probe,
> + .remove = sun4i_hdmi_remove,
> + .driver = {
> + .name = "sun4i-hdmi",
> + .of_match_table = sun4i_hdmi_of_table,
> + },
> +};
> +module_platform_driver(sun4i_hdmi_driver);
> +
> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
> new file mode 100644
> index 000000000000..5cf2527bffc8
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
> @@ -0,0 +1,225 @@
> +/*
> + * Copyright (C) 2016 Free Electrons
> + * Copyright (C) 2016 NextThing Co
> + *
> + * Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun4i_tcon.h"
> +#include "sun4i_hdmi.h"
> +
> +struct sun4i_tmds {
> + struct clk_hw hw;
> + struct sun4i_hdmi *hdmi;
> +};
> +
> +static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
> +{
> + return container_of(hw, struct sun4i_tmds, hw);
> +}
> +
> +
> +static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
> + unsigned long parent_rate,
> + u8 *div,
> + bool *half)
> +{
> + unsigned long best_rate = 0;
> + u8 best_m = 0, m;
> + bool is_double;
> +
> + for (m = 1; m < 16; m++) {
> + u8 d;
> +
> + for (d = 1; d < 3; d++) {
> + unsigned long tmp_rate;
> +
> + tmp_rate = parent_rate / m / d;
> +
> + if (tmp_rate > rate)
> + continue;
> +
> + if (!best_rate ||
> + (rate - tmp_rate) < (rate - best_rate)) {
> + best_rate = tmp_rate;
> + best_m = m;
> + is_double = d;
> + }
> + }
> + }
> +
> + if (div && half) {
> + *div = best_m;
> + *half = is_double;
> + }
> +
> + return best_rate;
> +}
> +
> +
> +static int sun4i_tmds_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct clk_hw *parent;
> + unsigned long best_parent = 0;
> + unsigned long rate = req->rate;
> + int best_div = 1, best_half = 1;
> + int i, j;
> +
> + /*
> + * We only consider PLL3, since the TCON is very likely to be
> + * clocked from it, and to have the same rate than our HDMI
> + * clock, so we should not need to do anything.
> + */
> +
> + parent = clk_hw_get_parent_by_index(hw, 0);
> + if (!parent)
> + return -EINVAL;
> +
> + for (i = 1; i < 3; i++) {
> + for (j = 1; j < 16; j++) {
> + unsigned long ideal = rate * i * j;
> + unsigned long rounded;
> +
> + rounded = clk_hw_round_rate(parent, ideal);
> +
> + if (rounded == ideal) {
> + best_parent = rounded;
> + best_half = i;
> + best_div = j;
> + goto out;
> + }
> +
> + if (abs(rate - rounded / i) <
> + abs(rate - best_parent / best_div)) {
> + best_parent = rounded;
> + best_div = i;
> + }
> + }
> + }
> +
> +out:
> + req->rate = best_parent / best_half / best_div;
> + req->best_parent_rate = best_parent;
> + req->best_parent_hw = parent;
> +
> + return 0;
> +}
> +
> +static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct sun4i_tmds *tmds = hw_to_tmds(hw);
> + u32 reg;
> +
> + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> + if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
> + parent_rate /= 2;
> +
> + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> + reg = (reg >> 4) & 0xf;
> + if (!reg)
> + reg = 1;
> +
> + return parent_rate / reg;
> +}
> +
> +static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct sun4i_tmds *tmds = hw_to_tmds(hw);
> + bool half;
> + u32 reg;
> + u8 div;
> +
> + sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
> +
> + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> + reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
> + if (half)
> + reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
> + writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +
> + 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),
> + tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +
> + return 0;
> +}
> +
> +static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
> +{
> + struct sun4i_tmds *tmds = hw_to_tmds(hw);
> + u32 reg;
> +
> + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> + return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
> + SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
> +}
> +
> +static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
> +{
> + struct sun4i_tmds *tmds = hw_to_tmds(hw);
> + u32 reg;
> +
> + if (index > 1)
> + return -EINVAL;
> +
> + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> + reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
> + writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
> + tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops sun4i_tmds_ops = {
> + .determine_rate = sun4i_tmds_determine_rate,
> + .recalc_rate = sun4i_tmds_recalc_rate,
> + .set_rate = sun4i_tmds_set_rate,
> +
> + .get_parent = sun4i_tmds_get_parent,
> + .set_parent = sun4i_tmds_set_parent,
> +};
> +
> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
> +{
> + struct clk_init_data init;
> + struct sun4i_tmds *tmds;
> + const char *parents[2];
> +
> + parents[0] = __clk_get_name(hdmi->pll0_clk);
> + if (!parents[0])
> + return -ENODEV;
> +
> + parents[1] = __clk_get_name(hdmi->pll1_clk);
> + if (!parents[1])
> + return -ENODEV;
> +
> + tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
> + if (!tmds)
> + return -ENOMEM;
> +
> + init.name = "hdmi-tmds";
> + init.ops = &sun4i_tmds_ops;
> + init.parent_names = parents;
> + init.num_parents = 2;
> + init.flags = CLK_SET_RATE_PARENT;
> +
> + tmds->hdmi = hdmi;
> + tmds->hw.init = &init;
> +
> + hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
> + if (IS_ERR(hdmi->tmds_clk))
> + return PTR_ERR(hdmi->tmds_clk);
> +
> + return 0;
> +}
> --

The rest looks good. This is coming together nicely.

Reviewed-by: Chen-Yu Tsai <wens@xxxxxxxx>