From patchwork Tue Mar 6 13:56:00 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxime Ripard X-Patchwork-Id: 10261739 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 1C90B6016D for ; Tue, 6 Mar 2018 13:57:19 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 07AAA28FCB for ; Tue, 6 Mar 2018 13:57:19 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id EFFC728FCE; Tue, 6 Mar 2018 13:57:18 +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, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 78A1828FCD for ; Tue, 6 Mar 2018 13:57:16 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 3009E6E0A0; Tue, 6 Mar 2018 13:57:15 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mail.bootlin.com (mail.bootlin.com [62.4.15.54]) by gabe.freedesktop.org (Postfix) with ESMTP id 12D626E0A0 for ; Tue, 6 Mar 2018 13:57:13 +0000 (UTC) Received: by mail.bootlin.com (Postfix, from userid 110) id 0A1902077B; Tue, 6 Mar 2018 14:57:11 +0100 (CET) Received: from localhost (unknown [185.94.189.190]) by mail.bootlin.com (Postfix) with ESMTPSA id 96A1220956; Tue, 6 Mar 2018 14:56:17 +0100 (CET) From: Maxime Ripard To: Thierry Reding , Chen-Yu Tsai , Maxime Ripard , Mark Rutland , Rob Herring Subject: [PATCH v3 3/7] drm/sun4i: Add Allwinner A31 MIPI-DSI controller support Date: Tue, 6 Mar 2018 14:56:00 +0100 Message-Id: <50e3b73fcd1d2ef6be8ccbcefc91f9779c5a8382.1520344489.git-series.maxime.ripard@bootlin.com> X-Mailer: git-send-email 2.14.3 In-Reply-To: References: In-Reply-To: References: X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, Thomas Petazzoni , Daniel Vetter , Maxime Ripard , linux-arm-kernel@lists.infradead.org MIME-Version: 1.0 Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP From: Maxime Ripard Most of the Allwinner SoCs since the A31 share the same MIPI-DSI controller. While that controller is mostly undocumented, the code is out there and has been cleaned up in order to be integrated into DRM. However, there's still some dark areas that are a bit unclear about how the block exactly operates. Signed-off-by: Maxime Ripard Reviewed-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/Kconfig | 10 +- drivers/gpu/drm/sun4i/Makefile | 4 +- drivers/gpu/drm/sun4i/sun4i_tcon.c | 102 ++- drivers/gpu/drm/sun4i/sun4i_tcon.h | 42 +- drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c | 297 ++++++- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 1111 ++++++++++++++++++++++++- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h | 67 +- 7 files changed, 1633 insertions(+) create mode 100644 drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c create mode 100644 drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c create mode 100644 drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 882d85db9053..982db116a658 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -40,6 +40,16 @@ 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_SUN6I_DSI + tristate "Allwinner A31 MIPI-DSI Controller Support" + default MACH_SUN8I + select CRC_CCITT + select DRM_MIPI_DSI + help + Choose this option if you want have an Allwinner SoC with + MIPI-DSI support. If M is selected the module will be called + sun6i-dsi + 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 2b37a6abbb1d..84d8ed85e0c0 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -19,6 +19,9 @@ sun4i-tcon-y += sun4i_lvds.o sun4i-tcon-y += sun4i_tcon.o sun4i-tcon-y += sun4i_rgb.o +sun6i-dsi-y += sun6i_mipi_dphy.o +sun6i-dsi-y += sun6i_mipi_dsi.o + obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o @@ -26,4 +29,5 @@ obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o +obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i-dsi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 7e5134bfb028..72f37faec189 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -34,6 +34,7 @@ #include "sun4i_lvds.h" #include "sun4i_rgb.h" #include "sun4i_tcon.h" +#include "sun6i_mipi_dsi.h" #include "sunxi_engine.h" static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder) @@ -164,6 +165,7 @@ void sun4i_tcon_set_status(struct sun4i_tcon *tcon, case DRM_MODE_ENCODER_LVDS: is_lvds = true; /* Fallthrough */ + case DRM_MODE_ENCODER_DSI: case DRM_MODE_ENCODER_NONE: channel = 0; break; @@ -257,6 +259,29 @@ static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode, return delay; } +static void sun4i_tcon0_frame_control_enable(struct sun4i_tcon *tcon) +{ + regmap_write(tcon->regs, SUN4I_TCON_FRM_SEED_PR_REG, 1); + regmap_write(tcon->regs, SUN4I_TCON_FRM_SEED_PG_REG, 3); + regmap_write(tcon->regs, SUN4I_TCON_FRM_SEED_PB_REG, 5); + regmap_write(tcon->regs, SUN4I_TCON_FRM_SEED_LR_REG, 7); + regmap_write(tcon->regs, SUN4I_TCON_FRM_SEED_LG_REG, 11); + regmap_write(tcon->regs, SUN4I_TCON_FRM_SEED_LB_REG, 13); + + regmap_write(tcon->regs, SUN4I_TCON_FRM_TBL0_REG, 0x01010000); + regmap_write(tcon->regs, SUN4I_TCON_FRM_TBL1_REG, 0x15151111); + regmap_write(tcon->regs, SUN4I_TCON_FRM_TBL2_REG, 0x57575555); + regmap_write(tcon->regs, SUN4I_TCON_FRM_TBL3_REG, 0x7f7f7777); + + /* + * FIXME: This seems to only be the case for RGB666, but it's + * not clear when the TCON should be setup to something + * different + */ + regmap_write(tcon->regs, SUN4I_TCON_FRM_CTL_REG, + SUN4I_TCON_FRM_CTL_EN); +} + static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, const struct drm_display_mode *mode) { @@ -269,6 +294,73 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); } +static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon *tcon, + struct mipi_dsi_device *device, + const struct drm_display_mode *mode) +{ + u8 bpp = mipi_dsi_pixel_format_to_bpp(device->format); + u8 lanes = device->lanes; + u32 block_space, start_delay; + u32 tcon_div; + + tcon->dclk_min_div = 4; + tcon->dclk_max_div = 127; + + sun4i_tcon0_mode_set_common(tcon, mode); + + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_IF_MASK, + SUN4I_TCON0_CTL_IF_8080); + + regmap_write(tcon->regs, SUN4I_TCON_ECC_FIFO_REG, + SUN4I_TCON_ECC_FIFO_EN); + + regmap_write(tcon->regs, SUN4I_TCON0_CPU_IF_REG, + SUN4I_TCON0_CPU_IF_MODE_DSI | + SUN4I_TCON0_CPU_IF_TRI_FIFO_FLUSH | + SUN4I_TCON0_CPU_IF_TRI_FIFO_EN | + SUN4I_TCON0_CPU_IF_TRI_EN); + + /* + * This looks suspicious, but it works... + * + * The datasheet says that this should be set higher than 20 * + * pixel cycle, but it's not clear what a pixel cycle is. + */ + regmap_read(tcon->regs, SUN4I_TCON0_DCLK_REG, &tcon_div); + tcon_div &= GENMASK(6, 0); + block_space = mode->htotal * bpp / (tcon_div * lanes); + block_space -= mode->hdisplay + 40; + + regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI0_REG, + SUN4I_TCON0_CPU_TRI0_BLOCK_SPACE(block_space) | + SUN4I_TCON0_CPU_TRI0_BLOCK_SIZE(mode->hdisplay)); + + regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI1_REG, + SUN4I_TCON0_CPU_TRI1_BLOCK_NUM(mode->vdisplay)); + + start_delay = (mode->crtc_vtotal - mode->crtc_vdisplay - 10 - 1); + start_delay = start_delay * mode->crtc_htotal * 149; + start_delay = start_delay / (mode->crtc_clock / 1000) / 8; + regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI2_REG, + SUN4I_TCON0_CPU_TRI2_TRANS_START_SET(10) | + SUN4I_TCON0_CPU_TRI2_START_DELAY(start_delay)); + + sun4i_tcon0_frame_control_enable(tcon); + + /* + * The Allwinner BSP has a comment that the period should be + * the display clock * 15, but uses an hardcoded 3000... + */ + regmap_write(tcon->regs, SUN4I_TCON_SAFE_PERIOD_REG, + SUN4I_TCON_SAFE_PERIOD_NUM(3000) | + SUN4I_TCON_SAFE_PERIOD_MODE(3)); + + /* Enable the output on the pins */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, + 0xe0000000); +} + static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon, const struct drm_encoder *encoder, const struct drm_display_mode *mode) @@ -506,7 +598,17 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, const struct drm_encoder *encoder, const struct drm_display_mode *mode) { + struct sun6i_dsi *dsi; + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_DSI: + /* + * This is not really elegant, but it's the "cleaner" + * way I could think of... + */ + dsi = encoder_to_sun6i_dsi(encoder); + sun4i_tcon0_mode_set_cpu(tcon, dsi->device, mode); + break; case DRM_MODE_ENCODER_LVDS: sun4i_tcon0_mode_set_lvds(tcon, encoder, mode); break; diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 067183076807..d976c44019ac 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -35,10 +35,25 @@ #define SUN4I_TCON_GINT0_TCON0_TRI_COUNTER_INT BIT(10) #define SUN4I_TCON_GINT1_REG 0x8 + #define SUN4I_TCON_FRM_CTL_REG 0x10 +#define SUN4I_TCON_FRM_CTL_EN BIT(31) + +#define SUN4I_TCON_FRM_SEED_PR_REG 0x14 +#define SUN4I_TCON_FRM_SEED_PG_REG 0x18 +#define SUN4I_TCON_FRM_SEED_PB_REG 0x1c +#define SUN4I_TCON_FRM_SEED_LR_REG 0x20 +#define SUN4I_TCON_FRM_SEED_LG_REG 0x24 +#define SUN4I_TCON_FRM_SEED_LB_REG 0x28 +#define SUN4I_TCON_FRM_TBL0_REG 0x2c +#define SUN4I_TCON_FRM_TBL1_REG 0x30 +#define SUN4I_TCON_FRM_TBL2_REG 0x34 +#define SUN4I_TCON_FRM_TBL3_REG 0x38 #define SUN4I_TCON0_CTL_REG 0x40 #define SUN4I_TCON0_CTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON0_CTL_IF_MASK GENMASK(25, 24) +#define SUN4I_TCON0_CTL_IF_8080 (1 << 24) #define SUN4I_TCON0_CTL_CLK_DELAY_MASK GENMASK(8, 4) #define SUN4I_TCON0_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON0_CTL_CLK_DELAY_MASK) #define SUN4I_TCON0_CTL_SRC_SEL_MASK GENMASK(2, 0) @@ -65,7 +80,14 @@ #define SUN4I_TCON0_BASIC3_V_SYNC(height) (((height) - 1) & 0x7ff) #define SUN4I_TCON0_HV_IF_REG 0x58 + #define SUN4I_TCON0_CPU_IF_REG 0x60 +#define SUN4I_TCON0_CPU_IF_MODE_MASK GENMASK(31, 28) +#define SUN4I_TCON0_CPU_IF_MODE_DSI (1 << 28) +#define SUN4I_TCON0_CPU_IF_TRI_FIFO_FLUSH BIT(16) +#define SUN4I_TCON0_CPU_IF_TRI_FIFO_EN BIT(2) +#define SUN4I_TCON0_CPU_IF_TRI_EN BIT(0) + #define SUN4I_TCON0_CPU_WR_REG 0x64 #define SUN4I_TCON0_CPU_RD0_REG 0x68 #define SUN4I_TCON0_CPU_RDA_REG 0x6c @@ -132,6 +154,10 @@ #define SUN4I_TCON1_IO_POL_REG 0xf0 #define SUN4I_TCON1_IO_TRI_REG 0xf4 + +#define SUN4I_TCON_ECC_FIFO_REG 0xf8 +#define SUN4I_TCON_ECC_FIFO_EN BIT(3) + #define SUN4I_TCON_CEU_CTL_REG 0x100 #define SUN4I_TCON_CEU_MUL_RR_REG 0x110 #define SUN4I_TCON_CEU_MUL_RG_REG 0x114 @@ -148,6 +174,22 @@ #define SUN4I_TCON_CEU_RANGE_R_REG 0x140 #define SUN4I_TCON_CEU_RANGE_G_REG 0x144 #define SUN4I_TCON_CEU_RANGE_B_REG 0x148 + +#define SUN4I_TCON0_CPU_TRI0_REG 0x160 +#define SUN4I_TCON0_CPU_TRI0_BLOCK_SPACE(space) ((((space) - 1) & 0xfff) << 16) +#define SUN4I_TCON0_CPU_TRI0_BLOCK_SIZE(size) (((size) - 1) & 0xfff) + +#define SUN4I_TCON0_CPU_TRI1_REG 0x164 +#define SUN4I_TCON0_CPU_TRI1_BLOCK_NUM(num) (((num) - 1) & 0xffff) + +#define SUN4I_TCON0_CPU_TRI2_REG 0x168 +#define SUN4I_TCON0_CPU_TRI2_START_DELAY(delay) (((delay) & 0xffff) << 16) +#define SUN4I_TCON0_CPU_TRI2_TRANS_START_SET(set) ((set) & 0xfff) + +#define SUN4I_TCON_SAFE_PERIOD_REG 0x1f0 +#define SUN4I_TCON_SAFE_PERIOD_NUM(num) (((num) & 0xfff) << 16) +#define SUN4I_TCON_SAFE_PERIOD_MODE(mode) ((mode) & 0x3) + #define SUN4I_TCON_MUX_CTRL_REG 0x200 #define SUN4I_TCON0_LVDS_ANA0_REG 0x220 diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c new file mode 100644 index 000000000000..f2acc60f98fa --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * Copyright (C) 2017 Free Electrons + * + * 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 "sun6i_mipi_dsi.h" + +#define SUN6I_DPHY_GCTL_REG 0x00 +#define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) +#define SUN6I_DPHY_GCTL_EN BIT(0) + +#define SUN6I_DPHY_TX_CTL_REG 0x04 +#define SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT BIT(28) + +#define SUN6I_DPHY_TX_TIME0_REG 0x10 +#define SUN6I_DPHY_TX_TIME0_HS_TRAIL(n) (((n) & 0xff) << 24) +#define SUN6I_DPHY_TX_TIME0_HS_PREPARE(n) (((n) & 0xff) << 16) +#define SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME1_REG 0x14 +#define SUN6I_DPHY_TX_TIME1_CLK_POST(n) (((n) & 0xff) << 24) +#define SUN6I_DPHY_TX_TIME1_CLK_PRE(n) (((n) & 0xff) << 16) +#define SUN6I_DPHY_TX_TIME1_CLK_ZERO(n) (((n) & 0xff) << 8) +#define SUN6I_DPHY_TX_TIME1_CLK_PREPARE(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME2_REG 0x18 +#define SUN6I_DPHY_TX_TIME2_CLK_TRAIL(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME3_REG 0x1c + +#define SUN6I_DPHY_TX_TIME4_REG 0x20 +#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(n) (((n) & 0xff) << 8) +#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(n) ((n) & 0xff) + +#define SUN6I_DPHY_ANA0_REG 0x4c +#define SUN6I_DPHY_ANA0_REG_PWS BIT(31) +#define SUN6I_DPHY_ANA0_REG_DMPC BIT(28) +#define SUN6I_DPHY_ANA0_REG_DMPD(n) (((n) & 0xf) << 24) +#define SUN6I_DPHY_ANA0_REG_SLV(n) (((n) & 7) << 12) +#define SUN6I_DPHY_ANA0_REG_DEN(n) (((n) & 0xf) << 8) + +#define SUN6I_DPHY_ANA1_REG 0x50 +#define SUN6I_DPHY_ANA1_REG_VTTMODE BIT(31) +#define SUN6I_DPHY_ANA1_REG_CSMPS(n) (((n) & 3) << 28) +#define SUN6I_DPHY_ANA1_REG_SVTT(n) (((n) & 0xf) << 24) + +#define SUN6I_DPHY_ANA2_REG 0x54 +#define SUN6I_DPHY_ANA2_EN_P2S_CPU(n) (((n) & 0xf) << 24) +#define SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK GENMASK(27, 24) +#define SUN6I_DPHY_ANA2_EN_CK_CPU BIT(4) +#define SUN6I_DPHY_ANA2_REG_ENIB BIT(1) + +#define SUN6I_DPHY_ANA3_REG 0x58 +#define SUN6I_DPHY_ANA3_EN_VTTD(n) (((n) & 0xf) << 28) +#define SUN6I_DPHY_ANA3_EN_VTTD_MASK GENMASK(31, 28) +#define SUN6I_DPHY_ANA3_EN_VTTC BIT(27) +#define SUN6I_DPHY_ANA3_EN_DIV BIT(26) +#define SUN6I_DPHY_ANA3_EN_LDOC BIT(25) +#define SUN6I_DPHY_ANA3_EN_LDOD BIT(24) +#define SUN6I_DPHY_ANA3_EN_LDOR BIT(18) + +#define SUN6I_DPHY_ANA4_REG 0x5c +#define SUN6I_DPHY_ANA4_REG_DMPLVC BIT(24) +#define SUN6I_DPHY_ANA4_REG_DMPLVD(n) (((n) & 0xf) << 20) +#define SUN6I_DPHY_ANA4_REG_CKDV(n) (((n) & 0x1f) << 12) +#define SUN6I_DPHY_ANA4_REG_TMSC(n) (((n) & 3) << 10) +#define SUN6I_DPHY_ANA4_REG_TMSD(n) (((n) & 3) << 8) +#define SUN6I_DPHY_ANA4_REG_TXDNSC(n) (((n) & 3) << 6) +#define SUN6I_DPHY_ANA4_REG_TXDNSD(n) (((n) & 3) << 4) +#define SUN6I_DPHY_ANA4_REG_TXPUSC(n) (((n) & 3) << 2) +#define SUN6I_DPHY_ANA4_REG_TXPUSD(n) ((n) & 3) + +#define SUN6I_DPHY_DBG5_REG 0xf4 + +int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes) +{ + reset_control_deassert(dphy->reset); + clk_prepare_enable(dphy->mod_clk); + clk_set_rate_exclusive(dphy->mod_clk, 150000000); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, + SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME0_REG, + SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(14) | + SUN6I_DPHY_TX_TIME0_HS_PREPARE(6) | + SUN6I_DPHY_TX_TIME0_HS_TRAIL(10)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME1_REG, + SUN6I_DPHY_TX_TIME1_CLK_PREPARE(7) | + SUN6I_DPHY_TX_TIME1_CLK_ZERO(50) | + SUN6I_DPHY_TX_TIME1_CLK_PRE(3) | + SUN6I_DPHY_TX_TIME1_CLK_POST(10)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME2_REG, + SUN6I_DPHY_TX_TIME2_CLK_TRAIL(30)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME3_REG, 0); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME4_REG, + SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(3) | + SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3)); + + /* FIXME: Number of lanes? */ + regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, + SUN6I_DPHY_GCTL_LANE_NUM(lanes) | + SUN6I_DPHY_GCTL_EN); + + return 0; +} + +int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes) +{ + u8 lanes_mask = GENMASK(lanes - 1, 0); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, + SUN6I_DPHY_ANA0_REG_PWS | + SUN6I_DPHY_ANA0_REG_DMPC | + SUN6I_DPHY_ANA0_REG_SLV(7) | + SUN6I_DPHY_ANA0_REG_DMPD(lanes_mask) | + SUN6I_DPHY_ANA0_REG_DEN(lanes_mask)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_CSMPS(1) | + SUN6I_DPHY_ANA1_REG_SVTT(7)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, + SUN6I_DPHY_ANA4_REG_CKDV(1) | + SUN6I_DPHY_ANA4_REG_TMSC(1) | + SUN6I_DPHY_ANA4_REG_TMSD(1) | + SUN6I_DPHY_ANA4_REG_TXDNSC(1) | + SUN6I_DPHY_ANA4_REG_TXDNSD(1) | + SUN6I_DPHY_ANA4_REG_TXPUSC(1) | + SUN6I_DPHY_ANA4_REG_TXPUSD(1) | + SUN6I_DPHY_ANA4_REG_DMPLVC | + SUN6I_DPHY_ANA4_REG_DMPLVD(lanes_mask)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_REG_ENIB); + udelay(5); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_LDOR | + SUN6I_DPHY_ANA3_EN_LDOC | + SUN6I_DPHY_ANA3_EN_LDOD); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_VTTC | + SUN6I_DPHY_ANA3_EN_VTTD_MASK, + SUN6I_DPHY_ANA3_EN_VTTC | + SUN6I_DPHY_ANA3_EN_VTTD(lanes_mask)); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_DIV, + SUN6I_DPHY_ANA3_EN_DIV); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_EN_CK_CPU, + SUN6I_DPHY_ANA2_EN_CK_CPU); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_VTTMODE, + SUN6I_DPHY_ANA1_REG_VTTMODE); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK, + SUN6I_DPHY_ANA2_EN_P2S_CPU(lanes_mask)); + + return 0; +} + +int sun6i_dphy_power_off(struct sun6i_dphy *dphy) +{ + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_VTTMODE, 0); + + return 0; +} + +int sun6i_dphy_exit(struct sun6i_dphy *dphy) +{ + clk_rate_exclusive_put(dphy->mod_clk); + clk_disable_unprepare(dphy->mod_clk); + reset_control_assert(dphy->reset); + + return 0; +} + +static struct regmap_config sun6i_dphy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SUN6I_DPHY_DBG5_REG, + .name = "mipi-dphy", +}; + +static const struct of_device_id sun6i_dphy_of_table[] = { + { .compatible = "allwinner,sun6i-a31-mipi-dphy" }, + { } +}; + +int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node) +{ + struct sun6i_dphy *dphy; + struct resource res; + void __iomem *regs; + int ret; + + if (!of_match_node(sun6i_dphy_of_table, node)) { + dev_err(dsi->dev, "Incompatible D-PHY\n"); + return -EINVAL; + } + + dphy = devm_kzalloc(dsi->dev, sizeof(*dphy), GFP_KERNEL); + if (!dphy) + return -ENOMEM; + + ret = of_address_to_resource(node, 0, &res); + if (ret) { + dev_err(dsi->dev, "phy: Couldn't get our resources\n"); + return ret; + } + + regs = devm_ioremap_resource(dsi->dev, &res); + if (IS_ERR(regs)) { + dev_err(dsi->dev, "Couldn't map the DPHY encoder registers\n"); + return PTR_ERR(regs); + } + + dphy->regs = devm_regmap_init_mmio(dsi->dev, regs, + &sun6i_dphy_regmap_config); + if (IS_ERR(dphy->regs)) { + dev_err(dsi->dev, "Couldn't create the DPHY encoder regmap\n"); + return PTR_ERR(dphy->regs); + } + + dphy->reset = of_reset_control_get_shared(node, NULL); + if (IS_ERR(dphy->reset)) { + dev_err(dsi->dev, "Couldn't get our reset line\n"); + return PTR_ERR(dphy->reset); + } + + dphy->bus_clk = of_clk_get_by_name(node, "bus"); + if (IS_ERR(dphy->bus_clk)) { + dev_err(dsi->dev, "Couldn't get the DPHY bus clock\n"); + ret = PTR_ERR(dphy->bus_clk); + goto err_free_reset; + } + regmap_mmio_attach_clk(dphy->regs, dphy->bus_clk); + + dphy->mod_clk = of_clk_get_by_name(node, "mod"); + if (IS_ERR(dphy->mod_clk)) { + dev_err(dsi->dev, "Couldn't get the DPHY mod clock\n"); + ret = PTR_ERR(dphy->mod_clk); + goto err_free_bus; + } + + dsi->dphy = dphy; + + return 0; + +err_free_bus: + regmap_mmio_detach_clk(dphy->regs); + clk_put(dphy->bus_clk); +err_free_reset: + reset_control_put(dphy->reset); + return ret; +} + +int sun6i_dphy_remove(struct sun6i_dsi *dsi) +{ + struct sun6i_dphy *dphy = dsi->dphy; + + regmap_mmio_detach_clk(dphy->regs); + clk_put(dphy->mod_clk); + clk_put(dphy->bus_clk); + reset_control_put(dphy->reset); + + return 0; +} diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c new file mode 100644 index 000000000000..566a5c5f6145 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c @@ -0,0 +1,1111 @@ +/* + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * Copyright (C) 2017 Free Electrons + * + * 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 + +#include "sun4i_drv.h" +#include "sun6i_mipi_dsi.h" + +#include