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> -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html