From patchwork Wed Sep 20 19:38:29 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Jernej_=C5=A0krabec?= X-Patchwork-Id: 9962223 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 037D6601D5 for ; Wed, 20 Sep 2017 19:43:06 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E809D2921C for ; Wed, 20 Sep 2017 19:43:05 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id DCF2529220; Wed, 20 Sep 2017 19:43:05 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_MED autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id B4B652921C for ; Wed, 20 Sep 2017 19:43:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=uqA8iZHo5SMHhbFdRFfOKQuDo3smOpDy4LGaSFtyWfw=; b=k12k7JHLrgwzdroJIY2Ij7hhHJ sG3xk2h8GhB6nHh2mKheI4CG6qe1oB1HDiWCA68rAzcpCb0NnDkIfxUWEknLhHhoRieG/Ka4FK5J5 4Px8XnQasKAp31FuCxI8qtUtjur/Pysyc1eGgi8qxxxre+y8FKQvuz27NwdyzLhdy9k89vp0HDRjr H55FX4J7AFl9NNXsvuESH2nsDWvwWxWDt4WGHjRQSOwHKMBaOsCixs18SayFb5WQe8DPdSKDhI7c+ tZtrACGDWOideDk7KYoHtEOVD4ispYcS5/hNZPFq4MWSoeVTeL96mrOmKpV5nzkgnct8TtA4qwAI2 HfomGVfw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1dukte-0000M2-OB; Wed, 20 Sep 2017 19:42:58 +0000 Received: from mailoutvs1.siol.net ([213.250.19.134] helo=mail.siol.net) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1dukqP-0004Kh-SK for linux-arm-kernel@lists.infradead.org; Wed, 20 Sep 2017 19:40:06 +0000 Received: from localhost (localhost [127.0.0.1]) by mail.siol.net (Postfix) with ESMTP id 7BD6B528505; Wed, 20 Sep 2017 21:39:03 +0200 (CEST) X-Virus-Scanned: amavisd-new at psrvmta11.zcs-production.pri Received: from mail.siol.net ([127.0.0.1]) by localhost (psrvmta11.zcs-production.pri [127.0.0.1]) (amavisd-new, port 10032) with ESMTP id rKHSIsr865MQ; Wed, 20 Sep 2017 21:39:01 +0200 (CEST) Received: from mail.siol.net (localhost [127.0.0.1]) by mail.siol.net (Postfix) with ESMTPS id D5573528502; Wed, 20 Sep 2017 21:39:01 +0200 (CEST) Received: from localhost.localdomain (cpe-86-58-68-135.ftth.triera.net [86.58.68.135]) (Authenticated sender: 031275009) by mail.siol.net (Postfix) with ESMTPSA id 68C76528500; Wed, 20 Sep 2017 21:38:59 +0200 (CEST) From: Jernej Skrabec To: maxime.ripard@free-electrons.com, wens@csie.org Subject: [RFC PATCH 5/7] drm: sun4i: Add a glue for the DesignWare HDMI controller in H3 Date: Wed, 20 Sep 2017 21:38:29 +0200 Message-Id: <20170920193831.20268-6-jernej.skrabec@siol.net> X-Mailer: git-send-email 2.14.1 In-Reply-To: <20170920193831.20268-1-jernej.skrabec@siol.net> References: <20170920193831.20268-1-jernej.skrabec@siol.net> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170920_123939_365730_04E71BDD X-CRM114-Status: GOOD ( 23.33 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, narmstrong@baylibre.com, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-sunxi@googlegroups.com, Laurent.pinchart@ideasonboard.com, linux-clk@vger.kernel.org, linux-arm-kernel@lists.infradead.org, icenowy@aosc.io MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP Allwinner H3 features DesignWare HDMI Transmitter paired with custom PHY. Add a glue driver for it. For now, only video and CEC are supported. Audio will be supported at a later time. Signed-off-by: Jernej Skrabec --- drivers/gpu/drm/sun4i/Kconfig | 9 + drivers/gpu/drm/sun4i/Makefile | 1 + drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c | 500 ++++++++++++++++++++++++++++++++++ 3 files changed, 510 insertions(+) create mode 100644 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 06f05302ee75..589502ffe31a 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -40,6 +40,15 @@ config DRM_SUN4I_BACKEND do some alpha blending and feed graphics to TCON. If M is selected the module will be called sun4i-backend. +config DRM_SUN8I_DW_HDMI + tristate "Support for Allwinner version of DesignWare HDMI" + depends on DRM_SUN4I + select DRM_DW_HDMI + help + Choose this option if you have an Allwinner SoC with the + DesignWare HDMI controller with custom HDMI PHY. If M is + selected the module will be called sun8i_dw_hdmi. + config DRM_SUN8I_MIXER tristate "Support for Allwinner Display Engine 2.0 Mixer" default MACH_SUN8I diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 43c753cafc88..9c56173bf140 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -22,3 +22,4 @@ obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o +obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i_dw_hdmi.o diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c new file mode 100644 index 000000000000..65db3e10e311 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2017, Jernej Skrabec + * + * Based on hdmi_bsp_sun8iw7.c which is: + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * + * 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 "sun4i_crtc.h" +#include "sun4i_tcon.h" + +#define SUN8I_HDMI_PHY_REG_POL 0x0000 + +#define SUN8I_HDMI_PHY_REG_READ_EN 0x0010 +#define SUN8I_HDMI_PHY_REG_READ_EN_MAGIC 0x54524545 + +#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE 0x0014 +#define SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC 0x42494E47 + +#define SUN8I_HDMI_PHY_REG_CTRL 0x0020 +#define SUN8I_HDMI_PHY_REG_UNK1 0x0024 +#define SUN8I_HDMI_PHY_REG_UNK2 0x0028 +#define SUN8I_HDMI_PHY_REG_PLL 0x002c +#define SUN8I_HDMI_PHY_REG_CLK 0x0030 +#define SUN8I_HDMI_PHY_REG_UNK3 0x0034 + +#define SUN8I_HDMI_PHY_REG_STATUS 0x0038 +#define SUN8I_HDMI_PHY_REG_STATUS_READY BIT(7) +#define SUN8I_HDMI_PHY_REG_STATUS_HPD BIT(19) + +#define SUN8I_HDMI_PHY_REG_CEC 0x003c + +#define to_sun8i_dw_hdmi(x) container_of(x, struct sun8i_dw_hdmi, x) +#define set_bits(p, v) writel(readl(p) | (v), p) + +struct sun8i_dw_hdmi { + struct clk *clk_ahb; + struct clk *clk_ddc; + struct clk *clk_sfr; + struct device *dev; + struct drm_encoder encoder; + void __iomem *phy_base; + struct dw_hdmi_plat_data plat_data; + struct reset_control *rst_ddc; + struct reset_control *rst_hdmi; +}; + +static u32 sun8i_dw_hdmi_get_divider(int clk_khz) +{ + /* + * Due to missing documentation of HDMI PHY, we know correct + * settings only for following four PHY dividers. Select one + * based on pixel clock. + */ + if (clk_khz <= 27000) + return 11; + else if (clk_khz <= 74250) + return 4; + else if (clk_khz <= 148500) + return 2; + else + return 1; +} + +static void sun8i_dw_hdmi_encoder_disable(struct drm_encoder *encoder) +{ + struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); + struct sun4i_tcon *tcon = crtc->tcon; + + DRM_DEBUG_DRIVER("Disabling HDMI Output\n"); + + sun4i_tcon_channel_disable(tcon, 1); +} + +static void sun8i_dw_hdmi_encoder_enable(struct drm_encoder *encoder) +{ + struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); + struct sun4i_tcon *tcon = crtc->tcon; + + DRM_DEBUG_DRIVER("Enabling HDMI Output\n"); + + sun4i_tcon_channel_enable(tcon, 1); +} + +static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct sun8i_dw_hdmi *hdmi = to_sun8i_dw_hdmi(encoder); + struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); + struct sun4i_tcon *tcon = crtc->tcon; + u32 div; + + sun4i_tcon1_mode_set(tcon, mode); + + div = sun8i_dw_hdmi_get_divider(mode->crtc_clock); + clk_set_rate(hdmi->clk_sfr, mode->crtc_clock * 1000 * div); + clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); +} + +static const struct drm_encoder_helper_funcs + sun8i_dw_hdmi_encoder_helper_funcs = { + .mode_set = sun8i_dw_hdmi_encoder_mode_set, + .enable = sun8i_dw_hdmi_encoder_enable, + .disable = sun8i_dw_hdmi_encoder_disable, +}; + +static int sun8i_dw_hdmi_phy_init(struct dw_hdmi *hdmi_data, void *data, + struct drm_display_mode *mode) +{ + struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data; + u32 div = sun8i_dw_hdmi_get_divider(mode->crtc_clock); + u32 val; + + /* + * Unfortunately, we don't know much about those magic + * numbers. They are taken from Allwinner BSP driver. + */ + + val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + writel(val & ~0xf000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + + switch (div) { + case 1: + writel(0x30dc5fc0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL); + writel(0x800863C0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK); + usleep_range(10000, 15000); + writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25)); + msleep(200); + val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS); + val = (val & 0x1f800) >> 11; + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, + BIT(31) | BIT(30)); + if (val < 0x3d) + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, + val + 2); + else + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, 0x3f); + msleep(100); + writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1); + writel(0x0F8246B5, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2); + break; + case 2: + writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL); + writel(0x80084381, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK); + usleep_range(10000, 15000); + writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25)); + msleep(100); + val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS); + val = (val & 0x1f800) >> 11; + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, + BIT(31) | BIT(30)); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val); + writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + writel(0x8063a800, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1); + writel(0x0F81C485, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2); + break; + case 4: + writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL); + writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK); + usleep_range(10000, 15000); + writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25)); + msleep(100); + val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS); + val = (val & 0x1f800) >> 11; + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, + BIT(31) | BIT(30)); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val); + writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1); + writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2); + break; + case 11: + writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL); + writel(0x8008430a, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK); + usleep_range(10000, 15000); + writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25)); + msleep(100); + val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS); + val = (val & 0x1f800) >> 11; + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, + BIT(31) | BIT(30)); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val); + writel(0x01FFFF7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + writel(0x8063b000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1); + writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2); + break; + } + + /* clear polarity bits */ + val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_POL); + val &= ~0x300; + + /* + * Set polarity bits if necessary. Condition in original code + * is a bit weird. This is attempt to make it more reasonable + * and it works. It could be that bits and conditions are + * related and should be separated. + */ + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC) || + !(mode->flags & DRM_MODE_FLAG_PVSYNC)) { + val |= 0x300; + } + + writel(val, hdmi->phy_base + SUN8I_HDMI_PHY_REG_POL); + + return 0; +} + +static void sun8i_dw_hdmi_phy_disable(struct dw_hdmi *hdmi_data, void *data) +{ + struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data; + + /* Disable output and stop PLL */ + writel(7, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL); +} + +static enum drm_connector_status + sun8i_dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi_data, + void *data) +{ + struct sun8i_dw_hdmi *hdmi = (struct sun8i_dw_hdmi *)data; + u32 reg_val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS); + + return (reg_val & SUN8I_HDMI_PHY_REG_STATUS_HPD) ? + connector_status_connected : connector_status_disconnected; +} + +static const struct dw_hdmi_phy_ops sun8i_dw_hdmi_phy_ops = { + .init = &sun8i_dw_hdmi_phy_init, + .disable = &sun8i_dw_hdmi_phy_disable, + .read_hpd = &sun8i_dw_hdmi_phy_read_hpd, +}; + +static void sun8i_dw_hdmi_init(struct sun8i_dw_hdmi *hdmi) +{ + u32 timeout = 20; + u32 val; + + /* + * HDMI PHY settings are taken as-is from Allwinner BSP code. + * There is no documentation. + */ + writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(0)); + udelay(5); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(16)); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(1)); + usleep_range(10, 20); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(2)); + udelay(5); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(3)); + usleep_range(40, 100); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(19)); + usleep_range(100, 200); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(18)); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 7 << 4); + + /* Note that Allwinner code doesn't fail in case of timeout */ + while (!(readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS) & + SUN8I_HDMI_PHY_REG_STATUS_READY)) { + if (!timeout--) { + dev_warn(hdmi->dev, "HDMI PHY init timeout!\n"); + break; + } + usleep_range(100, 200); + } + + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, 0xf << 8); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL, BIT(7)); + + writel(0x39dc5040, hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL); + writel(0x80084343, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CLK); + usleep_range(10000, 15000); + writel(1, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK3); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(25)); + msleep(100); + val = readl(hdmi->phy_base + SUN8I_HDMI_PHY_REG_STATUS); + val = (val & 0x1f800) >> 11; + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, BIT(31) | BIT(30)); + set_bits(hdmi->phy_base + SUN8I_HDMI_PHY_REG_PLL, val); + writel(0x01FF0F7F, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CTRL); + writel(0x80639000, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK1); + writel(0x0F81C405, hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNK2); + + /* enable read access to HDMI controller */ + writel(SUN8I_HDMI_PHY_REG_READ_EN_MAGIC, + hdmi->phy_base + SUN8I_HDMI_PHY_REG_READ_EN); + + /* unscramble register offsets */ + writel(SUN8I_HDMI_PHY_REG_UNSCRAMBLE_MAGIC, + hdmi->phy_base + SUN8I_HDMI_PHY_REG_UNSCRAMBLE); + + /* Reset PHY CEC settings. This gives dw hdmi total control over CEC. */ + writel(0, hdmi->phy_base + SUN8I_HDMI_PHY_REG_CEC); +} + +static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_hdmi_plat_data *plat_data; + struct drm_device *drm = data; + struct drm_encoder *encoder; + struct sun8i_dw_hdmi *hdmi; + struct resource *res; + int ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + plat_data = &hdmi->plat_data; + hdmi->dev = &pdev->dev; + encoder = &hdmi->encoder; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + /* resource 0 is the memory region for the core controller */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + hdmi->phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(hdmi->phy_base)) + return PTR_ERR(hdmi->phy_base); + + hdmi->clk_ahb = devm_clk_get(dev, "iahb"); + if (IS_ERR(hdmi->clk_ahb)) { + dev_err(dev, "Could not get iahb clock\n"); + return PTR_ERR(hdmi->clk_ahb); + } + + hdmi->clk_sfr = devm_clk_get(dev, "isfr"); + if (IS_ERR(hdmi->clk_sfr)) { + dev_err(dev, "Could not get isfr clock\n"); + return PTR_ERR(hdmi->clk_sfr); + } + + hdmi->clk_ddc = devm_clk_get(dev, "ddc"); + if (IS_ERR(hdmi->clk_ddc)) { + dev_err(dev, "Could not get ddc clock\n"); + return PTR_ERR(hdmi->clk_ddc); + } + + hdmi->rst_hdmi = devm_reset_control_get(dev, "hdmi"); + if (IS_ERR(hdmi->rst_hdmi)) { + dev_err(dev, "Could not get hdmi reset control\n"); + return PTR_ERR(hdmi->rst_hdmi); + } + + hdmi->rst_ddc = devm_reset_control_get(dev, "ddc"); + if (IS_ERR(hdmi->rst_ddc)) { + dev_err(dev, "Could not get ddc reset control\n"); + return PTR_ERR(hdmi->rst_ddc); + } + + ret = clk_prepare_enable(hdmi->clk_ahb); + if (ret) { + dev_err(dev, "Cannot enable ahb clock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(hdmi->clk_sfr); + if (ret) { + dev_err(dev, "Cannot enable isfr clock: %d\n", ret); + goto err_ahb_clk; + } + + ret = clk_prepare_enable(hdmi->clk_ddc); + if (ret) { + dev_err(dev, "Cannot enable ddc clock: %d\n", ret); + goto err_sfr_clk; + } + + ret = reset_control_deassert(hdmi->rst_hdmi); + if (ret) { + dev_err(dev, "Could not deassert hdmi reset control\n"); + goto err_ddc_clk; + } + + ret = reset_control_deassert(hdmi->rst_ddc); + if (ret) { + dev_err(dev, "Could not deassert ddc reset control\n"); + goto err_assert_hdmi_reset; + } + + drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs); + drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + + sun8i_dw_hdmi_init(hdmi); + + plat_data->phy_ops = &sun8i_dw_hdmi_phy_ops, + plat_data->phy_name = "sun8i_dw_hdmi_phy", + plat_data->phy_data = hdmi; + + ret = dw_hdmi_bind(pdev, encoder, plat_data); + + /* + * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), + * which would have called the encoder cleanup. Do it manually. + */ + if (ret) + goto cleanup_encoder; + + return 0; + +cleanup_encoder: + drm_encoder_cleanup(encoder); + reset_control_assert(hdmi->rst_ddc); +err_assert_hdmi_reset: + reset_control_assert(hdmi->rst_hdmi); +err_ddc_clk: + clk_disable_unprepare(hdmi->clk_ddc); +err_sfr_clk: + clk_disable_unprepare(hdmi->clk_sfr); +err_ahb_clk: + clk_disable_unprepare(hdmi->clk_ahb); + + return ret; +} + +static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + return dw_hdmi_unbind(dev); +} + +static const struct component_ops sun8i_dw_hdmi_ops = { + .bind = sun8i_dw_hdmi_bind, + .unbind = sun8i_dw_hdmi_unbind, +}; + +static int sun8i_dw_hdmi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun8i_dw_hdmi_ops); +} + +static int sun8i_dw_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun8i_dw_hdmi_ops); + + return 0; +} + +static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = { + { .compatible = "allwinner,sun8i-h3-dw-hdmi" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids); + +struct platform_driver sun8i_dw_hdmi_pltfm_driver = { + .probe = sun8i_dw_hdmi_probe, + .remove = sun8i_dw_hdmi_remove, + .driver = { + .name = "sun8i-dw-hdmi", + .of_match_table = sun8i_dw_hdmi_dt_ids, + }, +}; +module_platform_driver(sun8i_dw_hdmi_pltfm_driver); + +MODULE_AUTHOR("Jernej Skrabec "); +MODULE_DESCRIPTION("Allwinner H3 DW HDMI bridge"); +MODULE_LICENSE("GPL");