From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755196AbdELGuu (ORCPT ); Fri, 12 May 2017 02:50:50 -0400 Received: from smtp.csie.ntu.edu.tw ([140.112.30.61]:33106 "EHLO smtp.csie.ntu.edu.tw" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751323AbdELGus (ORCPT ); Fri, 12 May 2017 02:50:48 -0400 MIME-Version: 1.0 In-Reply-To: References: From: Chen-Yu Tsai Date: Fri, 12 May 2017 14:50:18 +0800 X-Gmail-Original-Message-ID: Message-ID: Subject: Re: [linux-sunxi] [PATCH v2 18/20] drm/sun4i: Add HDMI support To: Maxime Ripard Cc: Chen-Yu Tsai , Mike Turquette , Stephen Boyd , Daniel Vetter , David Airlie , dri-devel , Mark Rutland , Rob Herring , devicetree , linux-arm-kernel , linux-clk , linux-kernel , linux-sunxi Content-Type: text/plain; charset="UTF-8" Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Thu, May 4, 2017 at 3:55 PM, Chen-Yu Tsai wrote: > On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard > 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 >> --- >> 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 >> + * >> + * 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 >> +#include >> + >> +#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 >> + * >> + * 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 >> + >> +#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 >> + * >> + * 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 >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#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 "); >> +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 >> + * >> + * 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 >> + >> +#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 From mboxrd@z Thu Jan 1 00:00:00 1970 From: Chen-Yu Tsai Subject: Re: [PATCH v2 18/20] drm/sun4i: Add HDMI support Date: Fri, 12 May 2017 14:50:18 +0800 Message-ID: References: Reply-To: wens-jdAy2FN1RRM@public.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset="UTF-8" Return-path: Sender: linux-sunxi-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org In-Reply-To: List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , To: Maxime Ripard Cc: Chen-Yu Tsai , Mike Turquette , Stephen Boyd , Daniel Vetter , David Airlie , dri-devel , Mark Rutland , Rob Herring , devicetree , linux-arm-kernel , linux-clk , linux-kernel , linux-sunxi List-Id: devicetree@vger.kernel.org On Thu, May 4, 2017 at 3:55 PM, Chen-Yu Tsai wrote: > On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard > 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 >> --- >> 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 >> + * >> + * 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 >> +#include >> + >> +#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 >> + * >> + * 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 >> + >> +#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 >> + * >> + * 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 >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#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 "); >> +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 >> + * >> + * 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 >> + >> +#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 From mboxrd@z Thu Jan 1 00:00:00 1970 From: wens@csie.org (Chen-Yu Tsai) Date: Fri, 12 May 2017 14:50:18 +0800 Subject: [linux-sunxi] [PATCH v2 18/20] drm/sun4i: Add HDMI support In-Reply-To: References: Message-ID: To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org On Thu, May 4, 2017 at 3:55 PM, Chen-Yu Tsai wrote: > On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard > 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 >> --- >> 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 >> + * >> + * 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 >> +#include >> + >> +#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 >> + * >> + * 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 >> + >> +#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 >> + * >> + * 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 >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#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 "); >> +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 >> + * >> + * 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 >> + >> +#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