[PATCH v2 1/3] drm/bridge/synopsys: Add MIPI DSI2 host controller bridge

neil.armstrong at linaro.org neil.armstrong at linaro.org
Wed Nov 27 10:16:49 UTC 2024


On 26/11/2024 21:12, Heiko Stuebner wrote:
> From: Heiko Stuebner <heiko.stuebner at cherry.de>
> 
> Add a Synopsys Designware MIPI DSI host DRM bridge driver for their
> DSI2 host controller, based on the Rockchip version from the driver
> rockchip/dw-mipi-dsi2.c in their vendor-kernel with phy & bridge APIs.
> 
> While the driver is heavily modelled after the previous IP, the register
> set of this DSI2 controller is completely different and there are also
> additional properties like the variable-width phy interface.
> 
> Tested-by: Daniel Semkowicz <dse at thaumatec.com>
> Signed-off-by: Heiko Stuebner <heiko.stuebner at cherry.de>
> ---
>   drivers/gpu/drm/bridge/synopsys/Kconfig       |    6 +
>   drivers/gpu/drm/bridge/synopsys/Makefile      |    1 +
>   .../gpu/drm/bridge/synopsys/dw-mipi-dsi2.c    | 1030 +++++++++++++++++
>   include/drm/bridge/dw_mipi_dsi2.h             |   95 ++
>   4 files changed, 1132 insertions(+)
>   create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c
>   create mode 100644 include/drm/bridge/dw_mipi_dsi2.h
> 
> diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
> index ca416dab156d..f3ab2f985f8c 100644
> --- a/drivers/gpu/drm/bridge/synopsys/Kconfig
> +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
> @@ -59,3 +59,9 @@ config DRM_DW_MIPI_DSI
>   	select DRM_KMS_HELPER
>   	select DRM_MIPI_DSI
>   	select DRM_PANEL_BRIDGE
> +
> +config DRM_DW_MIPI_DSI2
> +	tristate
> +	select DRM_KMS_HELPER
> +	select DRM_MIPI_DSI
> +	select DRM_PANEL_BRIDGE
> diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile
> index 9869d9651ed1..9dc376d220ad 100644
> --- a/drivers/gpu/drm/bridge/synopsys/Makefile
> +++ b/drivers/gpu/drm/bridge/synopsys/Makefile
> @@ -8,3 +8,4 @@ obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o
>   obj-$(CONFIG_DRM_DW_HDMI_QP) += dw-hdmi-qp.o
>   
>   obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw-mipi-dsi.o
> +obj-$(CONFIG_DRM_DW_MIPI_DSI2) += dw-mipi-dsi2.o
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c
> new file mode 100644
> index 000000000000..099cf812650a
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c
> @@ -0,0 +1,1030 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (c) 2024, Fuzhou Rockchip Electronics Co., Ltd
> + *
> + * Modified by Heiko Stuebner <heiko.stuebner at cherry.de>
> + * This generic Synopsys DesignWare MIPI DSI2 host driver is based on the
> + * Rockchip version from rockchip/dw-mipi-dsi2.c converted to use bridge APIs.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/iopoll.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +
> +#include <video/mipi_display.h>
> +
> +#include <drm/bridge/dw_mipi_dsi2.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_print.h>
> +
> +

Nit: spurious line

