Re: [PATCH v3 3/7] drm/sun4i: Add Allwinner A31 MIPI-DSI controller support

From: Chen-Yu Tsai
Date: Wed Mar 21 2018 - 22:18:23 EST


On Tue, Mar 6, 2018 at 9:56 PM, Maxime Ripard <maxime.ripard@xxxxxxxxxxx> wrote:
> From: Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>
>
> 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 <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> ---
> 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 +-

Would it be possible to split the TCON parts to a second patch?

> 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);
> +}
> +

This is for dithering. IIRC Jonathan Liu (might be wrong) had patches
covering this, and I tested them out. In any case, this is not limited
to DSI, and should not be in this patch.

> 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);

Dithering should be in the tcon0 common setup code.

> +
> + /*
> + * 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 <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/of_address.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#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? */

What needs to be fixed?

> + 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 <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/crc-ccitt.h>
> +#include <linux/of_address.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#include <linux/phy/phy.h>
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +
> +#include "sun4i_drv.h"
> +#include "sun6i_mipi_dsi.h"
> +
> +#include <video/mipi_display.h>
> +
> +#define SUN6I_DSI_CTL_REG 0x000
> +#define SUN6I_DSI_CTL_EN BIT(0)
> +
> +#define SUN6I_DSI_BASIC_CTL_REG 0x00c
> +#define SUN6I_DSI_BASIC_CTL_HBP_DIS BIT(2)
> +#define SUN6I_DSI_BASIC_CTL_HSA_HSE_DIS BIT(1)
> +#define SUN6I_DSI_BASIC_CTL_VIDEO_BURST BIT(0)
> +
> +#define SUN6I_DSI_BASIC_CTL0_REG 0x010
> +#define SUN6I_DSI_BASIC_CTL0_HS_EOTP_EN BIT(18)
> +#define SUN6I_DSI_BASIC_CTL0_CRC_EN BIT(17)
> +#define SUN6I_DSI_BASIC_CTL0_ECC_EN BIT(16)
> +#define SUN6I_DSI_BASIC_CTL0_INST_ST BIT(0)
> +
> +#define SUN6I_DSI_BASIC_CTL1_REG 0x014
> +#define SUN6I_DSI_BASIC_CTL1_VIDEO_ST_DELAY(n) (((n) & 0x1fff) << 4)
> +#define SUN6I_DSI_BASIC_CTL1_VIDEO_FILL BIT(2)
> +#define SUN6I_DSI_BASIC_CTL1_VIDEO_PRECISION BIT(1)
> +#define SUN6I_DSI_BASIC_CTL1_VIDEO_MODE BIT(0)
> +
> +#define SUN6I_DSI_BASIC_SIZE0_REG 0x018
> +#define SUN6I_DSI_BASIC_SIZE0_VBP(n) (((n) & 0xfff) << 16)
> +#define SUN6I_DSI_BASIC_SIZE0_VSA(n) ((n) & 0xfff)
> +
> +#define SUN6I_DSI_BASIC_SIZE1_REG 0x01c
> +#define SUN6I_DSI_BASIC_SIZE1_VT(n) (((n) & 0xfff) << 16)
> +#define SUN6I_DSI_BASIC_SIZE1_VACT(n) ((n) & 0xfff)
> +
> +#define SUN6I_DSI_INST_FUNC_REG(n) (0x020 + (n) * 0x04)
> +#define SUN6I_DSI_INST_FUNC_INST_MODE(n) (((n) & 0xf) << 28)
> +#define SUN6I_DSI_INST_FUNC_ESCAPE_ENTRY(n) (((n) & 0xf) << 24)
> +#define SUN6I_DSI_INST_FUNC_TRANS_PACKET(n) (((n) & 0xf) << 20)
> +#define SUN6I_DSI_INST_FUNC_LANE_CEN BIT(4)
> +#define SUN6I_DSI_INST_FUNC_LANE_DEN(n) ((n) & 0xf)
> +
> +#define SUN6I_DSI_INST_LOOP_SEL_REG 0x040
> +
> +#define SUN6I_DSI_INST_LOOP_NUM_REG(n) (0x044 + (n) * 0x10)
> +#define SUN6I_DSI_INST_LOOP_NUM_N1(n) (((n) & 0xfff) << 16)
> +#define SUN6I_DSI_INST_LOOP_NUM_N0(n) ((n) & 0xfff)
> +
> +#define SUN6I_DSI_INST_JUMP_SEL_REG 0x048
> +
> +#define SUN6I_DSI_INST_JUMP_CFG_REG(n) (0x04c + (n) * 0x04)
> +#define SUN6I_DSI_INST_JUMP_CFG_TO(n) (((n) & 0xf) << 20)
> +#define SUN6I_DSI_INST_JUMP_CFG_POINT(n) (((n) & 0xf) << 16)
> +#define SUN6I_DSI_INST_JUMP_CFG_NUM(n) ((n) & 0xffff)
> +
> +#define SUN6I_DSI_TRANS_START_REG 0x060
> +
> +#define SUN6I_DSI_TRANS_ZERO_REG 0x078
> +
> +#define SUN6I_DSI_TCON_DRQ_REG 0x07c
> +#define SUN6I_DSI_TCON_DRQ_ENABLE_MODE BIT(28)
> +#define SUN6I_DSI_TCON_DRQ_SET(n) ((n) & 0x3ff)
> +
> +#define SUN6I_DSI_PIXEL_CTL0_REG 0x080
> +#define SUN6I_DSI_PIXEL_CTL0_PD_PLUG_DISABLE BIT(16)
> +#define SUN6I_DSI_PIXEL_CTL0_FORMAT(n) ((n) & 0xf)
> +
> +#define SUN6I_DSI_PIXEL_CTL1_REG 0x084
> +
> +#define SUN6I_DSI_PIXEL_PH_REG 0x090
> +#define SUN6I_DSI_PIXEL_PH_ECC(n) (((n) & 0xff) << 24)
> +#define SUN6I_DSI_PIXEL_PH_WC(n) (((n) & 0xffff) << 8)
> +#define SUN6I_DSI_PIXEL_PH_VC(n) (((n) & 3) << 6)
> +#define SUN6I_DSI_PIXEL_PH_DT(n) ((n) & 0x3f)
> +
> +#define SUN6I_DSI_PIXEL_PF0_REG 0x098
> +#define SUN6I_DSI_PIXEL_PF0_CRC_FORCE(n) ((n) & 0xffff)
> +
> +#define SUN6I_DSI_PIXEL_PF1_REG 0x09c
> +#define SUN6I_DSI_PIXEL_PF1_CRC_INIT_LINEN(n) (((n) & 0xffff) << 16)
> +#define SUN6I_DSI_PIXEL_PF1_CRC_INIT_LINE0(n) ((n) & 0xffff)
> +
> +#define SUN6I_DSI_SYNC_HSS_REG 0x0b0
> +
> +#define SUN6I_DSI_SYNC_HSE_REG 0x0b4
> +
> +#define SUN6I_DSI_SYNC_VSS_REG 0x0b8
> +
> +#define SUN6I_DSI_SYNC_VSE_REG 0x0bc
> +
> +#define SUN6I_DSI_BLK_HSA0_REG 0x0c0
> +
> +#define SUN6I_DSI_BLK_HSA1_REG 0x0c4
> +#define SUN6I_DSI_BLK_PF(n) (((n) & 0xffff) << 16)
> +#define SUN6I_DSI_BLK_PD(n) ((n) & 0xff)
> +
> +#define SUN6I_DSI_BLK_HBP0_REG 0x0c8
> +
> +#define SUN6I_DSI_BLK_HBP1_REG 0x0cc
> +
> +#define SUN6I_DSI_BLK_HFP0_REG 0x0d0
> +
> +#define SUN6I_DSI_BLK_HFP1_REG 0x0d4
> +
> +#define SUN6I_DSI_BLK_HBLK0_REG 0x0e0
> +
> +#define SUN6I_DSI_BLK_HBLK1_REG 0x0e4
> +
> +#define SUN6I_DSI_BLK_VBLK0_REG 0x0e8
> +
> +#define SUN6I_DSI_BLK_VBLK1_REG 0x0ec
> +
> +#define SUN6I_DSI_BURST_LINE_REG 0x0f0
> +#define SUN6I_DSI_BURST_LINE_SYNC_POINT(n) (((n) & 0xffff) << 16)
> +#define SUN6I_DSI_BURST_LINE_NUM(n) ((n) & 0xffff)
> +
> +#define SUN6I_DSI_BURST_DRQ_REG 0x0f4
> +#define SUN6I_DSI_BURST_DRQ_EDGE1(n) (((n) & 0xffff) << 16)
> +#define SUN6I_DSI_BURST_DRQ_EDGE0(n) ((n) & 0xffff)
> +
> +#define SUN6I_DSI_CMD_CTL_REG 0x200
> +#define SUN6I_DSI_CMD_CTL_RX_OVERFLOW BIT(26)
> +#define SUN6I_DSI_CMD_CTL_RX_FLAG BIT(25)
> +#define SUN6I_DSI_CMD_CTL_TX_FLAG BIT(9)
> +
> +#define SUN6I_DSI_CMD_RX_REG(n) (0x240 + (n) * 0x04)
> +
> +#define SUN6I_DSI_DEBUG_DATA_REG 0x2f8
> +
> +#define SUN6I_DSI_CMD_TX_REG(n) (0x300 + (n) * 0x04)
> +
> +enum sun6i_dsi_start_inst {
> + DSI_START_LPRX,
> + DSI_START_LPTX,
> + DSI_START_HSC,
> + DSI_START_HSD,
> +};
> +
> +enum sun6i_dsi_inst_id {
> + DSI_INST_ID_LP11 = 0,
> + DSI_INST_ID_TBA,
> + DSI_INST_ID_HSC,
> + DSI_INST_ID_HSD,
> + DSI_INST_ID_LPDT,
> + DSI_INST_ID_HSCEXIT,
> + DSI_INST_ID_NOP,
> + DSI_INST_ID_DLY,
> + DSI_INST_ID_END = 15,
> +};
> +
> +enum sun6i_dsi_inst_mode {
> + DSI_INST_MODE_STOP = 0,
> + DSI_INST_MODE_TBA,
> + DSI_INST_MODE_HS,
> + DSI_INST_MODE_ESCAPE,
> + DSI_INST_MODE_HSCEXIT,
> + DSI_INST_MODE_NOP,
> +};
> +
> +enum sun6i_dsi_inst_escape {
> + DSI_INST_ESCA_LPDT = 0,
> + DSI_INST_ESCA_ULPS,
> + DSI_INST_ESCA_UN1,
> + DSI_INST_ESCA_UN2,
> + DSI_INST_ESCA_RESET,
> + DSI_INST_ESCA_UN3,
> + DSI_INST_ESCA_UN4,
> + DSI_INST_ESCA_UN5,
> +};
> +
> +enum sun6i_dsi_inst_packet {
> + DSI_INST_PACK_PIXEL = 0,
> + DSI_INST_PACK_COMMAND,
> +};
> +
> +static u32 sun6i_dsi_ecc_array[] = {

const?

> + [0] = (BIT(0) | BIT(1) | BIT(2) | BIT(4) | BIT(5) | BIT(7) | BIT(10) |
> + BIT(11) | BIT(13) | BIT(16) | BIT(20) | BIT(21) | BIT(22) |
> + BIT(23)),
> + [1] = (BIT(0) | BIT(1) | BIT(3) | BIT(4) | BIT(6) | BIT(8) | BIT(10) |
> + BIT(12) | BIT(14) | BIT(17) | BIT(20) | BIT(21) | BIT(22) |
> + BIT(23)),
> + [2] = (BIT(0) | BIT(2) | BIT(3) | BIT(5) | BIT(6) | BIT(9) | BIT(11) |
> + BIT(12) | BIT(15) | BIT(18) | BIT(20) | BIT(21) | BIT(22)),
> + [3] = (BIT(1) | BIT(2) | BIT(3) | BIT(7) | BIT(8) | BIT(9) | BIT(13) |
> + BIT(14) | BIT(15) | BIT(19) | BIT(20) | BIT(21) | BIT(23)),
> + [4] = (BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9) | BIT(16) |
> + BIT(17) | BIT(18) | BIT(19) | BIT(20) | BIT(22) | BIT(23)),
> + [5] = (BIT(10) | BIT(11) | BIT(12) | BIT(13) | BIT(14) | BIT(15) |
> + BIT(16) | BIT(17) | BIT(18) | BIT(19) | BIT(21) | BIT(22) |
> + BIT(23)),
> +};
> +
> +static u32 sun6i_dsi_ecc_compute(unsigned int data)
> +{
> + int i;
> + u8 ecc = 0;
> +
> + for (i = 0; i < ARRAY_SIZE(sun6i_dsi_ecc_array); i++) {
> + u32 field = sun6i_dsi_ecc_array[i];
> + bool init = false;
> + u8 val = 0;
> + int j;
> +
> + for (j = 0; j < 24; j++) {
> + if (!(BIT(j) & field))
> + continue;
> +
> + if (!init) {
> + val = (BIT(j) & data) ? 1 : 0;
> + init = true;
> + } else {
> + val ^= (BIT(j) & data) ? 1 : 0;
> + }
> + }
> +
> + ecc |= val << i;
> + }
> +
> + return ecc;
> +}
> +
> +static u16 sun6i_dsi_crc_compute(u8 const *buffer, size_t len)
> +{
> + return crc_ccitt(0xffff, buffer, len);
> +}
> +
> +static u16 sun6i_dsi_crc_repeat_compute(u8 pd, size_t len)
> +{
> + u8 buffer[len];
> +
> + memset(buffer, pd, len);
> +
> + return sun6i_dsi_crc_compute(buffer, len);
> +}
> +
> +static u32 sun6i_dsi_build_sync_pkt(u8 dt, u8 vc, u8 d0, u8 d1)
> +{
> + u32 val = dt & 0x3f;
> +
> + val |= (vc & 3) << 6;
> + val |= (d0 & 0xff) << 8;
> + val |= (d1 & 0xff) << 16;
> + val |= sun6i_dsi_ecc_compute(val) << 24;
> +
> + return val;
> +}
> +
> +static u32 sun6i_dsi_build_blk0_pkt(u8 vc, u16 wc)
> +{
> + return sun6i_dsi_build_sync_pkt(MIPI_DSI_BLANKING_PACKET, vc,
> + wc & 0xff, wc >> 8);
> +}
> +
> +static u32 sun6i_dsi_build_blk1_pkt(u16 pd, size_t len)
> +{
> + u32 val = SUN6I_DSI_BLK_PD(pd);
> +
> + return val | SUN6I_DSI_BLK_PF(sun6i_dsi_crc_repeat_compute(pd, len));
> +}
> +
> +static void sun6i_dsi_inst_abort(struct sun6i_dsi *dsi)
> +{
> + regmap_update_bits(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG,
> + SUN6I_DSI_BASIC_CTL0_INST_ST, 0);
> +}
> +
> +static void sun6i_dsi_inst_commit(struct sun6i_dsi *dsi)
> +{
> + regmap_update_bits(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG,
> + SUN6I_DSI_BASIC_CTL0_INST_ST,
> + SUN6I_DSI_BASIC_CTL0_INST_ST);
> +}
> +
> +static int sun6i_dsi_inst_wait_for_completion(struct sun6i_dsi *dsi)
> +{
> + u32 val;
> +
> + return regmap_read_poll_timeout(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG,
> + val,
> + !(val & SUN6I_DSI_BASIC_CTL0_INST_ST),
> + 100, 5000);
> +}
> +
> +static void sun6i_dsi_inst_setup(struct sun6i_dsi *dsi,
> + enum sun6i_dsi_inst_id id,
> + enum sun6i_dsi_inst_mode mode,
> + bool clock, u8 data,
> + enum sun6i_dsi_inst_packet packet,
> + enum sun6i_dsi_inst_escape escape)
> +{
> + regmap_write(dsi->regs, SUN6I_DSI_INST_FUNC_REG(id),
> + SUN6I_DSI_INST_FUNC_INST_MODE(mode) |
> + SUN6I_DSI_INST_FUNC_ESCAPE_ENTRY(escape) |
> + SUN6I_DSI_INST_FUNC_TRANS_PACKET(packet) |
> + (clock ? SUN6I_DSI_INST_FUNC_LANE_CEN : 0) |
> + SUN6I_DSI_INST_FUNC_LANE_DEN(data));
> +}
> +
> +static void sun6i_dsi_inst_init(struct sun6i_dsi *dsi,
> + struct mipi_dsi_device *device)
> +{
> + u8 lanes_mask = GENMASK(device->lanes - 1, 0);
> +
> + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_LP11, DSI_INST_MODE_STOP,
> + true, lanes_mask, 0, 0);
> +
> + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_TBA, DSI_INST_MODE_TBA,
> + false, 1, 0, 0);
> +
> + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_HSC, DSI_INST_MODE_HS,
> + true, 0, DSI_INST_PACK_PIXEL, 0);
> +
> + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_HSD, DSI_INST_MODE_HS,
> + false, lanes_mask, DSI_INST_PACK_PIXEL, 0);
> +
> + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_LPDT, DSI_INST_MODE_ESCAPE,
> + false, 1, DSI_INST_PACK_COMMAND,
> + DSI_INST_ESCA_LPDT);
> +
> + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_HSCEXIT, DSI_INST_MODE_HSCEXIT,
> + true, 0, 0, 0);
> +
> + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_NOP, DSI_INST_MODE_STOP,
> + false, lanes_mask, 0, 0);
> +
> + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_DLY, DSI_INST_MODE_NOP,
> + true, lanes_mask, 0, 0);
> +
> + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_CFG_REG(0),
> + SUN6I_DSI_INST_JUMP_CFG_POINT(DSI_INST_ID_NOP) |
> + SUN6I_DSI_INST_JUMP_CFG_TO(DSI_INST_ID_HSCEXIT) |
> + SUN6I_DSI_INST_JUMP_CFG_NUM(1));
> +};
> +
> +static u16 sun6i_dsi_get_video_start_delay(struct sun6i_dsi *dsi,
> + struct drm_display_mode *mode)
> +{
> + return mode->vtotal - (mode->vsync_end - mode->vdisplay) + 1;
> +}
> +
> +static void sun6i_dsi_setup_burst(struct sun6i_dsi *dsi,
> + struct drm_display_mode *mode)
> +{
> + struct mipi_dsi_device *device = dsi->device;
> + u32 val = 0;
> +
> + if ((mode->hsync_end - mode->hdisplay) > 20) {
> + /* Maaaaaagic */
> + u16 drq = (mode->hsync_end - mode->hdisplay) - 20;
> +
> + drq *= mipi_dsi_pixel_format_to_bpp(device->format);
> + drq /= 32;
> +
> + val = (SUN6I_DSI_TCON_DRQ_ENABLE_MODE |
> + SUN6I_DSI_TCON_DRQ_SET(drq));
> + }
> +
> + regmap_write(dsi->regs, SUN6I_DSI_TCON_DRQ_REG, val);
> +}
> +
> +static void sun6i_dsi_setup_inst_loop(struct sun6i_dsi *dsi,
> + struct drm_display_mode *mode)
> +{
> + u16 delay = 50 - 1;
> +
> + regmap_write(dsi->regs, SUN6I_DSI_INST_LOOP_NUM_REG(0),
> + SUN6I_DSI_INST_LOOP_NUM_N0(50 - 1) |
> + SUN6I_DSI_INST_LOOP_NUM_N1(delay));
> + regmap_write(dsi->regs, SUN6I_DSI_INST_LOOP_NUM_REG(1),
> + SUN6I_DSI_INST_LOOP_NUM_N0(50 - 1) |
> + SUN6I_DSI_INST_LOOP_NUM_N1(delay));
> +}
> +
> +static void sun6i_dsi_setup_format(struct sun6i_dsi *dsi,
> + struct drm_display_mode *mode)
> +{
> + struct mipi_dsi_device *device = dsi->device;
> + u32 val = SUN6I_DSI_PIXEL_PH_VC(device->channel);
> + u8 dt, fmt;
> + u16 wc;
> +
> + /*
> + * TODO: The format defines are only valid in video mode and
> + * change in command mode.
> + */
> + switch (device->format) {
> + case MIPI_DSI_FMT_RGB888:
> + dt = MIPI_DSI_PACKED_PIXEL_STREAM_24;
> + fmt = 8;
> + break;
> + case MIPI_DSI_FMT_RGB666:
> + dt = MIPI_DSI_PIXEL_STREAM_3BYTE_18;
> + fmt = 9;
> + break;
> + case MIPI_DSI_FMT_RGB666_PACKED:
> + dt = MIPI_DSI_PACKED_PIXEL_STREAM_18;
> + fmt = 10;
> + break;
> + case MIPI_DSI_FMT_RGB565:
> + dt = MIPI_DSI_PACKED_PIXEL_STREAM_16;
> + fmt = 11;
> + break;
> + default:
> + return;
> + }
> + val |= SUN6I_DSI_PIXEL_PH_DT(dt);
> +
> + wc = mode->hdisplay * mipi_dsi_pixel_format_to_bpp(device->format) / 8;
> + val |= SUN6I_DSI_PIXEL_PH_WC(wc);
> + val |= SUN6I_DSI_PIXEL_PH_ECC(sun6i_dsi_ecc_compute(val));
> +
> + regmap_write(dsi->regs, SUN6I_DSI_PIXEL_PH_REG, val);
> +
> + regmap_write(dsi->regs, SUN6I_DSI_PIXEL_PF0_REG,
> + SUN6I_DSI_PIXEL_PF0_CRC_FORCE(0xffff));
> +
> + regmap_write(dsi->regs, SUN6I_DSI_PIXEL_PF1_REG,
> + SUN6I_DSI_PIXEL_PF1_CRC_INIT_LINE0(0xffff) |
> + SUN6I_DSI_PIXEL_PF1_CRC_INIT_LINEN(0xffff));
> +
> + regmap_write(dsi->regs, SUN6I_DSI_PIXEL_CTL0_REG,
> + SUN6I_DSI_PIXEL_CTL0_PD_PLUG_DISABLE |
> + SUN6I_DSI_PIXEL_CTL0_FORMAT(fmt));
> +}
> +
> +static void sun6i_dsi_setup_timings(struct sun6i_dsi *dsi,
> + struct drm_display_mode *mode)
> +{
> + struct mipi_dsi_device *device = dsi->device;
> + unsigned int Bpp = mipi_dsi_pixel_format_to_bpp(device->format) / 8;
> + u16 hbp, hfp, hsa, hblk, vblk;
> +
> + regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL_REG, 0);
> +
> + regmap_write(dsi->regs, SUN6I_DSI_SYNC_HSS_REG,
> + sun6i_dsi_build_sync_pkt(MIPI_DSI_H_SYNC_START,
> + device->channel,
> + 0, 0));
> +
> + regmap_write(dsi->regs, SUN6I_DSI_SYNC_HSE_REG,
> + sun6i_dsi_build_sync_pkt(MIPI_DSI_H_SYNC_END,
> + device->channel,
> + 0, 0));
> +
> + regmap_write(dsi->regs, SUN6I_DSI_SYNC_VSS_REG,
> + sun6i_dsi_build_sync_pkt(MIPI_DSI_V_SYNC_START,
> + device->channel,
> + 0, 0));
> +
> + regmap_write(dsi->regs, SUN6I_DSI_SYNC_VSE_REG,
> + sun6i_dsi_build_sync_pkt(MIPI_DSI_V_SYNC_END,
> + device->channel,
> + 0, 0));
> +
> + regmap_write(dsi->regs, SUN6I_DSI_BASIC_SIZE0_REG,
> + SUN6I_DSI_BASIC_SIZE0_VSA(mode->vsync_end -
> + mode->vsync_start) |
> + SUN6I_DSI_BASIC_SIZE0_VBP(mode->vsync_start -
> + mode->vdisplay));
> +
> + regmap_write(dsi->regs, SUN6I_DSI_BASIC_SIZE1_REG,
> + SUN6I_DSI_BASIC_SIZE1_VACT(mode->vdisplay) |
> + SUN6I_DSI_BASIC_SIZE1_VT(mode->vtotal));
> +
> + /*
> + * A sync period is composed of a blanking packet (4 bytes +
> + * payload + 2 bytes) and a sync event packet (4 bytes). Its
> + * minimal size is therefore 10 bytes
> + */
> +#define HSA_PACKET_OVERHEAD 10
> + hsa = max((unsigned int)HSA_PACKET_OVERHEAD,
> + (mode->hsync_end - mode->hsync_start) * Bpp - HSA_PACKET_OVERHEAD);
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_HSA0_REG,
> + sun6i_dsi_build_blk0_pkt(device->channel, hsa));
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_HSA1_REG,
> + sun6i_dsi_build_blk1_pkt(0, hsa));
> +
> + /*
> + * The backporch is set using a blanking packet (4 bytes +
> + * payload + 2 bytes). Its minimal size is therefore 6 bytes
> + */
> +#define HBP_PACKET_OVERHEAD 6
> + hbp = max((unsigned int)HBP_PACKET_OVERHEAD,
> + (mode->hsync_start - mode->hdisplay) * Bpp - HBP_PACKET_OVERHEAD);
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_HBP0_REG,
> + sun6i_dsi_build_blk0_pkt(device->channel, hbp));
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_HBP1_REG,
> + sun6i_dsi_build_blk1_pkt(0, hbp));
> +
> + /*
> + * The frontporch is set using a blanking packet (4 bytes +
> + * payload + 2 bytes). Its minimal size is therefore 6 bytes
> + */
> +#define HFP_PACKET_OVERHEAD 6
> + hfp = max((unsigned int)HFP_PACKET_OVERHEAD,
> + (mode->htotal - mode->hsync_end) * Bpp - HFP_PACKET_OVERHEAD);
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_HFP0_REG,
> + sun6i_dsi_build_blk0_pkt(device->channel, hfp));
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_HFP1_REG,
> + sun6i_dsi_build_blk1_pkt(0, hfp));
> +
> + /*
> + * hblk seems to be the line + porches length.
> + */
> + hblk = mode->htotal * Bpp - hsa;
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_HBLK0_REG,
> + sun6i_dsi_build_blk0_pkt(device->channel, hblk));
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_HBLK1_REG,
> + sun6i_dsi_build_blk1_pkt(0, hblk));
> +
> + /*
> + * And I'm not entirely sure what vblk is about. The driver in
> + * Allwinner BSP is using a rather convoluted calculation
> + * there only for 4 lanes. However, using 0 (the !4 lanes
> + * case) even with a 4 lanes screen seems to work...
> + */
> + vblk = 0;
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_VBLK0_REG,
> + sun6i_dsi_build_blk0_pkt(device->channel, vblk));
> + regmap_write(dsi->regs, SUN6I_DSI_BLK_VBLK1_REG,
> + sun6i_dsi_build_blk1_pkt(0, vblk));
> +}
> +
> +static int sun6i_dsi_start(struct sun6i_dsi *dsi,
> + enum sun6i_dsi_start_inst func)
> +{
> + switch (func) {
> + case DSI_START_LPTX:
> + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG,
> + DSI_INST_ID_LPDT << (4 * DSI_INST_ID_LP11) |
> + DSI_INST_ID_END << (4 * DSI_INST_ID_LPDT));
> + break;
> + case DSI_START_LPRX:
> + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG,
> + DSI_INST_ID_LPDT << (4 * DSI_INST_ID_LP11) |
> + DSI_INST_ID_DLY << (4 * DSI_INST_ID_LPDT) |
> + DSI_INST_ID_TBA << (4 * DSI_INST_ID_DLY) |
> + DSI_INST_ID_END << (4 * DSI_INST_ID_TBA));
> + break;
> + case DSI_START_HSC:
> + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG,
> + DSI_INST_ID_HSC << (4 * DSI_INST_ID_LP11) |
> + DSI_INST_ID_END << (4 * DSI_INST_ID_HSC));
> + break;
> + case DSI_START_HSD:
> + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG,
> + DSI_INST_ID_NOP << (4 * DSI_INST_ID_LP11) |
> + DSI_INST_ID_HSD << (4 * DSI_INST_ID_NOP) |
> + DSI_INST_ID_DLY << (4 * DSI_INST_ID_HSD) |
> + DSI_INST_ID_NOP << (4 * DSI_INST_ID_DLY) |
> + DSI_INST_ID_END << (4 * DSI_INST_ID_HSCEXIT));
> + break;
> + default:
> + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG,
> + DSI_INST_ID_END << (4 * DSI_INST_ID_LP11));
> + break;
> + }
> +
> + sun6i_dsi_inst_abort(dsi);
> + sun6i_dsi_inst_commit(dsi);
> +
> + if (func == DSI_START_HSC)
> + regmap_write_bits(dsi->regs,
> + SUN6I_DSI_INST_FUNC_REG(DSI_INST_ID_LP11),
> + SUN6I_DSI_INST_FUNC_LANE_CEN, 0);
> +
> + return 0;
> +}
> +
> +static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder)
> +{
> + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
> + struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder);
> + struct mipi_dsi_device *device = dsi->device;
> + u16 delay;
> +
> + DRM_DEBUG_DRIVER("Enabling DSI output\n");
> +
> + pm_runtime_get_sync(dsi->dev);
> +
> + delay = sun6i_dsi_get_video_start_delay(dsi, mode);
> + regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL1_REG,
> + SUN6I_DSI_BASIC_CTL1_VIDEO_ST_DELAY(delay) |
> + SUN6I_DSI_BASIC_CTL1_VIDEO_FILL |
> + SUN6I_DSI_BASIC_CTL1_VIDEO_PRECISION |
> + SUN6I_DSI_BASIC_CTL1_VIDEO_MODE);
> +
> + sun6i_dsi_setup_burst(dsi, mode);
> + sun6i_dsi_setup_inst_loop(dsi, mode);
> + sun6i_dsi_setup_format(dsi, mode);
> + sun6i_dsi_setup_timings(dsi, mode);
> +
> + sun6i_dphy_init(dsi->dphy, device->lanes);
> + sun6i_dphy_power_on(dsi->dphy, device->lanes);
> +
> + if (!IS_ERR(dsi->panel))
> + drm_panel_prepare(dsi->panel);
> +
> + /*
> + * FIXME: This should be moved after the switch to HS mode.
> + *
> + * Unfortunately, once in HS mode, it seems like we're not
> + * able to send DCS commands anymore, which would prevent any
> + * panel to send any DCS command as part as their enable
> + * method, which is quite common.
> + *
> + * I haven't seen any artifact due to that sub-optimal
> + * ordering on the panels I've tested it with, so I guess this
> + * will do for now, until that IP is better understood.
> + */
> + if (!IS_ERR(dsi->panel))
> + drm_panel_enable(dsi->panel);
> +
> + sun6i_dsi_start(dsi, DSI_START_HSC);
> +
> + udelay(1000);
> +
> + sun6i_dsi_start(dsi, DSI_START_HSD);
> +}
> +
> +static void sun6i_dsi_encoder_disable(struct drm_encoder *encoder)
> +{
> + struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder);
> +
> + DRM_DEBUG_DRIVER("Disabling DSI output\n");
> +
> + if (!IS_ERR(dsi->panel)) {
> + drm_panel_disable(dsi->panel);
> + drm_panel_unprepare(dsi->panel);
> + }
> +
> + sun6i_dphy_power_off(dsi->dphy);
> + sun6i_dphy_exit(dsi->dphy);
> +
> + pm_runtime_put(dsi->dev);
> +}
> +
> +static int sun6i_dsi_get_modes(struct drm_connector *connector)
> +{
> + struct sun6i_dsi *dsi = connector_to_sun6i_dsi(connector);
> +
> + return drm_panel_get_modes(dsi->panel);
> +}
> +
> +static struct drm_connector_helper_funcs sun6i_dsi_connector_helper_funcs = {
> + .get_modes = sun6i_dsi_get_modes,
> +};
> +
> +static enum drm_connector_status
> +sun6i_dsi_connector_detect(struct drm_connector *connector, bool force)
> +{
> + return connector_status_connected;
> +}
> +
> +static const struct drm_connector_funcs sun6i_dsi_connector_funcs = {
> + .detect = sun6i_dsi_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 const struct drm_encoder_helper_funcs sun6i_dsi_enc_helper_funcs = {
> + .disable = sun6i_dsi_encoder_disable,
> + .enable = sun6i_dsi_encoder_enable,
> +};
> +
> +static const struct drm_encoder_funcs sun6i_dsi_enc_funcs = {
> + .destroy = drm_encoder_cleanup,
> +};
> +
> +static u32 sun6i_dsi_dcs_build_pkt_hdr(struct sun6i_dsi *dsi,
> + const struct mipi_dsi_msg *msg)
> +{
> + u32 pkt = msg->type;
> +
> + if (msg->type == MIPI_DSI_DCS_LONG_WRITE) {
> + pkt |= ((msg->tx_len + 1) & 0xffff) << 8;
> + pkt |= (((msg->tx_len + 1) >> 8) & 0xffff) << 16;
> + } else {
> + pkt |= (((u8 *)msg->tx_buf)[0] << 8);
> + if (msg->tx_len > 1)
> + pkt |= (((u8 *)msg->tx_buf)[1] << 16);
> + }
> +
> + pkt |= sun6i_dsi_ecc_compute(pkt) << 24;
> +
> + return pkt;
> +}
> +
> +static int sun6i_dsi_dcs_write_short(struct sun6i_dsi *dsi,
> + const struct mipi_dsi_msg *msg)
> +{
> + regmap_write(dsi->regs, SUN6I_DSI_CMD_TX_REG(0),
> + sun6i_dsi_dcs_build_pkt_hdr(dsi, msg));
> + regmap_write_bits(dsi->regs, SUN6I_DSI_CMD_CTL_REG,
> + 0xff, (4 - 1));
> +
> + sun6i_dsi_start(dsi, DSI_START_LPTX);
> +
> + return msg->tx_len;
> +}
> +
> +static int sun6i_dsi_dcs_write_long(struct sun6i_dsi *dsi,
> + const struct mipi_dsi_msg *msg)
> +{
> + int ret, len = 0;
> + u8 *bounce;
> + u16 crc;
> +
> + regmap_write(dsi->regs, SUN6I_DSI_CMD_TX_REG(0),
> + sun6i_dsi_dcs_build_pkt_hdr(dsi, msg));
> +
> + bounce = kzalloc(msg->tx_len + sizeof(crc), GFP_KERNEL);
> + if (!bounce)
> + return -ENOMEM;
> +
> + memcpy(bounce, msg->tx_buf, msg->tx_len);
> + len += msg->tx_len;
> +
> + crc = sun6i_dsi_crc_compute(bounce, msg->tx_len);
> + memcpy((u8 *)bounce + msg->tx_len, &crc, sizeof(crc));
> + len += sizeof(crc);
> +
> + regmap_bulk_write(dsi->regs, SUN6I_DSI_CMD_TX_REG(1), bounce, len);
> + regmap_write(dsi->regs, SUN6I_DSI_CMD_CTL_REG, len + 4 - 1);
> + kfree(bounce);
> +
> + sun6i_dsi_start(dsi, DSI_START_LPTX);
> +
> + ret = sun6i_dsi_inst_wait_for_completion(dsi);
> + if (ret < 0) {
> + sun6i_dsi_inst_abort(dsi);
> + return ret;
> + }
> +
> + /*
> + * TODO: There's some bits (reg 0x200, bits 8/9) that
> + * apparently can be used to check whether the data have been
> + * sent, but I couldn't get it to work reliably.
> + */
> + return msg->tx_len;
> +}
> +
> +static int sun6i_dsi_dcs_read(struct sun6i_dsi *dsi,
> + const struct mipi_dsi_msg *msg)
> +{
> + u32 val;
> + int ret;
> + u8 byte0;
> +
> + regmap_write(dsi->regs, SUN6I_DSI_CMD_TX_REG(0),
> + sun6i_dsi_dcs_build_pkt_hdr(dsi, msg));
> + regmap_write(dsi->regs, SUN6I_DSI_CMD_CTL_REG,
> + (4 - 1));
> +
> + sun6i_dsi_start(dsi, DSI_START_LPRX);
> +
> + ret = sun6i_dsi_inst_wait_for_completion(dsi);
> + if (ret < 0) {
> + sun6i_dsi_inst_abort(dsi);
> + return ret;
> + }
> +
> + /*
> + * TODO: There's some bits (reg 0x200, bits 24/25) that
> + * apparently can be used to check whether the data have been
> + * received, but I couldn't get it to work reliably.
> + */
> + regmap_read(dsi->regs, SUN6I_DSI_CMD_CTL_REG, &val);
> + if (val & SUN6I_DSI_CMD_CTL_RX_OVERFLOW)
> + return -EIO;
> +
> + regmap_read(dsi->regs, SUN6I_DSI_CMD_RX_REG(0), &val);
> + byte0 = val & 0xff;
> + if (byte0 == MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT)
> + return -EIO;
> +
> + ((u8 *)msg->rx_buf)[0] = (val >> 8);
> +
> + return 1;
> +}
> +
> +static int sun6i_dsi_attach(struct mipi_dsi_host *host,
> + struct mipi_dsi_device *device)
> +{
> + struct sun6i_dsi *dsi = host_to_sun6i_dsi(host);
> +
> + dsi->device = device;
> + dsi->panel = of_drm_find_panel(device->dev.of_node);
> + if (!dsi->panel)
> + return -EINVAL;
> +
> + dev_info(host->dev, "Attached device %s\n", device->name);
> +
> + return 0;
> +}
> +
> +static int sun6i_dsi_detach(struct mipi_dsi_host *host,
> + struct mipi_dsi_device *device)
> +{
> + struct sun6i_dsi *dsi = host_to_sun6i_dsi(host);
> +
> + dsi->panel = NULL;
> + dsi->device = NULL;
> +
> + return 0;
> +}
> +
> +static ssize_t sun6i_dsi_transfer(struct mipi_dsi_host *host,
> + const struct mipi_dsi_msg *msg)
> +{
> + struct sun6i_dsi *dsi = host_to_sun6i_dsi(host);
> + int ret;
> +
> + ret = sun6i_dsi_inst_wait_for_completion(dsi);
> + if (ret < 0)
> + sun6i_dsi_inst_abort(dsi);
> +
> + regmap_write(dsi->regs, SUN6I_DSI_CMD_CTL_REG,
> + SUN6I_DSI_CMD_CTL_RX_OVERFLOW |
> + SUN6I_DSI_CMD_CTL_RX_FLAG |
> + SUN6I_DSI_CMD_CTL_TX_FLAG);
> +
> + switch (msg->type) {
> + case MIPI_DSI_DCS_SHORT_WRITE:
> + case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
> + ret = sun6i_dsi_dcs_write_short(dsi, msg);
> + break;
> +
> + case MIPI_DSI_DCS_LONG_WRITE:
> + ret = sun6i_dsi_dcs_write_long(dsi, msg);
> + break;
> +
> + case MIPI_DSI_DCS_READ:
> + if (msg->rx_len == 1) {
> + ret = sun6i_dsi_dcs_read(dsi, msg);
> + break;
> + }
> +
> + default:
> + ret = -EINVAL;
> + }
> +
> + return ret;
> +}
> +
> +static const struct mipi_dsi_host_ops sun6i_dsi_host_ops = {
> + .attach = sun6i_dsi_attach,
> + .detach = sun6i_dsi_detach,
> + .transfer = sun6i_dsi_transfer,
> +};
> +
> +static const struct regmap_config sun6i_dsi_regmap_config = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> + .max_register = SUN6I_DSI_CMD_TX_REG(255),
> + .name = "mipi-dsi",
> +};
> +
> +static int sun6i_dsi_bind(struct device *dev, struct device *master,
> + void *data)
> +{
> + struct drm_device *drm = data;
> + struct sun4i_drv *drv = drm->dev_private;
> + struct sun6i_dsi *dsi = dev_get_drvdata(dev);
> + int ret;
> +
> + if (!dsi->panel)
> + return -EPROBE_DEFER;
> +
> + dsi->drv = drv;
> +
> + drm_encoder_helper_add(&dsi->encoder,
> + &sun6i_dsi_enc_helper_funcs);
> + ret = drm_encoder_init(drm,
> + &dsi->encoder,
> + &sun6i_dsi_enc_funcs,
> + DRM_MODE_ENCODER_DSI,
> + NULL);
> + if (ret) {
> + dev_err(dsi->dev, "Couldn't initialise the DSI encoder\n");
> + return ret;
> + }
> + dsi->encoder.possible_crtcs = BIT(0);
> +
> + drm_connector_helper_add(&dsi->connector,
> + &sun6i_dsi_connector_helper_funcs);
> + ret = drm_connector_init(drm, &dsi->connector,
> + &sun6i_dsi_connector_funcs,
> + DRM_MODE_CONNECTOR_DSI);
> + if (ret) {
> + dev_err(dsi->dev,
> + "Couldn't initialise the DSI connector\n");
> + goto err_cleanup_connector;
> + }
> +
> + drm_mode_connector_attach_encoder(&dsi->connector, &dsi->encoder);
> + drm_panel_attach(dsi->panel, &dsi->connector);
> +
> + return 0;
> +
> +err_cleanup_connector:
> + drm_encoder_cleanup(&dsi->encoder);
> + return ret;
> +}
> +
> +static void sun6i_dsi_unbind(struct device *dev, struct device *master,
> + void *data)
> +{
> + struct sun6i_dsi *dsi = dev_get_drvdata(dev);
> +
> + drm_panel_detach(dsi->panel);
> +}
> +
> +static const struct component_ops sun6i_dsi_ops = {
> + .bind = sun6i_dsi_bind,
> + .unbind = sun6i_dsi_unbind,
> +};
> +
> +static int sun6i_dsi_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *dphy_node;
> + struct sun6i_dsi *dsi;
> + struct resource *res;
> + void __iomem *base;
> + int ret;
> +
> + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
> + if (!dsi)
> + return -ENOMEM;
> + dev_set_drvdata(dev, dsi);
> + dsi->dev = dev;
> + dsi->host.ops = &sun6i_dsi_host_ops;
> + dsi->host.dev = dev;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + base = devm_ioremap_resource(dev, res);
> + if (IS_ERR(base)) {
> + dev_err(dev, "Couldn't map the DSI encoder registers\n");
> + return PTR_ERR(base);
> + }
> +
> + dsi->regs = devm_regmap_init_mmio_clk(dev, "bus", base,
> + &sun6i_dsi_regmap_config);
> + if (IS_ERR(dsi->regs)) {
> + dev_err(dev, "Couldn't create the DSI encoder regmap\n");
> + return PTR_ERR(dsi->regs);
> + }
> +
> + dsi->reset = devm_reset_control_get_shared(dev, NULL);
> + if (IS_ERR(dsi->reset)) {
> + dev_err(dev, "Couldn't get our reset line\n");
> + return PTR_ERR(dsi->reset);
> + }
> +
> + dsi->mod_clk = devm_clk_get(dev, "mod");
> + if (IS_ERR(dsi->mod_clk)) {
> + dev_err(dev, "Couldn't get the DSI mod clock\n");
> + return PTR_ERR(dsi->mod_clk);
> + }
> +
> + /*
> + * In order to operate properly, that clock seems to be always
> + * set to 297MHz.
> + */
> + clk_set_rate_exclusive(dsi->mod_clk, 297000000);
> +
> + dphy_node = of_parse_phandle(dev->of_node, "phys", 0);
> + ret = sun6i_dphy_probe(dsi, dphy_node);
> + of_node_put(dphy_node);
> + if (ret) {
> + dev_err(dev, "Couldn't get the MIPI D-PHY\n");
> + goto err_unprotect_clk;
> + }
> +
> + pm_runtime_enable(dev);
> +
> + ret = mipi_dsi_host_register(&dsi->host);
> + if (ret) {
> + dev_err(dev, "Couldn't register MIPI-DSI host\n");
> + goto err_remove_phy;
> + }
> +
> + ret = component_add(&pdev->dev, &sun6i_dsi_ops);
> + if (ret) {
> + dev_err(dev, "Couldn't register our component\n");
> + goto err_remove_dsi_host;
> + }
> +
> + return 0;
> +
> +err_remove_dsi_host:
> + mipi_dsi_host_unregister(&dsi->host);
> +err_remove_phy:
> + pm_runtime_disable(dev);
> + sun6i_dphy_remove(dsi);
> +err_unprotect_clk:
> + clk_rate_exclusive_put(dsi->mod_clk);
> + return ret;
> +}
> +
> +static int sun6i_dsi_remove(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct sun6i_dsi *dsi = dev_get_drvdata(dev);
> +
> + component_del(&pdev->dev, &sun6i_dsi_ops);
> + mipi_dsi_host_unregister(&dsi->host);
> + pm_runtime_disable(dev);
> + sun6i_dphy_remove(dsi);
> + clk_rate_exclusive_put(dsi->mod_clk);
> +
> + return 0;
> +}
> +
> +static int sun6i_dsi_runtime_resume(struct device *dev)
> +{
> + struct sun6i_dsi *dsi = dev_get_drvdata(dev);
> +
> + reset_control_deassert(dsi->reset);
> + clk_prepare_enable(dsi->mod_clk);
> +
> + /*
> + * Enable the DSI block.
> + *
> + * Some part of it can only be done once we get a number of
> + * lanes, see sun6i_dsi_inst_init
> + */
> + regmap_write(dsi->regs, SUN6I_DSI_CTL_REG, SUN6I_DSI_CTL_EN);
> +
> + regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG,
> + SUN6I_DSI_BASIC_CTL0_ECC_EN | SUN6I_DSI_BASIC_CTL0_CRC_EN);
> +
> + regmap_write(dsi->regs, SUN6I_DSI_TRANS_START_REG, 10);
> + regmap_write(dsi->regs, SUN6I_DSI_TRANS_ZERO_REG, 0);
> +
> + if (dsi->device)
> + sun6i_dsi_inst_init(dsi, dsi->device);
> +
> + regmap_write(dsi->regs, SUN6I_DSI_DEBUG_DATA_REG, 0xff);
> +
> + return 0;
> +}
> +
> +static int sun6i_dsi_runtime_suspend(struct device *dev)
> +{
> + struct sun6i_dsi *dsi = dev_get_drvdata(dev);
> +
> + clk_disable_unprepare(dsi->mod_clk);
> + reset_control_assert(dsi->reset);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops sun6i_dsi_pm_ops = {
> + SET_RUNTIME_PM_OPS(sun6i_dsi_runtime_suspend,
> + sun6i_dsi_runtime_resume,
> + NULL)
> +};
> +
> +static const struct of_device_id sun6i_dsi_of_table[] = {
> + { .compatible = "allwinner,sun6i-a31-mipi-dsi" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, sun6i_dsi_of_table);
> +
> +static struct platform_driver sun6i_dsi_platform_driver = {
> + .probe = sun6i_dsi_probe,
> + .remove = sun6i_dsi_remove,
> + .driver = {
> + .name = "sun6i-mipi-dsi",
> + .of_match_table = sun6i_dsi_of_table,
> + .pm = &sun6i_dsi_pm_ops,
> + },
> +};
> +module_platform_driver(sun6i_dsi_platform_driver);
> +
> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Allwinner A31 DSI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
> new file mode 100644
> index 000000000000..638fec374a80
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
> @@ -0,0 +1,67 @@
> +/*
> + * Copyright (c) 2016 Allwinnertech Co., Ltd.
> + * Copyright (C) 2017 Free Electrons
> + *
> + * Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#ifndef _SUN6I_MIPI_DSI_H_
> +#define _SUN6I_MIPI_DSI_H_
> +
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_mipi_dsi.h>
> +
> +struct sun6i_dphy {
> + struct clk *bus_clk;
> + struct clk *mod_clk;
> + struct regmap *regs;
> + struct reset_control *reset;
> +};
> +
> +struct sun6i_dsi {
> + struct drm_connector connector;
> + struct drm_encoder encoder;
> + struct mipi_dsi_host host;
> +
> + struct clk *bus_clk;
> + struct clk *mod_clk;
> + struct regmap *regs;
> + struct reset_control *reset;
> + struct sun6i_dphy *dphy;
> +
> + struct device *dev;
> + struct sun4i_drv *drv;
> + struct mipi_dsi_device *device;
> + struct drm_panel *panel;
> +};
> +
> +static inline struct sun6i_dsi *host_to_sun6i_dsi(struct mipi_dsi_host *host)
> +{
> + return container_of(host, struct sun6i_dsi, host);
> +};
> +
> +static inline struct sun6i_dsi *connector_to_sun6i_dsi(struct drm_connector *connector)
> +{
> + return container_of(connector, struct sun6i_dsi, connector);
> +};
> +
> +static inline struct sun6i_dsi *encoder_to_sun6i_dsi(const struct drm_encoder *encoder)
> +{
> + return container_of(encoder, struct sun6i_dsi, encoder);
> +};
> +
> +int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node);
> +int sun6i_dphy_remove(struct sun6i_dsi *dsi);
> +
> +int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes);
> +int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes);
> +int sun6i_dphy_power_off(struct sun6i_dphy *dphy);
> +int sun6i_dphy_exit(struct sun6i_dphy *dphy);
> +
> +#endif /* _SUN6I_MIPI_DSI_H_ */
> --
> git-series 0.9.1


So I don't know much about DSI in general, and the docs for this hardware
are kind of sparse. FWIW, the code structure looks sound, so

Reviewed-by: Chen-Yu Tsai <wens@xxxxxxxx>

I would get someone to take a closer look at the DSI sequences though.

ChenYu