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

Chen-Yu Tsai wens at csie.org
Fri May 12 06:50:18 UTC 2017


On Thu, May 4, 2017 at 3:55 PM, Chen-Yu Tsai <wens at csie.org> wrote:
> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
> <maxime.ripard at free-electrons.com> 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 at free-electrons.com>
>> ---
>>  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 at free-electrons.com>
>> + *
>> + * 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)

This field is 19:17, so it should be (((n) & 7) << 17)

>> +#define SUN4I_HDMI_PLL_CTRL_CP_S(n)            (((n) & 0xf) << 12)

And this field is bits 16:12, so it should be (((n) & 0x1f) << 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 at free-electrons.com>
>> + *
>> + * 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 at free-electrons.com>
>> + *
>> + * 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) |

And your values here might be off.

ChenYu

>> +               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 at free-electrons.com>");
>> +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 at free-electrons.com>
>> + *
>> + * 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 at csie.org>


More information about the dri-devel mailing list