> +#define DSI2_PWR_UP			0x000c
> +#define RESET				0
> +#define POWER_UP			BIT(0)
> +#define CMD_TX_MODE(x)			FIELD_PREP(BIT(24), x)
> +#define DSI2_SOFT_RESET			0x0010
> +#define SYS_RSTN			BIT(2)
> +#define PHY_RSTN			BIT(1)
> +#define IPI_RSTN			BIT(0)
> +#define INT_ST_MAIN			0x0014
> +#define DSI2_MODE_CTRL			0x0018
> +#define DSI2_MODE_STATUS		0x001c
> +#define DSI2_CORE_STATUS		0x0020
> +#define PRI_RD_DATA_AVAIL		BIT(26)
> +#define PRI_FIFOS_NOT_EMPTY		BIT(25)
> +#define PRI_BUSY			BIT(24)
> +#define CRI_RD_DATA_AVAIL		BIT(18)
> +#define CRT_FIFOS_NOT_EMPTY		BIT(17)
> +#define CRI_BUSY			BIT(16)
> +#define IPI_FIFOS_NOT_EMPTY		BIT(9)
> +#define IPI_BUSY			BIT(8)
> +#define CORE_FIFOS_NOT_EMPTY		BIT(1)
> +#define CORE_BUSY			BIT(0)
> +#define MANUAL_MODE_CFG			0x0024
> +#define MANUAL_MODE_EN			BIT(0)
> +#define DSI2_TIMEOUT_HSTX_CFG		0x0048
> +#define TO_HSTX(x)			FIELD_PREP(GENMASK(15, 0), x)
> +#define DSI2_TIMEOUT_HSTXRDY_CFG	0x004c
> +#define TO_HSTXRDY(x)			FIELD_PREP(GENMASK(15, 0), x)
> +#define DSI2_TIMEOUT_LPRX_CFG		0x0050
> +#define TO_LPRXRDY(x)			FIELD_PREP(GENMASK(15, 0), x)
> +#define DSI2_TIMEOUT_LPTXRDY_CFG	0x0054
> +#define TO_LPTXRDY(x)			FIELD_PREP(GENMASK(15, 0), x)
> +#define DSI2_TIMEOUT_LPTXTRIG_CFG	0x0058
> +#define TO_LPTXTRIG(x)			FIELD_PREP(GENMASK(15, 0), x)
> +#define DSI2_TIMEOUT_LPTXULPS_CFG	0x005c
> +#define TO_LPTXULPS(x)			FIELD_PREP(GENMASK(15, 0), x)
> +#define DSI2_TIMEOUT_BTA_CFG		0x60
> +#define TO_BTA(x)			FIELD_PREP(GENMASK(15, 0), x)
> +
> +#define DSI2_PHY_MODE_CFG		0x0100
> +#define PPI_WIDTH(x)			FIELD_PREP(GENMASK(9, 8), x)
> +#define PHY_LANES(x)			FIELD_PREP(GENMASK(5, 4), (x) - 1)
> +#define PHY_TYPE(x)			FIELD_PREP(BIT(0), x)
> +#define DSI2_PHY_CLK_CFG		0X0104
> +#define PHY_LPTX_CLK_DIV(x)		FIELD_PREP(GENMASK(12, 8), x)
> +#define CLK_TYPE_MASK			BIT(0)
> +#define NON_CONTINUOUS_CLK		BIT(0)
> +#define CONTINUOUS_CLK			0
> +#define DSI2_PHY_LP2HS_MAN_CFG		0x010c
> +#define PHY_LP2HS_TIME(x)		FIELD_PREP(GENMASK(28, 0), x)
> +#define DSI2_PHY_HS2LP_MAN_CFG		0x0114
> +#define PHY_HS2LP_TIME(x)		FIELD_PREP(GENMASK(28, 0), x)
> +#define DSI2_PHY_MAX_RD_T_MAN_CFG	0x011c
> +#define PHY_MAX_RD_TIME(x)		FIELD_PREP(GENMASK(26, 0), x)
> +#define DSI2_PHY_ESC_CMD_T_MAN_CFG	0x0124
> +#define PHY_ESC_CMD_TIME(x)		FIELD_PREP(GENMASK(28, 0), x)
> +#define DSI2_PHY_ESC_BYTE_T_MAN_CFG	0x012c
> +#define PHY_ESC_BYTE_TIME(x)		FIELD_PREP(GENMASK(28, 0), x)
> +
> +#define DSI2_PHY_IPI_RATIO_MAN_CFG	0x0134
> +#define PHY_IPI_RATIO(x)		FIELD_PREP(GENMASK(21, 0), x)
> +#define DSI2_PHY_SYS_RATIO_MAN_CFG	0x013C
> +#define PHY_SYS_RATIO(x)		FIELD_PREP(GENMASK(16, 0), x)
> +
> +#define DSI2_DSI_GENERAL_CFG		0x0200
> +#define BTA_EN				BIT(1)
> +#define EOTP_TX_EN			BIT(0)
> +#define DSI2_DSI_VCID_CFG		0x0204
> +#define TX_VCID(x)			FIELD_PREP(GENMASK(1, 0), x)
> +#define DSI2_DSI_SCRAMBLING_CFG		0x0208
> +#define SCRAMBLING_SEED(x)		FIELD_PREP(GENMASK(31, 16), x)
> +#define SCRAMBLING_EN			BIT(0)
> +#define DSI2_DSI_VID_TX_CFG		0x020c
> +#define LPDT_DISPLAY_CMD_EN		BIT(20)
> +#define BLK_VFP_HS_EN			BIT(14)
> +#define BLK_VBP_HS_EN			BIT(13)
> +#define BLK_VSA_HS_EN			BIT(12)
> +#define BLK_HFP_HS_EN			BIT(6)
> +#define BLK_HBP_HS_EN			BIT(5)
> +#define BLK_HSA_HS_EN			BIT(4)
> +#define VID_MODE_TYPE(x)		FIELD_PREP(GENMASK(1, 0), x)
> +#define DSI2_CRI_TX_HDR			0x02c0
> +#define CMD_TX_MODE(x)			FIELD_PREP(BIT(24), x)
> +#define DSI2_CRI_TX_PLD			0x02c4
> +#define DSI2_CRI_RX_HDR			0x02c8
> +#define DSI2_CRI_RX_PLD			0x02cc
> +
> +#define DSI2_IPI_COLOR_MAN_CFG		0x0300
> +#define IPI_DEPTH(x)			FIELD_PREP(GENMASK(7, 4), x)
> +#define IPI_DEPTH_5_6_5_BITS		0x02
> +#define IPI_DEPTH_6_BITS		0x03
> +#define IPI_DEPTH_8_BITS		0x05
> +#define IPI_DEPTH_10_BITS		0x06
> +#define IPI_FORMAT(x)			FIELD_PREP(GENMASK(3, 0), x)
> +#define IPI_FORMAT_RGB			0x0
> +#define IPI_FORMAT_DSC			0x0b
> +#define DSI2_IPI_VID_HSA_MAN_CFG	0x0304
> +#define VID_HSA_TIME(x)			FIELD_PREP(GENMASK(29, 0), x)
> +#define DSI2_IPI_VID_HBP_MAN_CFG	0x030c
> +#define VID_HBP_TIME(x)			FIELD_PREP(GENMASK(29, 0), x)
> +#define DSI2_IPI_VID_HACT_MAN_CFG	0x0314
> +#define VID_HACT_TIME(x)		FIELD_PREP(GENMASK(29, 0), x)
> +#define DSI2_IPI_VID_HLINE_MAN_CFG	0x031c
> +#define VID_HLINE_TIME(x)		FIELD_PREP(GENMASK(29, 0), x)
> +#define DSI2_IPI_VID_VSA_MAN_CFG	0x0324
> +#define VID_VSA_LINES(x)		FIELD_PREP(GENMASK(9, 0), x)
> +#define DSI2_IPI_VID_VBP_MAN_CFG	0X032C
> +#define VID_VBP_LINES(x)		FIELD_PREP(GENMASK(9, 0), x)
> +#define DSI2_IPI_VID_VACT_MAN_CFG	0X0334
> +#define VID_VACT_LINES(x)		FIELD_PREP(GENMASK(13, 0), x)
> +#define DSI2_IPI_VID_VFP_MAN_CFG	0X033C
> +#define VID_VFP_LINES(x)		FIELD_PREP(GENMASK(9, 0), x)
> +#define DSI2_IPI_PIX_PKT_CFG		0x0344
> +#define MAX_PIX_PKT(x)			FIELD_PREP(GENMASK(15, 0), x)
> +
> +#define DSI2_INT_ST_PHY			0x0400
> +#define DSI2_INT_MASK_PHY		0x0404
> +#define DSI2_INT_ST_TO			0x0410
> +#define DSI2_INT_MASK_TO		0x0414
> +#define DSI2_INT_ST_ACK			0x0420
> +#define DSI2_INT_MASK_ACK		0x0424
> +#define DSI2_INT_ST_IPI			0x0430
> +#define DSI2_INT_MASK_IPI		0x0434
> +#define DSI2_INT_ST_FIFO		0x0440
> +#define DSI2_INT_MASK_FIFO		0x0444
> +#define DSI2_INT_ST_PRI			0x0450
> +#define DSI2_INT_MASK_PRI		0x0454
> +#define DSI2_INT_ST_CRI			0x0460
> +#define DSI2_INT_MASK_CRI		0x0464
> +#define DSI2_INT_FORCE_CRI		0x0468
> +#define DSI2_MAX_REGISGER		DSI2_INT_FORCE_CRI
> +
> +#define MODE_STATUS_TIMEOUT_US		10000
> +#define CMD_PKT_STATUS_TIMEOUT_US	20000
> +
> +enum vid_mode_type {
> +	VID_MODE_TYPE_NON_BURST_SYNC_PULSES,
> +	VID_MODE_TYPE_NON_BURST_SYNC_EVENTS,
> +	VID_MODE_TYPE_BURST,
> +};
> +
> +enum mode_ctrl {
> +	IDLE_MODE,
> +	AUTOCALC_MODE,
> +	COMMAND_MODE,
> +	VIDEO_MODE,
> +	DATA_STREAM_MODE,
> +	VIDEO_TEST_MODE,
> +	DATA_STREAM_TEST_MODE,
> +};
> +
> +enum ppi_width {
> +	PPI_WIDTH_8_BITS,
> +	PPI_WIDTH_16_BITS,
> +	PPI_WIDTH_32_BITS,
> +};
> +
> +struct cmd_header {
> +	u8 cmd_type;
> +	u8 delay;
> +	u8 payload_length;
> +};
> +
> +struct dw_mipi_dsi2 {
> +	struct drm_bridge bridge;
> +	struct mipi_dsi_host dsi_host;
> +	struct drm_bridge *panel_bridge;
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct clk *pclk;
> +	struct clk *sys_clk;
> +
> +	unsigned int lane_mbps; /* per lane */
> +	u32 channel;
> +	u32 lanes;
> +	u32 format;
> +	unsigned long mode_flags;
> +
> +	struct drm_display_mode mode;
> +	const struct dw_mipi_dsi2_plat_data *plat_data;
> +};
> +
> +static inline struct dw_mipi_dsi2 *host_to_dsi2(struct mipi_dsi_host *host)
> +{
> +	return container_of(host, struct dw_mipi_dsi2, dsi_host);
> +}
> +
> +static inline struct dw_mipi_dsi2 *bridge_to_dsi2(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct dw_mipi_dsi2, bridge);
> +}
> +
> +static int cri_fifos_wait_avail(struct dw_mipi_dsi2 *dsi2)
> +{
> +	u32 sts, mask;
> +	int ret;
> +
> +	mask = CRI_BUSY | CRT_FIFOS_NOT_EMPTY;
> +	ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_CORE_STATUS, sts,
> +				       !(sts & mask), 0, CMD_PKT_STATUS_TIMEOUT_US);
> +	if (ret < 0) {
> +		dev_err(dsi2->dev, "command interface is busy\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void dw_mipi_dsi2_set_vid_mode(struct dw_mipi_dsi2 *dsi2)
> +{
> +	u32 val = 0, mode;
> +	int ret;
> +
> +	if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HFP)
> +		val |= BLK_HFP_HS_EN;
> +
> +	if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HBP)
> +		val |= BLK_HBP_HS_EN;
> +
> +	if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HSA)
> +		val |= BLK_HSA_HS_EN;
> +
> +	if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
> +		val |= VID_MODE_TYPE_BURST;
> +	else if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
> +		val |= VID_MODE_TYPE_NON_BURST_SYNC_PULSES;
> +	else
> +		val |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS;
> +
> +	regmap_write(dsi2->regmap, DSI2_DSI_VID_TX_CFG, val);
> +
> +	regmap_write(dsi2->regmap, DSI2_MODE_CTRL, VIDEO_MODE);
> +	ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_MODE_STATUS,
> +				       mode, mode & VIDEO_MODE,
> +				       1000, MODE_STATUS_TIMEOUT_US);
> +	if (ret < 0)
> +		dev_err(dsi2->dev, "failed to enter video mode\n");
> +}
> +
> +static void dw_mipi_dsi2_set_data_stream_mode(struct dw_mipi_dsi2 *dsi2)
> +{
> +	u32 mode;
> +	int ret;
> +
> +	regmap_write(dsi2->regmap, DSI2_MODE_CTRL, DATA_STREAM_MODE);
> +	ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_MODE_STATUS,
> +				       mode, mode & DATA_STREAM_MODE,
> +				       1000, MODE_STATUS_TIMEOUT_US);
> +	if (ret < 0)
> +		dev_err(dsi2->dev, "failed to enter data stream mode\n");
> +}
> +
> +static void dw_mipi_dsi2_set_cmd_mode(struct dw_mipi_dsi2 *dsi2)
> +{
> +	u32 mode;
> +	int ret;
> +
> +	regmap_write(dsi2->regmap, DSI2_MODE_CTRL, COMMAND_MODE);
> +	ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_MODE_STATUS,
> +				       mode, mode & COMMAND_MODE,
> +				       1000, MODE_STATUS_TIMEOUT_US);
> +	if (ret < 0)
> +		dev_err(dsi2->dev, "failed to enter data stream mode\n");
> +}
> +
> +static void dw_mipi_dsi2_host_softrst(struct dw_mipi_dsi2 *dsi2)
> +{
> +	regmap_write(dsi2->regmap, DSI2_SOFT_RESET, 0x0);
> +	usleep_range(50, 100);
> +	regmap_write(dsi2->regmap, DSI2_SOFT_RESET,
> +		   SYS_RSTN | PHY_RSTN | IPI_RSTN);
> +}
> +
> +static void dw_mipi_dsi2_phy_clk_mode_cfg(struct dw_mipi_dsi2 *dsi2)
> +{
> +	u32 sys_clk, esc_clk_div;
> +	u32 val = 0;
> +
> +	/*
> +	 * clk_type should be NON_CONTINUOUS_CLK before
> +	 * initial deskew calibration be sent.
> +	 */
> +	val |= NON_CONTINUOUS_CLK;
> +
> +	/* The maximum value of the escape clock frequency is 20MHz */
> +	sys_clk = clk_get_rate(dsi2->sys_clk) / USEC_PER_SEC;
> +	esc_clk_div = DIV_ROUND_UP(sys_clk, 20 * 2);
> +	val |= PHY_LPTX_CLK_DIV(esc_clk_div);
> +
> +	regmap_write(dsi2->regmap, DSI2_PHY_CLK_CFG, val);
> +}
> +
> +static void dw_mipi_dsi2_phy_ratio_cfg(struct dw_mipi_dsi2 *dsi2)
> +{
> +	struct drm_display_mode *mode = &dsi2->mode;
> +	u64 sys_clk = clk_get_rate(dsi2->sys_clk);
> +	u64 pixel_clk, ipi_clk, phy_hsclk;
> +	u64 tmp;
> +
> +	/*
> +	 * in DPHY mode, the phy_hstx_clk is exactly 1/16 the Lane high-speed
> +	 * data rate; In CPHY mode, the phy_hstx_clk is exactly 1/7 the trio
> +	 * high speed symbol rate.
> +	 */
> +	phy_hsclk = DIV_ROUND_CLOSEST_ULL(dsi2->lane_mbps * USEC_PER_SEC, 16);
> +
> +	/* IPI_RATIO_MAN_CFG = PHY_HSTX_CLK / IPI_CLK */
> +	pixel_clk = mode->crtc_clock * MSEC_PER_SEC;
> +	ipi_clk = pixel_clk / 4;
> +
> +	tmp = DIV_ROUND_CLOSEST_ULL(phy_hsclk << 16, ipi_clk);
> +	regmap_write(dsi2->regmap, DSI2_PHY_IPI_RATIO_MAN_CFG,
> +		   PHY_IPI_RATIO(tmp));
> +
> +	/*
> +	 * SYS_RATIO_MAN_CFG = MIPI_DCPHY_HSCLK_Freq / MIPI_DCPHY_HSCLK_Freq
> +	 */
> +	tmp = DIV_ROUND_CLOSEST_ULL(phy_hsclk << 16, sys_clk);
> +	regmap_write(dsi2->regmap, DSI2_PHY_SYS_RATIO_MAN_CFG,
> +		   PHY_SYS_RATIO(tmp));
> +}
> +
> +static void dw_mipi_dsi2_lp2hs_or_hs2lp_cfg(struct dw_mipi_dsi2 *dsi2)
> +{
> +	const struct dw_mipi_dsi2_phy_ops *phy_ops = dsi2->plat_data->phy_ops;
> +	struct dw_mipi_dsi2_phy_timing timing;
> +	int ret;
> +
> +	ret = phy_ops->get_timing(dsi2->plat_data->priv_data,
> +				  dsi2->lane_mbps, &timing);
> +	if (ret)
> +		dev_err(dsi2->dev, "Retrieving phy timings failed\n");
> +
> +	regmap_write(dsi2->regmap, DSI2_PHY_LP2HS_MAN_CFG, PHY_LP2HS_TIME(timing.data_lp2hs));
> +	regmap_write(dsi2->regmap, DSI2_PHY_HS2LP_MAN_CFG, PHY_HS2LP_TIME(timing.data_hs2lp));
> +}
> +
> +static void dw_mipi_dsi2_phy_init(struct dw_mipi_dsi2 *dsi2)
> +{
> +	const struct dw_mipi_dsi2_phy_ops *phy_ops = dsi2->plat_data->phy_ops;
> +	struct dw_mipi_dsi2_phy_iface iface;
> +	u32 val = 0;
> +
> +	phy_ops->get_interface(dsi2->plat_data->priv_data, &iface);
> +
> +	switch (iface.ppi_width) {
> +	case 8:
> +		val |= PPI_WIDTH(PPI_WIDTH_8_BITS);
> +		break;
> +	case 16:
> +		val |= PPI_WIDTH(PPI_WIDTH_16_BITS);
> +		break;
> +	case 32:
> +		val |= PPI_WIDTH(PPI_WIDTH_32_BITS);
> +		break;
> +	default:
> +		/* Caught in probe */
> +		break;
> +	}
> +
> +	val |= PHY_LANES(dsi2->lanes);
> +	val |= PHY_TYPE(DW_MIPI_DSI2_DPHY);
> +	regmap_write(dsi2->regmap, DSI2_PHY_MODE_CFG, val);
> +
> +	dw_mipi_dsi2_phy_clk_mode_cfg(dsi2);
> +	dw_mipi_dsi2_phy_ratio_cfg(dsi2);
> +	dw_mipi_dsi2_lp2hs_or_hs2lp_cfg(dsi2);
> +
> +	/* phy configuration 8 - 10 */
> +}
> +
> +static void dw_mipi_dsi2_tx_option_set(struct dw_mipi_dsi2 *dsi2)
> +{
> +	u32 val;
> +
> +	val = BTA_EN | EOTP_TX_EN;
> +
> +	if (dsi2->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
> +		val &= ~EOTP_TX_EN;
> +
> +	regmap_write(dsi2->regmap, DSI2_DSI_GENERAL_CFG, val);
> +	regmap_write(dsi2->regmap, DSI2_DSI_VCID_CFG, TX_VCID(dsi2->channel));
> +}
> +
> +static void dw_mipi_dsi2_ipi_color_coding_cfg(struct dw_mipi_dsi2 *dsi2)
> +{
> +	u32 val, color_depth;
> +
> +	switch (dsi2->format) {
> +	case MIPI_DSI_FMT_RGB666:
> +	case MIPI_DSI_FMT_RGB666_PACKED:
> +		color_depth = IPI_DEPTH_6_BITS;
> +		break;
> +	case MIPI_DSI_FMT_RGB565:
> +		color_depth = IPI_DEPTH_5_6_5_BITS;
> +		break;
> +	case MIPI_DSI_FMT_RGB888:
> +	default:
> +		color_depth = IPI_DEPTH_8_BITS;
> +		break;
> +	}
> +
> +	val = IPI_DEPTH(color_depth) |
> +	      IPI_FORMAT(IPI_FORMAT_RGB);
> +	regmap_write(dsi2->regmap, DSI2_IPI_COLOR_MAN_CFG, val);
> +}
> +
> +static void dw_mipi_dsi2_vertical_timing_config(struct dw_mipi_dsi2 *dsi2,
> +						const struct drm_display_mode *mode)
> +{
> +	u32 vactive, vsa, vfp, vbp;
> +
> +	vactive = mode->vdisplay;
> +	vsa = mode->vsync_end - mode->vsync_start;
> +	vfp = mode->vsync_start - mode->vdisplay;
> +	vbp = mode->vtotal - mode->vsync_end;
> +
> +	regmap_write(dsi2->regmap, DSI2_IPI_VID_VSA_MAN_CFG, VID_VSA_LINES(vsa));
> +	regmap_write(dsi2->regmap, DSI2_IPI_VID_VBP_MAN_CFG, VID_VBP_LINES(vbp));
> +	regmap_write(dsi2->regmap, DSI2_IPI_VID_VACT_MAN_CFG, VID_VACT_LINES(vactive));
> +	regmap_write(dsi2->regmap, DSI2_IPI_VID_VFP_MAN_CFG, VID_VFP_LINES(vfp));
> +}
> +
> +static void dw_mipi_dsi2_ipi_set(struct dw_mipi_dsi2 *dsi2)
> +{
> +	struct drm_display_mode *mode = &dsi2->mode;
> +	u32 hline, hsa, hbp, hact;
> +	u64 hline_time, hsa_time, hbp_time, hact_time, tmp;
> +	u64 pixel_clk, phy_hs_clk;
> +	u16 val;
> +
> +	val = mode->hdisplay;
> +
> +	regmap_write(dsi2->regmap, DSI2_IPI_PIX_PKT_CFG, MAX_PIX_PKT(val));
> +
> +	dw_mipi_dsi2_ipi_color_coding_cfg(dsi2);
> +
> +	/*
> +	 * if the controller is intended to operate in data stream mode,
> +	 * no more steps are required.
> +	 */
> +	if (!(dsi2->mode_flags & MIPI_DSI_MODE_VIDEO))
> +		return;
> +
> +	hact = mode->hdisplay;
> +	hsa = mode->hsync_end - mode->hsync_start;
> +	hbp = mode->htotal - mode->hsync_end;
> +	hline = mode->htotal;
> +
> +	pixel_clk = mode->crtc_clock * MSEC_PER_SEC;
> +
> +	phy_hs_clk = DIV_ROUND_CLOSEST_ULL(dsi2->lane_mbps * USEC_PER_SEC, 16);
> +
> +	tmp = hsa * phy_hs_clk;
> +	hsa_time = DIV_ROUND_CLOSEST_ULL(tmp << 16, pixel_clk);
> +	regmap_write(dsi2->regmap, DSI2_IPI_VID_HSA_MAN_CFG, VID_HSA_TIME(hsa_time));
> +
> +	tmp = hbp * phy_hs_clk;
> +	hbp_time = DIV_ROUND_CLOSEST_ULL(tmp << 16, pixel_clk);
> +	regmap_write(dsi2->regmap, DSI2_IPI_VID_HBP_MAN_CFG, VID_HBP_TIME(hbp_time));
> +
> +	tmp = hact * phy_hs_clk;
> +	hact_time = DIV_ROUND_CLOSEST_ULL(tmp << 16, pixel_clk);
> +	regmap_write(dsi2->regmap, DSI2_IPI_VID_HACT_MAN_CFG, VID_HACT_TIME(hact_time));
> +
> +	tmp = hline * phy_hs_clk;
> +	hline_time = DIV_ROUND_CLOSEST_ULL(tmp << 16, pixel_clk);
> +	regmap_write(dsi2->regmap, DSI2_IPI_VID_HLINE_MAN_CFG, VID_HLINE_TIME(hline_time));
> +
> +	dw_mipi_dsi2_vertical_timing_config(dsi2, mode);
> +}
> +
> +static void
> +dw_mipi_dsi2_work_mode(struct dw_mipi_dsi2 *dsi2, u32 mode)
> +{
> +	/*
> +	 * select controller work in Manual mode
> +	 * Manual: MANUAL_MODE_EN
> +	 * Automatic: 0
> +	 */
> +	regmap_write(dsi2->regmap, MANUAL_MODE_CFG, mode);
> +}
> +
> +static int dw_mipi_dsi2_host_attach(struct mipi_dsi_host *host,
> +				    struct mipi_dsi_device *device)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = host_to_dsi2(host);
> +	const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
> +	struct drm_bridge *bridge;
> +	int ret;
> +
> +	if (device->lanes > dsi2->plat_data->max_data_lanes) {
> +		dev_err(dsi2->dev, "the number of data lanes(%u) is too many\n",
> +			device->lanes);
> +		return -EINVAL;
> +	}
> +
> +	dsi2->lanes = device->lanes;
> +	dsi2->channel = device->channel;
> +	dsi2->format = device->format;
> +	dsi2->mode_flags = device->mode_flags;
> +
> +	bridge = devm_drm_of_get_bridge(dsi2->dev, dsi2->dev->of_node, 1, 0);
> +	if (IS_ERR(bridge))
> +		return PTR_ERR(bridge);
> +
> +	bridge->pre_enable_prev_first = true;
> +	dsi2->panel_bridge = bridge;
> +
> +	drm_bridge_add(&dsi2->bridge);
> +
> +	if (pdata->host_ops && pdata->host_ops->attach) {
> +		ret = pdata->host_ops->attach(pdata->priv_data, device);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int dw_mipi_dsi2_host_detach(struct mipi_dsi_host *host,
> +				    struct mipi_dsi_device *device)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = host_to_dsi2(host);
> +	const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
> +	int ret;
> +
> +	if (pdata->host_ops && pdata->host_ops->detach) {
> +		ret = pdata->host_ops->detach(pdata->priv_data, device);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	drm_bridge_remove(&dsi2->bridge);
> +
> +	drm_of_panel_bridge_remove(host->dev->of_node, 1, 0);
> +
> +	return 0;
> +}
> +
> +static int dw_mipi_dsi2_gen_pkt_hdr_write(struct dw_mipi_dsi2 *dsi2,
> +					  u32 hdr_val, bool lpm)
> +{
> +	int ret;
> +
> +	regmap_write(dsi2->regmap, DSI2_CRI_TX_HDR, hdr_val | CMD_TX_MODE(lpm));
> +
> +	ret = cri_fifos_wait_avail(dsi2);
> +	if (ret) {
> +		dev_err(dsi2->dev, "failed to write command header\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int dw_mipi_dsi2_write(struct dw_mipi_dsi2 *dsi2,
> +			      const struct mipi_dsi_packet *packet, bool lpm)
> +{
> +	const u8 *tx_buf = packet->payload;
> +	int len = packet->payload_length, pld_data_bytes = sizeof(u32);
> +	__le32 word;
> +
> +	/* Send payload */
> +	while (len) {
> +		if (len < pld_data_bytes) {
> +			word = 0;
> +			memcpy(&word, tx_buf, len);
> +			regmap_write(dsi2->regmap, DSI2_CRI_TX_PLD, le32_to_cpu(word));
> +			len = 0;
> +		} else {
> +			memcpy(&word, tx_buf, pld_data_bytes);
> +			regmap_write(dsi2->regmap, DSI2_CRI_TX_PLD, le32_to_cpu(word));
> +			tx_buf += pld_data_bytes;
> +			len -= pld_data_bytes;
> +		}
> +	}
> +
> +	word = 0;
> +	memcpy(&word, packet->header, sizeof(packet->header));
> +	return dw_mipi_dsi2_gen_pkt_hdr_write(dsi2, le32_to_cpu(word), lpm);
> +}
> +
> +static int dw_mipi_dsi2_read(struct dw_mipi_dsi2 *dsi2,
> +			     const struct mipi_dsi_msg *msg)
> +{
> +	u8 *payload = msg->rx_buf;
> +	int i, j, ret, len = msg->rx_len;
> +	u8 data_type;
> +	u16 wc;
> +	u32 val;
> +
> +	ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_CORE_STATUS,
> +				       val, val & CRI_RD_DATA_AVAIL,
> +				       100, CMD_PKT_STATUS_TIMEOUT_US);
> +	if (ret) {
> +		dev_err(dsi2->dev, "CRI has no available read data\n");
> +		return ret;
> +	}
> +
> +	regmap_read(dsi2->regmap, DSI2_CRI_RX_HDR, &val);
> +	data_type = val & 0x3f;
> +
> +	if (mipi_dsi_packet_format_is_short(data_type)) {
> +		for (i = 0; i < len && i < 2; i++)
> +			payload[i] = (val >> (8 * (i + 1))) & 0xff;
> +
> +		return 0;
> +	}
> +
> +	wc = (val >> 8) & 0xffff;
> +	/* Receive payload */
> +	for (i = 0; i < len && i < wc; i += 4) {
> +		regmap_read(dsi2->regmap, DSI2_CRI_RX_PLD, &val);
> +		for (j = 0; j < 4 && j + i < len && j + i < wc; j++)
> +			payload[i + j] = val >> (8 * j);
> +	}
> +
> +	return 0;
> +}
> +
> +static ssize_t dw_mipi_dsi2_host_transfer(struct mipi_dsi_host *host,
> +					  const struct mipi_dsi_msg *msg)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = host_to_dsi2(host);
> +	bool lpm = msg->flags & MIPI_DSI_MSG_USE_LPM;
> +	struct mipi_dsi_packet packet;
> +	int ret, nb_bytes;
> +
> +	regmap_update_bits(dsi2->regmap, DSI2_DSI_VID_TX_CFG,
> +			   LPDT_DISPLAY_CMD_EN,
> +			   lpm ? LPDT_DISPLAY_CMD_EN : 0);
> +
> +	/* create a packet to the DSI protocol */
> +	ret = mipi_dsi_create_packet(&packet, msg);
> +	if (ret) {
> +		dev_err(dsi2->dev, "failed to create packet: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = cri_fifos_wait_avail(dsi2);
> +	if (ret)
> +		return ret;
> +
> +	ret = dw_mipi_dsi2_write(dsi2, &packet, lpm);
> +	if (ret)
> +		return ret;
> +
> +	if (msg->rx_buf && msg->rx_len) {
> +		ret = dw_mipi_dsi2_read(dsi2, msg);
> +		if (ret < 0)
> +			return ret;
> +		nb_bytes = msg->rx_len;
> +	} else {
> +		nb_bytes = packet.size;
> +	}
> +
> +	return nb_bytes;
> +}
> +
> +static const struct mipi_dsi_host_ops dw_mipi_dsi2_host_ops = {
> +	.attach = dw_mipi_dsi2_host_attach,
> +	.detach = dw_mipi_dsi2_host_detach,
> +	.transfer = dw_mipi_dsi2_host_transfer,
> +};
> +
> +static u32 *
> +dw_mipi_dsi2_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
> +					      struct drm_bridge_state *bridge_state,
> +					      struct drm_crtc_state *crtc_state,
> +					      struct drm_connector_state *conn_state,
> +					      u32 output_fmt,
> +					      unsigned int *num_input_fmts)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
> +	const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
> +	u32 *input_fmts;
> +
> +	if (pdata->get_input_bus_fmts)
> +		return pdata->get_input_bus_fmts(pdata->priv_data,
> +						 bridge, bridge_state,
> +						 crtc_state, conn_state,
> +						 output_fmt, num_input_fmts);
> +
> +	/* Fall back to MEDIA_BUS_FMT_FIXED as the only input format. */
> +	input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
> +	if (!input_fmts)
> +		return NULL;
> +	input_fmts[0] = MEDIA_BUS_FMT_FIXED;
> +	*num_input_fmts = 1;
> +
> +	return input_fmts;
> +}
> +
> +static int dw_mipi_dsi2_bridge_atomic_check(struct drm_bridge *bridge,
> +					    struct drm_bridge_state *bridge_state,
> +					    struct drm_crtc_state *crtc_state,
> +					    struct drm_connector_state *conn_state)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
> +	const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
> +	bool ret;
> +
> +	bridge_state->input_bus_cfg.flags =
> +		DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
> +
> +	if (pdata->mode_fixup) {
> +		ret = pdata->mode_fixup(pdata->priv_data, &crtc_state->mode,
> +					&crtc_state->adjusted_mode);
> +		if (!ret) {
> +			DRM_DEBUG_DRIVER("failed to fixup mode " DRM_MODE_FMT "\n",
> +					 DRM_MODE_ARG(&crtc_state->mode));
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void dw_mipi_dsi2_bridge_post_atomic_disable(struct drm_bridge *bridge,
> +						    struct drm_bridge_state *old_bridge_state)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
> +	const struct dw_mipi_dsi2_phy_ops *phy_ops = dsi2->plat_data->phy_ops;
> +
> +	regmap_write(dsi2->regmap, DSI2_IPI_PIX_PKT_CFG, 0);
> +
> +	/*
> +	 * Switch to command mode before panel-bridge post_disable &
> +	 * panel unprepare.
> +	 * Note: panel-bridge disable & panel disable has been called
> +	 * before by the drm framework.
> +	 */
> +	dw_mipi_dsi2_set_cmd_mode(dsi2);
> +
> +	regmap_write(dsi2->regmap, DSI2_PWR_UP, RESET);
> +
> +	if (phy_ops->power_off)
> +		phy_ops->power_off(dsi2->plat_data->priv_data);
> +
> +	clk_disable_unprepare(dsi2->pclk);
> +	pm_runtime_put(dsi2->dev);
> +}
> +
> +static unsigned int dw_mipi_dsi2_get_lanes(struct dw_mipi_dsi2 *dsi2)
> +{
> +	/* single-dsi, so no other instance to consider */
> +	return dsi2->lanes;
> +}
> +
> +static void dw_mipi_dsi2_mode_set(struct dw_mipi_dsi2 *dsi2,
> +				  const struct drm_display_mode *adjusted_mode)
> +{
> +	const struct dw_mipi_dsi2_phy_ops *phy_ops = dsi2->plat_data->phy_ops;
> +	void *priv_data = dsi2->plat_data->priv_data;
> +	u32 lanes = dw_mipi_dsi2_get_lanes(dsi2);
> +	int ret;
> +
> +	clk_prepare_enable(dsi2->pclk);
> +
> +	ret = phy_ops->get_lane_mbps(priv_data, adjusted_mode, dsi2->mode_flags,
> +				     lanes, dsi2->format, &dsi2->lane_mbps);
> +	if (ret)
> +		DRM_DEBUG_DRIVER("Phy get_lane_mbps() failed\n");
> +
> +	pm_runtime_get_sync(dsi2->dev);
> +
> +	dw_mipi_dsi2_host_softrst(dsi2);
> +	regmap_write(dsi2->regmap, DSI2_PWR_UP, RESET);
> +
> +	dw_mipi_dsi2_work_mode(dsi2, MANUAL_MODE_EN);
> +	dw_mipi_dsi2_phy_init(dsi2);
> +
> +	if (phy_ops->power_on)
> +		phy_ops->power_on(dsi2->plat_data->priv_data);
> +
> +	dw_mipi_dsi2_tx_option_set(dsi2);
> +
> +	/*
> +	 * initial deskew calibration is send after phy_power_on,
> +	 * then we can configure clk_type.
> +	 */
> +
> +	regmap_update_bits(dsi2->regmap, DSI2_PHY_CLK_CFG, CLK_TYPE_MASK,
> +			   dsi2->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS ? NON_CONTINUOUS_CLK :
> +									      CONTINUOUS_CLK);
> +
> +	regmap_write(dsi2->regmap, DSI2_PWR_UP, POWER_UP);
> +	dw_mipi_dsi2_set_cmd_mode(dsi2);
> +
> +	dw_mipi_dsi2_ipi_set(dsi2);
> +}
> +
> +static void dw_mipi_dsi2_bridge_atomic_pre_enable(struct drm_bridge *bridge,
> +						  struct drm_bridge_state *old_bridge_state)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
> +
> +	/* Power up the dsi ctl into a command mode */
> +	dw_mipi_dsi2_mode_set(dsi2, &dsi2->mode);
> +}
> +
> +static void dw_mipi_dsi2_bridge_mode_set(struct drm_bridge *bridge,
> +					 const struct drm_display_mode *mode,
> +					 const struct drm_display_mode *adjusted_mode)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
> +
> +	/* Store the display mode for later use in pre_enable callback */
> +	drm_mode_copy(&dsi2->mode, adjusted_mode);
> +}
> +
> +static void dw_mipi_dsi2_bridge_atomic_enable(struct drm_bridge *bridge,
> +					      struct drm_bridge_state *old_bridge_state)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
> +
> +	/* Switch to video mode for panel-bridge enable & panel enable */
> +	if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO)
> +		dw_mipi_dsi2_set_vid_mode(dsi2);
> +	else
> +		dw_mipi_dsi2_set_data_stream_mode(dsi2);
> +}
> +
> +static enum drm_mode_status
> +dw_mipi_dsi2_bridge_mode_valid(struct drm_bridge *bridge,
> +			       const struct drm_display_info *info,
> +			       const struct drm_display_mode *mode)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
> +	const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
> +	enum drm_mode_status mode_status = MODE_OK;
> +
> +	if (pdata->mode_valid)
> +		mode_status = pdata->mode_valid(pdata->priv_data, mode,
> +						dsi2->mode_flags,
> +						dw_mipi_dsi2_get_lanes(dsi2),
> +						dsi2->format);
> +
> +	return mode_status;
> +}
> +
> +static int dw_mipi_dsi2_bridge_attach(struct drm_bridge *bridge,
> +				      enum drm_bridge_attach_flags flags)
> +{
> +	struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
> +
> +	/* Set the encoder type as caller does not know it */
> +	bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
> +
> +	/* Attach the panel-bridge to the dsi bridge */
> +	return drm_bridge_attach(bridge->encoder, dsi2->panel_bridge, bridge,
> +				 flags);
> +}
> +
> +static const struct drm_bridge_funcs dw_mipi_dsi2_bridge_funcs = {
> +	.atomic_duplicate_state	= drm_atomic_helper_bridge_duplicate_state,
> +	.atomic_destroy_state	= drm_atomic_helper_bridge_destroy_state,
> +	.atomic_get_input_bus_fmts = dw_mipi_dsi2_bridge_atomic_get_input_bus_fmts,
> +	.atomic_check		= dw_mipi_dsi2_bridge_atomic_check,
> +	.atomic_reset		= drm_atomic_helper_bridge_reset,
> +	.atomic_pre_enable	= dw_mipi_dsi2_bridge_atomic_pre_enable,
> +	.atomic_enable		= dw_mipi_dsi2_bridge_atomic_enable,
> +	.atomic_post_disable	= dw_mipi_dsi2_bridge_post_atomic_disable,
> +	.mode_set		= dw_mipi_dsi2_bridge_mode_set,
> +	.mode_valid		= dw_mipi_dsi2_bridge_mode_valid,
> +	.attach			= dw_mipi_dsi2_bridge_attach,
> +};
> +
> +static const struct regmap_config dw_mipi_dsi2_regmap_config = {
> +	.name = "dsi2-host",
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 4,
> +	.fast_io = true,
> +};
> +
> +static struct dw_mipi_dsi2 *
> +__dw_mipi_dsi2_probe(struct platform_device *pdev,
> +		     const struct dw_mipi_dsi2_plat_data *plat_data)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct reset_control *apb_rst;
> +	struct dw_mipi_dsi2 *dsi2;
> +	int ret;
> +
> +	dsi2 = devm_kzalloc(dev, sizeof(*dsi2), GFP_KERNEL);
> +	if (!dsi2)
> +		return ERR_PTR(-ENOMEM);
> +
> +	dsi2->dev = dev;
> +	dsi2->plat_data = plat_data;
> +
> +	if (!plat_data->phy_ops->init || !plat_data->phy_ops->get_lane_mbps ||
> +	    !plat_data->phy_ops->get_timing)
> +		return dev_err_ptr_probe(dev, -ENODEV, "Phy not properly configured\n");
> +
> +	if (!plat_data->regmap) {
> +		void __iomem *base = devm_platform_ioremap_resource(pdev, 0);
> +
> +		if (IS_ERR(base))
> +			return dev_err_cast_probe(dev, base, "failed to registers\n");
> +
> +		dsi2->regmap = devm_regmap_init_mmio(dev, base,
> +						     &dw_mipi_dsi2_regmap_config);
> +		if (IS_ERR(dsi2->regmap))
> +			return dev_err_cast_probe(dev, dsi2->regmap, "failed to init regmap\n");
> +	} else {
> +		dsi2->regmap = plat_data->regmap;
> +	}
> +
> +	dsi2->pclk = devm_clk_get(dev, "pclk");
> +	if (IS_ERR(dsi2->pclk))
> +		return dev_err_cast_probe(dev, dsi2->pclk, "Unable to get pclk\n");
> +
> +	dsi2->sys_clk = devm_clk_get(dev, "sys");
> +	if (IS_ERR(dsi2->sys_clk))
> +		return dev_err_cast_probe(dev, dsi2->sys_clk, "Unable to get sys_clk\n");
> +
> +	/*
> +	 * Note that the reset was not defined in the initial device tree, so
> +	 * we have to be prepared for it not being found.
> +	 */
> +	apb_rst = devm_reset_control_get_optional_exclusive(dev, "apb");
> +	if (IS_ERR(apb_rst))
> +		return dev_err_cast_probe(dev, apb_rst, "Unable to get reset control\n");
> +
> +	if (apb_rst) {
> +		ret = clk_prepare_enable(dsi2->pclk);
> +		if (ret) {
> +			dev_err(dev, "%s: Failed to enable pclk\n", __func__);
> +			return ERR_PTR(ret);
> +		}
> +
> +		reset_control_assert(apb_rst);
> +		usleep_range(10, 20);
> +		reset_control_deassert(apb_rst);
> +
> +		clk_disable_unprepare(dsi2->pclk);
> +	}
> +
> +	pm_runtime_enable(dev);
> +
> +	dsi2->dsi_host.ops = &dw_mipi_dsi2_host_ops;
> +	dsi2->dsi_host.dev = dev;
> +	ret = mipi_dsi_host_register(&dsi2->dsi_host);
> +	if (ret) {
> +		dev_err(dev, "Failed to register MIPI host: %d\n", ret);
> +		pm_runtime_disable(dev);
> +		return ERR_PTR(ret);
> +	}
> +
> +	dsi2->bridge.driver_private = dsi2;
> +	dsi2->bridge.funcs = &dw_mipi_dsi2_bridge_funcs;
> +	dsi2->bridge.of_node = pdev->dev.of_node;
> +
> +	return dsi2;
> +}
> +
> +static void __dw_mipi_dsi2_remove(struct dw_mipi_dsi2 *dsi2)
> +{
> +	mipi_dsi_host_unregister(&dsi2->dsi_host);
> +
> +	pm_runtime_disable(dsi2->dev);
> +}
> +
> +/*
> + * Probe/remove API, used to create the bridge instance.
> + */
> +struct dw_mipi_dsi2 *
> +dw_mipi_dsi2_probe(struct platform_device *pdev,
> +		   const struct dw_mipi_dsi2_plat_data *plat_data)
> +{
> +	return __dw_mipi_dsi2_probe(pdev, plat_data);
> +}
> +EXPORT_SYMBOL_GPL(dw_mipi_dsi2_probe);
> +
> +void dw_mipi_dsi2_remove(struct dw_mipi_dsi2 *dsi2)
> +{
> +	__dw_mipi_dsi2_remove(dsi2);
> +}
> +EXPORT_SYMBOL_GPL(dw_mipi_dsi2_remove);
> +
> +/*
> + * Bind/unbind API, used from platforms based on the component framework
> + * to attach the bridge to an encoder.
> + */
> +int dw_mipi_dsi2_bind(struct dw_mipi_dsi2 *dsi2, struct drm_encoder *encoder)
> +{
> +	return drm_bridge_attach(encoder, &dsi2->bridge, NULL, 0);
> +}
> +EXPORT_SYMBOL_GPL(dw_mipi_dsi2_bind);
> +
> +void dw_mipi_dsi2_unbind(struct dw_mipi_dsi2 *dsi2)
> +{
> +}
> +EXPORT_SYMBOL_GPL(dw_mipi_dsi2_unbind);
> +
> +MODULE_AUTHOR("Guochun Huang <hero.huang at rock-chips.com>");
> +MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner at cherry.de>");
> +MODULE_DESCRIPTION("DW MIPI DSI2 host controller driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:dw-mipi-dsi2");
> diff --git a/include/drm/bridge/dw_mipi_dsi2.h b/include/drm/bridge/dw_mipi_dsi2.h
> new file mode 100644
> index 000000000000..c18c49379247
> --- /dev/null
> +++ b/include/drm/bridge/dw_mipi_dsi2.h
> @@ -0,0 +1,95 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024, Fuzhou Rockchip Electronics Co., Ltd
> + *
> + * Authors: Guochun Huang <hero.huang at rock-chips.com>
> + *          Heiko Stuebner <heiko.stuebner at cherry.de>
> + */
> +
> +#ifndef __DW_MIPI_DSI2__
> +#define __DW_MIPI_DSI2__
> +
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_modes.h>
> +
> +struct drm_display_mode;
> +struct drm_encoder;
> +struct dw_mipi_dsi2;
> +struct mipi_dsi_device;
> +struct platform_device;
> +
> +enum dw_mipi_dsi2_phy_type {
> +	DW_MIPI_DSI2_DPHY,
> +	DW_MIPI_DSI2_CPHY,
> +};
> +
> +struct dw_mipi_dsi2_phy_iface {
> +	int ppi_width;
> +	enum dw_mipi_dsi2_phy_type phy_type;
> +};
> +
> +struct dw_mipi_dsi2_phy_timing {
> +	u32 data_hs2lp;
> +	u32 data_lp2hs;
> +};
> +
> +struct dw_mipi_dsi2_phy_ops {
> +	int (*init)(void *priv_data);
> +	void (*power_on)(void *priv_data);
> +	void (*power_off)(void *priv_data);
> +	void (*get_interface)(void *priv_data, struct dw_mipi_dsi2_phy_iface *iface);
> +	int (*get_lane_mbps)(void *priv_data,
> +			     const struct drm_display_mode *mode,
> +			     unsigned long mode_flags, u32 lanes, u32 format,
> +			     unsigned int *lane_mbps);
> +	int (*get_timing)(void *priv_data, unsigned int lane_mbps,
> +			  struct dw_mipi_dsi2_phy_timing *timing);
> +	int (*get_esc_clk_rate)(void *priv_data, unsigned int *esc_clk_rate);
> +};
> +
> +struct dw_mipi_dsi2_host_ops {
> +	int (*attach)(void *priv_data,
> +		      struct mipi_dsi_device *dsi);
> +	int (*detach)(void *priv_data,
> +		      struct mipi_dsi_device *dsi);
> +};
> +
> +struct dw_mipi_dsi2_plat_data {
> +	struct regmap *regmap;
> +	unsigned int max_data_lanes;
> +
> +	enum drm_mode_status (*mode_valid)(void *priv_data,
> +					   const struct drm_display_mode *mode,
> +					   unsigned long mode_flags,
> +					   u32 lanes, u32 format);
> +
> +	bool (*mode_fixup)(void *priv_data, const struct drm_display_mode *mode,
> +			   struct drm_display_mode *adjusted_mode);
> +
> +	u32 *(*get_input_bus_fmts)(void *priv_data,
> +				   struct drm_bridge *bridge,
> +				   struct drm_bridge_state *bridge_state,
> +				   struct drm_crtc_state *crtc_state,
> +				   struct drm_connector_state *conn_state,
> +				   u32 output_fmt,
> +				   unsigned int *num_input_fmts);
> +
> +	const struct dw_mipi_dsi2_phy_ops *phy_ops;
> +	const struct dw_mipi_dsi2_host_ops *host_ops;
> +
> +	void *priv_data;
> +};
> +
> +struct dw_mipi_dsi2 *dw_mipi_dsi2_probe(struct platform_device *pdev,
> +					const struct dw_mipi_dsi2_plat_data *plat_data);
> +void dw_mipi_dsi2_remove(struct dw_mipi_dsi2 *dsi2);
> +int dw_mipi_dsi2_bind(struct dw_mipi_dsi2 *dsi2, struct drm_encoder *encoder);
> +void dw_mipi_dsi2_unbind(struct dw_mipi_dsi2 *dsi2);
> +
> +#endif /* __DW_MIPI_DSI2__ */

Looks very good, thanks for switching to regmap & field macros :-)

Reviewed-by: Neil Armstrong <neil.armstrong at linaro.org>


More information about the dri-devel mailing list