[PATCH 18/26] video: bridge: Add Samsung DSIM bridge

Dario Binacchi
2024-09-13T10:00:07Z

From: Michael Trimarchi <michael@amarulasolutions.com>

Samsung MIPI DSIM controller is common DSI IP that can be used in various
SoCs like Exynos, i.MX8M Mini/Nano.

In order to access this DSI controller between various platform SoCs,
the ideal way to incorporate this in the as a bridge that can support
all different platform.

Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
Signed-off-by: Dario Binacchi <dario.binacchi@amarulasolutions.com>
---

drivers/video/bridge/Kconfig | 13 +
drivers/video/bridge/Makefile | 1 +
drivers/video/bridge/samsung-dsi-host.c | 1567 +++++++++++++++++++++++
drivers/video/bridge/samsung-dsim.c | 148 +++
drivers/video/bridge/samsung-dsim.h | 20 +
5 files changed, 1749 insertions(+)
create mode 100644 drivers/video/bridge/samsung-dsi-host.c
create mode 100644 drivers/video/bridge/samsung-dsim.c
create mode 100644 drivers/video/bridge/samsung-dsim.h

diff --git a/drivers/video/bridge/Kconfig b/drivers/video/bridge/Kconfig
index ab9172737201..5cb1e7af9935 100644
--- a/drivers/video/bridge/Kconfig
+++ b/drivers/video/bridge/Kconfig
@@ -44,6 +44,19 @@ config VIDEO_BRIDGE_ANALOGIX_ANX6345
The Analogix ANX6345 is RGB-to-DP converter. It enables an eDP LCD
panel to be connected to an parallel LCD interface.

+config VIDEO_BRIDGE_SAMSUNG_DSIM
+ bool "Enable IMX SEC DSI video support"
+ select MIPI_DPHY_HELPERS
+ select VIDEO_BRIDGE
+ select VIDEO_LINK
+ select VIDEO_MIPI_DSI
+ help
+ Enables the common driver code for the Samsung
+ MIPI DSI block found in SoCs from various vendors.
+ As this does not provide any functionality by itself (but
+ rather requires a SoC-specific glue driver to call it), it
+ can not be enabled from the configuration menu.
+
config VIDEO_BRIDGE_SOLOMON_SSD2825
bool "Solomon SSD2825 bridge driver"
depends on PANEL && DM_GPIO
diff --git a/drivers/video/bridge/Makefile b/drivers/video/bridge/Makefile
index 58697e3cbe90..8f49013299ae 100644
--- a/drivers/video/bridge/Makefile
+++ b/drivers/video/bridge/Makefile
@@ -8,5 +8,6 @@ obj-$(CONFIG_VIDEO_BRIDGE_PARADE_DP501) += dp501.o
obj-$(CONFIG_VIDEO_BRIDGE_PARADE_PS862X) += ps862x.o
obj-$(CONFIG_VIDEO_BRIDGE_NXP_PTN3460) += ptn3460.o
obj-$(CONFIG_VIDEO_BRIDGE_ANALOGIX_ANX6345) += anx6345.o
+obj-$(CONFIG_VIDEO_BRIDGE_SAMSUNG_DSIM) += samsung-dsim.o samsung-dsi-host.o
obj-$(CONFIG_VIDEO_BRIDGE_SOLOMON_SSD2825) += ssd2825.o
obj-$(CONFIG_VIDEO_BRIDGE_TOSHIBA_TC358768) += tc358768.o
diff --git a/drivers/video/bridge/samsung-dsi-host.c b/drivers/video/bridge/samsung-dsi-host.c
new file mode 100644
index 000000000000..dd3e33c4edc7
--- /dev/null
+++ b/drivers/video/bridge/samsung-dsi-host.c
@@ -0,0 +1,1567 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 Amarula Solutions
+ * Copyright 2018 NXP
+ *
+ */
+
+#define LOG_CATEGORY UCLASS_DSI_HOST
+
+#include <dm.h>
+#include <clk.h>
+#include <dm/device_compat.h>
+#include <asm/io.h>
+#include <log.h>
+#include <phy-mipi-dphy.h>
+#include <linux/err.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/log2.h>
+#include <linux/math64.h>
+#include <asm/unaligned.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+#include <div64.h>
+#include <video_bridge.h>
+#include <panel.h>
+#include <dsi_host.h>
+#include <asm/arch/gpio.h>
+#include <dm/device-internal.h>
+#include "samsung-dsim.h"
+
+#define MIPI_FIFO_TIMEOUT 250000 /* 250ms */
+
+#define DRIVER_NAME "samsung_dsi"
+
+/* reg bit manipulation */
+#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s))
+#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s))
+#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s))
+
+#define RGB_STATUS_CMDMODE_INSEL BIT(31)
+#define RGB_STATUS_GET_RGBSTATE(x) REG_GET(x, 12, 0)
+
+#define CLKCTRL_DPHY_SEL_1P5G (0x0 << 29)
+#define CLKCTRL_PLLBYPASS BIT(29)
+#define CLKCTRL_BYTECLKSRC_DPHY_PLL REG_PUT(0, 26, 25)
+#define CLKCTRL_SET_LANEESCCLKEN(x) REG_PUT(x, 23, 19)
+#define CLKCTRL_SET_ESCPRESCALER(x) REG_PUT(x, 15, 0)
+
+#define ESCMODE_SET_STOPSTATE_CNT(X) REG_PUT(x, 31, 21)
+#define ESCMODE_FORCESTOPSTATE BIT(20)
+#define ESCMODE_FORCEBTA BIT(16)
+#define ESCMODE_CMDLPDT BIT(7)
+#define ESCMODE_TXLPDT BIT(6)
+#define ESCMODE_TXTRIGGERRST BIT(5)
+
+#define MVPORCH_SET_CMDALLOW(x) REG_PUT(x, 31, 28)
+#define MVPORCH_SET_STABLEVFP(x) REG_PUT(x, 26, 16)
+#define MVPORCH_SET_MAINVBP(x) REG_PUT(x, 10, 0)
+
+#define MHPORCH_SET_MAINHFP(x) REG_PUT(x, 31, 16)
+#define MHPORCH_SET_MAINHBP(x) REG_PUT(x, 15, 0)
+
+#define MSYNC_SET_MAINVSA(x) REG_PUT(x, 31, 22)
+#define MSYNC_SET_MAINHSA(x) REG_PUT(x, 15, 0)
+
+#define INTSRC_PLLSTABLE BIT(31)
+#define INTSRC_SWRSTRELEASE BIT(30)
+#define INTSRC_SFRPLFIFOEMPTY BIT(29)
+#define INTSRC_SFRPHFIFOEMPTY BIT(28)
+#define INTSRC_FRAMEDONE BIT(24)
+#define INTSRC_LPDRTOUT BIT(21)
+#define INTSRC_TATOUT BIT(20)
+#define INTSRC_RXDATDONE BIT(18)
+#define INTSRC_MASK (INTSRC_PLLSTABLE | \
+ INTSRC_SWRSTRELEASE | \
+ INTSRC_SFRPLFIFOEMPTY | \
+ INTSRC_SFRPHFIFOEMPTY | \
+ INTSRC_FRAMEDONE | \
+ INTSRC_LPDRTOUT | \
+ INTSRC_TATOUT | \
+ INTSRC_RXDATDONE)
+
+#define INTMSK_MSKPLLSTABLE BIT(31)
+#define INTMSK_MSKSWRELEASE BIT(30)
+#define INTMSK_MSKSFRPLFIFOEMPTY BIT(29)
+#define INTMSK_MSKSFRPHFIFOEMPTY BIT(28)
+#define INTMSK_MSKFRAMEDONE BIT(24)
+#define INTMSK_MSKLPDRTOUT BIT(21)
+#define INTMSK_MSKTATOUT BIT(20)
+#define INTMSK_MSKRXDATDONE BIT(18)
+
+#define PKTHDR_SET_DATA1(x) REG_PUT(x, 23, 16)
+#define PKTHDR_GET_DATA1(x) REG_GET(x, 23, 16)
+#define PKTHDR_SET_DATA0(x) REG_PUT(x, 15, 8)
+#define PKTHDR_GET_DATA0(x) REG_GET(x, 15, 8)
+#define PKTHDR_GET_WC(x) REG_GET(x, 23, 8)
+#define PKTHDR_SET_DI(x) REG_PUT(x, 7, 0)
+#define PKTHDR_GET_DI(x) REG_GET(x, 7, 0)
+#define PKTHDR_SET_DT(x) REG_PUT(x, 5, 0)
+#define PKTHDR_GET_DT(x) REG_GET(x, 5, 0)
+#define PKTHDR_SET_VC(x) REG_PUT(x, 7, 6)
+#define PKTHDR_GET_VC(x) REG_GET(x, 7, 6)
+
+#define FIFOCTRL_FULLRX BIT(25)
+#define FIFOCTRL_EMPTYRX BIT(24)
+#define FIFOCTRL_FULLHSFR BIT(23)
+#define FIFOCTRL_EMPTYHSFR BIT(22)
+#define FIFOCTRL_FULLLSFR BIT(21)
+#define FIFOCTRL_EMPTYLSFR BIT(20)
+#define FIFOCTRL_FULLHMAIN BIT(11)
+#define FIFOCTRL_EMPTYHMAIN BIT(10)
+#define FIFOCTRL_FULLLMAIN BIT(9)
+#define FIFOCTRL_EMPTYLMAIN BIT(8)
+#define FIFOCTRL_NINITRX BIT(4)
+#define FIFOCTRL_NINITSFR BIT(3)
+#define FIFOCTRL_NINITI80 BIT(2)
+#define FIFOCTRL_NINITSUB BIT(1)
+#define FIFOCTRL_NINITMAIN BIT(0)
+
+#define PLLCTRL_DPDNSWAP_CLK BIT(25)
+#define PLLCTRL_DPDNSWAP_DAT BIT(24)
+#define PLLCTRL_PLLEN BIT(23)
+#define PLLCTRL_SET_PMS(x) REG_PUT(x, 19, 1)
+#define PLLCTRL_SET_P(x) REG_PUT(x, 18, 13)
+#define PLLCTRL_SET_M(x) REG_PUT(x, 12, 3)
+#define PLLCTRL_SET_S(x) REG_PUT(x, 2, 0)
+
+#define MAX_MAIN_HRESOL 2047
+#define MAX_MAIN_VRESOL 2047
+#define MAX_SUB_HRESOL 1024
+#define MAX_SUB_VRESOL 1024
+
+/* in KHZ */
+#define MAX_ESC_CLK_FREQ 20000
+
+/* dsim all irqs index */
+#define PLLSTABLE 1
+#define SWRSTRELEASE 2
+#define SFRPLFIFOEMPTY 3
+#define SFRPHFIFOEMPTY 4
+#define SYNCOVERRIDE 5
+#define BUSTURNOVER 6
+#define FRAMEDONE 7
+#define LPDRTOUT 8
+#define TATOUT 9
+#define RXDATDONE 10
+#define RXTE 11
+#define RXACK 12
+#define ERRRXECC 13
+#define ERRRXCRC 14
+#define ERRESC3 15
+#define ERRESC2 16
+#define ERRESC1 17
+#define ERRESC0 18
+#define ERRSYNC3 19
+#define ERRSYNC2 20
+#define ERRSYNC1 21
+#define ERRSYNC0 22
+#define ERRCONTROL3 23
+#define ERRCONTROL2 24
+#define ERRCONTROL1 25
+#define ERRCONTROL0 26
+
+#define PS_TO_CYCLE(ps, hz) DIV64_U64_ROUND_CLOSEST(((ps) * (hz)), 1000000000000ULL)
+
+#define MIPI_HFP_PKT_OVERHEAD 6
+#define MIPI_HBP_PKT_OVERHEAD 6
+#define MIPI_HSA_PKT_OVERHEAD 6
+
+/* DSIM_STATUS */
+#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0)
+#define DSIM_STOP_STATE_CLK BIT(8)
+#define DSIM_TX_READY_HS_CLK BIT(10)
+#define DSIM_PLL_STABLE BIT(31)
+
+/* DSIM_SWRST */
+#define DSIM_FUNCRST BIT(16)
+#define DSIM_SWRST BIT(0)
+
+/* DSIM_TIMEOUT */
+#define DSIM_LPDR_TIMEOUT(x) ((x) << 0)
+#define DSIM_BTA_TIMEOUT(x) ((x) << 16)
+
+/* DSIM_CLKCTRL */
+#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0)
+#define DSIM_ESC_PRESCALER_MASK (0xffff << 0)
+#define DSIM_LANE_ESC_CLK_EN_CLK BIT(19)
+#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20)
+#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20)
+#define DSIM_BYTE_CLKEN BIT(24)
+#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25)
+#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25)
+#define DSIM_PLL_BYPASS BIT(27)
+#define DSIM_ESC_CLKEN BIT(28)
+#define DSIM_TX_REQUEST_HSCLK BIT(31)
+
+/* DSIM_CONFIG */
+#define DSIM_LANE_EN_CLK BIT(0)
+#define DSIM_LANE_EN(x) (((x) & 0xf) << 1)
+#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5)
+#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8)
+#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12)
+#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12)
+#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12)
+#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12)
+#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12)
+#define DSIM_SUB_VC(x) (((x) & 0x3) << 16)
+#define DSIM_MAIN_VC(x) (((x) & 0x3) << 18)
+#define DSIM_HSA_DISABLE_MODE BIT(20)
+#define DSIM_HBP_DISABLE_MODE BIT(21)
+#define DSIM_HFP_DISABLE_MODE BIT(22)
+/*
+ * The i.MX 8M Mini Applications Processor Reference Manual,
+ * Rev. 3, 11/2020 Page 4091
+ * The i.MX 8M Nano Applications Processor Reference Manual,
+ * Rev. 2, 07/2022 Page 3058
+ * The i.MX 8M Plus Applications Processor Reference Manual,
+ * Rev. 1, 06/2021 Page 5436
+ * all claims this bit is 'HseDisableMode' with the definition
+ * 0 = Disables transfer
+ * 1 = Enables transfer
+ *
+ * This clearly states that HSE is not a disabled bit.
+ *
+ * The naming convention follows as per the manual and the
+ * driver logic is based on the MIPI_DSI_MODE_VIDEO_HSE flag.
+ */
+#define DSIM_HSE_DISABLE_MODE BIT(23)
+#define DSIM_AUTO_MODE BIT(24)
+#define DSIM_VIDEO_MODE BIT(25)
+#define DSIM_BURST_MODE BIT(26)
+#define DSIM_SYNC_INFORM BIT(27)
+#define DSIM_EOT_DISABLE BIT(28)
+#define DSIM_MFLUSH_VS BIT(29)
+/* This flag is valid only for exynos3250/3472/5260/5430 */
+#define DSIM_CLKLANE_STOP BIT(30)
+#define DSIM_NON_CONTINUOUS_CLKLANE BIT(31)
+
+/* DSIM_ESCMODE */
+#define DSIM_TX_TRIGGER_RST BIT(4)
+#define DSIM_TX_LPDT_LP BIT(6)
+#define DSIM_CMD_LPDT_LP BIT(7)
+#define DSIM_FORCE_BTA BIT(16)
+#define DSIM_FORCE_STOP_STATE BIT(20)
+#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21)
+#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21)
+
+/* DSIM_MDRESOL */
+#define DSIM_MAIN_STAND_BY BIT(31)
+#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16)
+#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0)
+
+/* DSIM_MVPORCH */
+#define DSIM_CMD_ALLOW(x) ((x) << 28)
+#define DSIM_STABLE_VFP(x) ((x) << 16)
+#define DSIM_MAIN_VBP(x) ((x) << 0)
+#define DSIM_CMD_ALLOW_MASK (0xf << 28)
+#define DSIM_STABLE_VFP_MASK (0x7ff << 16)
+#define DSIM_MAIN_VBP_MASK (0x7ff << 0)
+
+/* DSIM_MHPORCH */
+#define DSIM_MAIN_HFP(x) ((x) << 16)
+#define DSIM_MAIN_HBP(x) ((x) << 0)
+#define DSIM_MAIN_HFP_MASK ((0xffff) << 16)
+#define DSIM_MAIN_HBP_MASK ((0xffff) << 0)
+
+/* DSIM_MSYNC */
+#define DSIM_MAIN_VSA(x) ((x) << 22)
+#define DSIM_MAIN_HSA(x) ((x) << 0)
+#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22)
+#define DSIM_MAIN_HSA_MASK ((0xffff) << 0)
+
+/* DSIM_SDRESOL */
+#define DSIM_SUB_STANDY(x) ((x) << 31)
+#define DSIM_SUB_VRESOL(x) ((x) << 16)
+#define DSIM_SUB_HRESOL(x) ((x) << 0)
+#define DSIM_SUB_STANDY_MASK ((0x1) << 31)
+#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16)
+#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0)
+
+/* DSIM_INTSRC */
+#define DSIM_INT_PLL_STABLE BIT(31)
+#define DSIM_INT_SW_RST_RELEASE BIT(30)
+#define DSIM_INT_SFR_FIFO_EMPTY BIT(29)
+#define DSIM_INT_SFR_HDR_FIFO_EMPTY BIT(28)
+#define DSIM_INT_BTA BIT(25)
+#define DSIM_INT_FRAME_DONE BIT(24)
+#define DSIM_INT_RX_TIMEOUT BIT(21)
+#define DSIM_INT_BTA_TIMEOUT BIT(20)
+#define DSIM_INT_RX_DONE BIT(18)
+#define DSIM_INT_RX_TE BIT(17)
+#define DSIM_INT_RX_ACK BIT(16)
+#define DSIM_INT_RX_ECC_ERR BIT(15)
+#define DSIM_INT_RX_CRC_ERR BIT(14)
+
+/* DSIM_FIFOCTRL */
+#define DSIM_RX_DATA_FULL BIT(25)
+#define DSIM_RX_DATA_EMPTY BIT(24)
+#define DSIM_SFR_HEADER_FULL BIT(23)
+#define DSIM_SFR_HEADER_EMPTY BIT(22)
+#define DSIM_SFR_PAYLOAD_FULL BIT(21)
+#define DSIM_SFR_PAYLOAD_EMPTY BIT(20)
+#define DSIM_I80_HEADER_FULL BIT(19)
+#define DSIM_I80_HEADER_EMPTY BIT(18)
+#define DSIM_I80_PAYLOAD_FULL BIT(17)
+#define DSIM_I80_PAYLOAD_EMPTY BIT(16)
+#define DSIM_SD_HEADER_FULL BIT(15)
+#define DSIM_SD_HEADER_EMPTY BIT(14)
+#define DSIM_SD_PAYLOAD_FULL BIT(13)
+#define DSIM_SD_PAYLOAD_EMPTY BIT(12)
+#define DSIM_MD_HEADER_FULL BIT(11)
+#define DSIM_MD_HEADER_EMPTY BIT(10)
+#define DSIM_MD_PAYLOAD_FULL BIT(9)
+#define DSIM_MD_PAYLOAD_EMPTY BIT(8)
+#define DSIM_RX_FIFO BIT(4)
+#define DSIM_SFR_FIFO BIT(3)
+#define DSIM_I80_FIFO BIT(2)
+#define DSIM_SD_FIFO BIT(1)
+#define DSIM_MD_FIFO BIT(0)
+
+/* DSIM_PHYACCHR */
+#define DSIM_AFC_EN BIT(14)
+#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5)
+
+/* DSIM_PLLCTRL */
+#define DSIM_FREQ_BAND(x) ((x) << 24)
+#define DSIM_PLL_EN BIT(23)
+#define DSIM_PLL_P(x, offset) ((x) << (offset))
+#define DSIM_PLL_M(x) ((x) << 4)
+#define DSIM_PLL_S(x) ((x) << 1)
+
+/* DSIM_PHYCTRL */
+#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0)
+#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP BIT(30)
+#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP BIT(14)
+
+/* DSIM_PHYTIMING */
+#define DSIM_PHYTIMING_LPX(x) ((x) << 8)
+#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0)
+
+/* DSIM_PHYTIMING1 */
+#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24)
+#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16)
+#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8)
+#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0)
+
+/* DSIM_PHYTIMING2 */
+#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16)
+#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8)
+#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0)
+
+#define DSI_MAX_BUS_WIDTH 4
+#define DSI_NUM_VIRTUAL_CHANNELS 4
+#define DSI_TX_FIFO_SIZE 2048
+#define DSI_RX_FIFO_SIZE 256
+#define DSI_XFER_TIMEOUT_MS 100
+#define DSI_RX_FIFO_EMPTY 0x30800002
+
+struct samsung_dsim_driver_data {
+ const unsigned int *reg_ofs;
+ unsigned int plltmr_reg;
+ unsigned int has_freqband:1;
+ unsigned int has_clklane_stop:1;
+ unsigned int has_broken_fifoctrl_emptyhdr:1;
+ unsigned int num_clks;
+ unsigned int min_freq;
+ unsigned int max_freq;
+ unsigned int wait_for_reset;
+ unsigned int num_bits_resol;
+ unsigned int pll_p_offset;
+ const unsigned int *reg_values;
+ unsigned int pll_fin_min;
+ unsigned int pll_fin_max;
+ u16 m_min;
+ u16 m_max;
+};
+
+enum reg_idx {
+ DSIM_STATUS_REG, /* Status register */
+ DSIM_RGB_STATUS_REG, /* RGB status register */
+ DSIM_SWRST_REG, /* Software reset register */
+ DSIM_CLKCTRL_REG, /* Clock control register */
+ DSIM_TIMEOUT_REG, /* Time out register */
+ DSIM_CONFIG_REG, /* Configuration register */
+ DSIM_ESCMODE_REG, /* Escape mode register */
+ DSIM_MDRESOL_REG,
+ DSIM_MVPORCH_REG, /* Main display Vporch register */
+ DSIM_MHPORCH_REG, /* Main display Hporch register */
+ DSIM_MSYNC_REG, /* Main display sync area register */
+ DSIM_INTSRC_REG, /* Interrupt source register */
+ DSIM_INTMSK_REG, /* Interrupt mask register */
+ DSIM_PKTHDR_REG, /* Packet Header FIFO register */
+ DSIM_PAYLOAD_REG, /* Payload FIFO register */
+ DSIM_RXFIFO_REG, /* Read FIFO register */
+ DSIM_FIFOCTRL_REG, /* FIFO status and control register */
+ DSIM_PLLCTRL_REG, /* PLL control register */
+ DSIM_PHYCTRL_REG,
+ DSIM_PHYTIMING_REG,
+ DSIM_PHYTIMING1_REG,
+ DSIM_PHYTIMING2_REG,
+ NUM_REGS
+};
+
+static const unsigned int exynos_reg_ofs[] = {
+ [DSIM_STATUS_REG] = 0x00,
+ [DSIM_SWRST_REG] = 0x04,
+ [DSIM_CLKCTRL_REG] = 0x08,
+ [DSIM_TIMEOUT_REG] = 0x0c,
+ [DSIM_CONFIG_REG] = 0x10,
+ [DSIM_ESCMODE_REG] = 0x14,
+ [DSIM_MDRESOL_REG] = 0x18,
+ [DSIM_MVPORCH_REG] = 0x1c,
+ [DSIM_MHPORCH_REG] = 0x20,
+ [DSIM_MSYNC_REG] = 0x24,
+ [DSIM_INTSRC_REG] = 0x2c,
+ [DSIM_INTMSK_REG] = 0x30,
+ [DSIM_PKTHDR_REG] = 0x34,
+ [DSIM_PAYLOAD_REG] = 0x38,
+ [DSIM_RXFIFO_REG] = 0x3c,
+ [DSIM_FIFOCTRL_REG] = 0x44,
+ [DSIM_PLLCTRL_REG] = 0x4c,
+ [DSIM_PHYCTRL_REG] = 0x5c,
+ [DSIM_PHYTIMING_REG] = 0x64,
+ [DSIM_PHYTIMING1_REG] = 0x68,
+ [DSIM_PHYTIMING2_REG] = 0x6c,
+};
+
+static const unsigned int exynos5433_reg_ofs[] = {
+ [DSIM_STATUS_REG] = 0x04,
+ [DSIM_RGB_STATUS_REG] = 0x08,
+ [DSIM_SWRST_REG] = 0x0C,
+ [DSIM_CLKCTRL_REG] = 0x10,
+ [DSIM_TIMEOUT_REG] = 0x14,
+ [DSIM_CONFIG_REG] = 0x18,
+ [DSIM_ESCMODE_REG] = 0x1C,
+ [DSIM_MDRESOL_REG] = 0x20,
+ [DSIM_MVPORCH_REG] = 0x24,
+ [DSIM_MHPORCH_REG] = 0x28,
+ [DSIM_MSYNC_REG] = 0x2C,
+ [DSIM_INTSRC_REG] = 0x34,
+ [DSIM_INTMSK_REG] = 0x38,
+ [DSIM_PKTHDR_REG] = 0x3C,
+ [DSIM_PAYLOAD_REG] = 0x40,
+ [DSIM_RXFIFO_REG] = 0x44,
+ [DSIM_FIFOCTRL_REG] = 0x4C,
+ [DSIM_PLLCTRL_REG] = 0x94,
+ [DSIM_PHYCTRL_REG] = 0xA4,
+ [DSIM_PHYTIMING_REG] = 0xB4,
+ [DSIM_PHYTIMING1_REG] = 0xB8,
+ [DSIM_PHYTIMING2_REG] = 0xBC,
+};
+
+enum reg_value_idx {
+ RESET_TYPE,
+ PLL_TIMER,
+ STOP_STATE_CNT,
+ PHYCTRL_ULPS_EXIT,
+ PHYCTRL_VREG_LP,
+ PHYCTRL_SLEW_UP,
+ PHYTIMING_LPX,
+ PHYTIMING_HS_EXIT,
+ PHYTIMING_CLK_PREPARE,
+ PHYTIMING_CLK_ZERO,
+ PHYTIMING_CLK_POST,
+ PHYTIMING_CLK_TRAIL,
+ PHYTIMING_HS_PREPARE,
+ PHYTIMING_HS_ZERO,
+ PHYTIMING_HS_TRAIL
+};
+
+static const unsigned int reg_values[] = {
+ [RESET_TYPE] = DSIM_SWRST,
+ [PLL_TIMER] = 500,
+ [STOP_STATE_CNT] = 0xf,
+ [PHYCTRL_ULPS_EXIT] = DSIM_PHYCTRL_ULPS_EXIT(0x0af),
+ [PHYCTRL_VREG_LP] = 0,
+ [PHYCTRL_SLEW_UP] = 0,
+ [PHYTIMING_LPX] = DSIM_PHYTIMING_LPX(0x06),
+ [PHYTIMING_HS_EXIT] = DSIM_PHYTIMING_HS_EXIT(0x0b),
+ [PHYTIMING_CLK_PREPARE] = DSIM_PHYTIMING1_CLK_PREPARE(0x07),
+ [PHYTIMING_CLK_ZERO] = DSIM_PHYTIMING1_CLK_ZERO(0x27),
+ [PHYTIMING_CLK_POST] = DSIM_PHYTIMING1_CLK_POST(0x0d),
+ [PHYTIMING_CLK_TRAIL] = DSIM_PHYTIMING1_CLK_TRAIL(0x08),
+ [PHYTIMING_HS_PREPARE] = DSIM_PHYTIMING2_HS_PREPARE(0x09),
+ [PHYTIMING_HS_ZERO] = DSIM_PHYTIMING2_HS_ZERO(0x0d),
+ [PHYTIMING_HS_TRAIL] = DSIM_PHYTIMING2_HS_TRAIL(0x0b),
+};
+
+static const unsigned int exynos5422_reg_values[] = {
+ [RESET_TYPE] = DSIM_SWRST,
+ [PLL_TIMER] = 500,
+ [STOP_STATE_CNT] = 0xf,
+ [PHYCTRL_ULPS_EXIT] = DSIM_PHYCTRL_ULPS_EXIT(0xaf),
+ [PHYCTRL_VREG_LP] = 0,
+ [PHYCTRL_SLEW_UP] = 0,
+ [PHYTIMING_LPX] = DSIM_PHYTIMING_LPX(0x08),
+ [PHYTIMING_HS_EXIT] = DSIM_PHYTIMING_HS_EXIT(0x0d),
+ [PHYTIMING_CLK_PREPARE] = DSIM_PHYTIMING1_CLK_PREPARE(0x09),
+ [PHYTIMING_CLK_ZERO] = DSIM_PHYTIMING1_CLK_ZERO(0x30),
+ [PHYTIMING_CLK_POST] = DSIM_PHYTIMING1_CLK_POST(0x0e),
+ [PHYTIMING_CLK_TRAIL] = DSIM_PHYTIMING1_CLK_TRAIL(0x0a),
+ [PHYTIMING_HS_PREPARE] = DSIM_PHYTIMING2_HS_PREPARE(0x0c),
+ [PHYTIMING_HS_ZERO] = DSIM_PHYTIMING2_HS_ZERO(0x11),
+ [PHYTIMING_HS_TRAIL] = DSIM_PHYTIMING2_HS_TRAIL(0x0d),
+};
+
+static const unsigned int exynos5433_reg_values[] = {
+ [RESET_TYPE] = DSIM_FUNCRST,
+ [PLL_TIMER] = 22200,
+ [STOP_STATE_CNT] = 0xa,
+ [PHYCTRL_ULPS_EXIT] = DSIM_PHYCTRL_ULPS_EXIT(0x190),
+ [PHYCTRL_VREG_LP] = DSIM_PHYCTRL_B_DPHYCTL_VREG_LP,
+ [PHYCTRL_SLEW_UP] = DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP,
+ [PHYTIMING_LPX] = DSIM_PHYTIMING_LPX(0x07),
+ [PHYTIMING_HS_EXIT] = DSIM_PHYTIMING_HS_EXIT(0x0c),
+ [PHYTIMING_CLK_PREPARE] = DSIM_PHYTIMING1_CLK_PREPARE(0x09),
+ [PHYTIMING_CLK_ZERO] = DSIM_PHYTIMING1_CLK_ZERO(0x2d),
+ [PHYTIMING_CLK_POST] = DSIM_PHYTIMING1_CLK_POST(0x0e),
+ [PHYTIMING_CLK_TRAIL] = DSIM_PHYTIMING1_CLK_TRAIL(0x09),
+ [PHYTIMING_HS_PREPARE] = DSIM_PHYTIMING2_HS_PREPARE(0x0b),
+ [PHYTIMING_HS_ZERO] = DSIM_PHYTIMING2_HS_ZERO(0x10),
+ [PHYTIMING_HS_TRAIL] = DSIM_PHYTIMING2_HS_TRAIL(0x0c),
+};
+
+static const unsigned int imx8mm_dsim_reg_values[] = {
+ [RESET_TYPE] = DSIM_SWRST,
+ [PLL_TIMER] = 500,
+ [STOP_STATE_CNT] = 0xf,
+ [PHYCTRL_ULPS_EXIT] = DSIM_PHYCTRL_ULPS_EXIT(0xaf),
+ [PHYCTRL_VREG_LP] = 0,
+ [PHYCTRL_SLEW_UP] = 0,
+ [PHYTIMING_LPX] = DSIM_PHYTIMING_LPX(0x06),
+ [PHYTIMING_HS_EXIT] = DSIM_PHYTIMING_HS_EXIT(0x0b),
+ [PHYTIMING_CLK_PREPARE] = DSIM_PHYTIMING1_CLK_PREPARE(0x07),
+ [PHYTIMING_CLK_ZERO] = DSIM_PHYTIMING1_CLK_ZERO(0x26),
+ [PHYTIMING_CLK_POST] = DSIM_PHYTIMING1_CLK_POST(0x0d),
+ [PHYTIMING_CLK_TRAIL] = DSIM_PHYTIMING1_CLK_TRAIL(0x08),
+ [PHYTIMING_HS_PREPARE] = DSIM_PHYTIMING2_HS_PREPARE(0x08),
+ [PHYTIMING_HS_ZERO] = DSIM_PHYTIMING2_HS_ZERO(0x0d),
+ [PHYTIMING_HS_TRAIL] = DSIM_PHYTIMING2_HS_TRAIL(0x0b),
+};
+
+static const struct samsung_dsim_driver_data exynos3_dsi_driver_data = {
+ .reg_ofs = exynos_reg_ofs,
+ .plltmr_reg = 0x50,
+ .has_freqband = 1,
+ .has_clklane_stop = 1,
+ .num_clks = 2,
+ .max_freq = 1000,
+ .wait_for_reset = 1,
+ .num_bits_resol = 11,
+ .pll_p_offset = 13,
+ .reg_values = reg_values,
+ .pll_fin_min = 6,
+ .pll_fin_max = 12,
+ .m_min = 41,
+ .m_max = 125,
+ .min_freq = 500,
+ .has_broken_fifoctrl_emptyhdr = 1,
+};
+
+static const struct samsung_dsim_driver_data exynos4_dsi_driver_data = {
+ .reg_ofs = exynos_reg_ofs,
+ .plltmr_reg = 0x50,
+ .has_freqband = 1,
+ .has_clklane_stop = 1,
+ .num_clks = 2,
+ .max_freq = 1000,
+ .wait_for_reset = 1,
+ .num_bits_resol = 11,
+ .pll_p_offset = 13,
+ .reg_values = reg_values,
+ .pll_fin_min = 6,
+ .pll_fin_max = 12,
+ .m_min = 41,
+ .m_max = 125,
+ .min_freq = 500,
+ .has_broken_fifoctrl_emptyhdr = 1,
+};
+
+static const struct samsung_dsim_driver_data exynos5_dsi_driver_data = {
+ .reg_ofs = exynos_reg_ofs,
+ .plltmr_reg = 0x58,
+ .num_clks = 2,
+ .max_freq = 1000,
+ .wait_for_reset = 1,
+ .num_bits_resol = 11,
+ .pll_p_offset = 13,
+ .reg_values = reg_values,
+ .pll_fin_min = 6,
+ .pll_fin_max = 12,
+ .m_min = 41,
+ .m_max = 125,
+ .min_freq = 500,
+};
+
+static const struct samsung_dsim_driver_data exynos5433_dsi_driver_data = {
+ .reg_ofs = exynos5433_reg_ofs,
+ .plltmr_reg = 0xa0,
+ .has_clklane_stop = 1,
+ .num_clks = 5,
+ .max_freq = 1500,
+ .wait_for_reset = 0,
+ .num_bits_resol = 12,
+ .pll_p_offset = 13,
+ .reg_values = exynos5433_reg_values,
+ .pll_fin_min = 6,
+ .pll_fin_max = 12,
+ .m_min = 41,
+ .m_max = 125,
+ .min_freq = 500,
+};
+
+static const struct samsung_dsim_driver_data exynos5422_dsi_driver_data = {
+ .reg_ofs = exynos5433_reg_ofs,
+ .plltmr_reg = 0xa0,
+ .has_clklane_stop = 1,
+ .num_clks = 2,
+ .max_freq = 1500,
+ .wait_for_reset = 1,
+ .num_bits_resol = 12,
+ .pll_p_offset = 13,
+ .reg_values = exynos5422_reg_values,
+ .pll_fin_min = 6,
+ .pll_fin_max = 12,
+ .m_min = 41,
+ .m_max = 125,
+ .min_freq = 500,
+};
+
+static const struct samsung_dsim_driver_data imx8mm_dsi_driver_data = {
+ .reg_ofs = exynos5433_reg_ofs,
+ .plltmr_reg = 0xa0,
+ .has_clklane_stop = 1,
+ .num_clks = 2,
+ .max_freq = 2100,
+ .wait_for_reset = 0,
+ .num_bits_resol = 12,
+ /*
+ * Unlike Exynos, PLL_P(PMS_P) offset 14 is used in i.MX8M Mini/Nano/Plus
+ * downstream driver - drivers/gpu/drm/bridge/sec-dsim.c
+ */
+ .pll_p_offset = 14,
+ .reg_values = imx8mm_dsim_reg_values,
+ .pll_fin_min = 2,
+ .pll_fin_max = 30,
+ .m_min = 64,
+ .m_max = 1023,
+ .min_freq = 1050,
+};
+
+static const struct samsung_dsim_driver_data *
+samsung_dsim_types[DSIM_TYPE_COUNT] = {
+ [DSIM_TYPE_EXYNOS3250] = &exynos3_dsi_driver_data,
+ [DSIM_TYPE_EXYNOS4210] = &exynos4_dsi_driver_data,
+ [DSIM_TYPE_EXYNOS5410] = &exynos5_dsi_driver_data,
+ [DSIM_TYPE_EXYNOS5422] = &exynos5422_dsi_driver_data,
+ [DSIM_TYPE_EXYNOS5433] = &exynos5433_dsi_driver_data,
+ [DSIM_TYPE_IMX8MM] = &imx8mm_dsi_driver_data,
+ [DSIM_TYPE_IMX8MP] = &imx8mm_dsi_driver_data,
+};
+
+/* DSIM PLL configuration from spec:
+ *
+ * Fout(DDR) = (M * Fin) / (P * 2^S), so Fout / Fin = M / (P * 2^S)
+ * Fin_pll = Fin / P (6 ~ 12 MHz)
+ * S: [2:0], M: [12:3], P: [18:13], so
+ * TODO: 'S' is in [0 ~ 3], 'M' is in, 'P' is in [1 ~ 33]
+ *
+ */
+
+struct samsung_dsi {
+ void __iomem *reg_base;
+ struct clk sclk_mipi;
+ const struct samsung_dsim_driver_data *driver_data;
+
+ /* kHz clocks */
+ u64 pix_clk;
+ u64 bit_clk;
+ u64 hs_clock;
+
+ unsigned int lanes;
+ unsigned int channel; /* virtual channel */
+ enum mipi_dsi_pixel_format format;
+ unsigned long mode_flags;
+ unsigned int pms;
+
+ struct mipi_dsi_device *device;
+ u32 max_data_lanes;
+
+ struct mipi_dsi_host dsi_host;
+ struct display_timing timings;
+};
+
+static inline void samsung_dsim_write(struct samsung_dsi *dsi,
+ enum reg_idx idx, u32 val)
+{
+ writel(val, dsi->reg_base + dsi->driver_data->reg_ofs[idx]);
+}
+
+static inline u32 samsung_dsim_read(struct samsung_dsi *dsi, enum reg_idx idx)
+{
+ return readl(dsi->reg_base + dsi->driver_data->reg_ofs[idx]);
+}
+
+static int samsung_dsi_wait_for_pkt_done(struct samsung_dsi *dsim, unsigned long timeout)
+{
+ u32 intsrc;
+
+ do {
+ intsrc = samsung_dsim_read(dsim, DSIM_INTSRC_REG);
+ if (intsrc & INTSRC_SFRPLFIFOEMPTY) {
+ samsung_dsim_write(dsim, DSIM_INTSRC_REG, INTSRC_SFRPLFIFOEMPTY);
+ return 0;
+ }
+
+ udelay(1);
+ } while (--timeout);
+
+ return -ETIMEDOUT;
+}
+
+static int samsung_dsi_wait_for_hdr_done(struct samsung_dsi *dsim, unsigned long timeout)
+{
+ u32 intsrc;
+
+ do {
+ intsrc = samsung_dsim_read(dsim, DSIM_INTSRC_REG);
+ if (intsrc & INTSRC_SFRPHFIFOEMPTY) {
+ samsung_dsim_write(dsim, DSIM_INTSRC_REG, INTSRC_SFRPHFIFOEMPTY);
+ return 0;
+ }
+
+ udelay(1);
+ } while (--timeout);
+
+ return -ETIMEDOUT;
+}
+
+static int samsung_dsi_wait_for_rx_done(struct samsung_dsi *dsim,
+ unsigned long timeout)
+{
+ u32 intsrc;
+
+ do {
+ intsrc = samsung_dsim_read(dsim, DSIM_INTSRC_REG);
+ if (intsrc & INTSRC_RXDATDONE) {
+ samsung_dsim_write(dsim, DSIM_INTSRC_REG, INTSRC_RXDATDONE);
+ return 0;
+ }
+
+ udelay(1);
+ } while (--timeout);
+
+ return -ETIMEDOUT;
+}
+
+static int samsung_dsi_wait_pll_stable(struct samsung_dsi *dsim)
+{
+ u32 status;
+ ulong start;
+
+ start = get_timer(0); /* Get current timestamp */
+
+ do {
+ status = samsung_dsim_read(dsim, DSIM_STATUS_REG);
+ if (status & DSIM_PLL_STABLE)
+ return 0;
+ } while (get_timer(0) < (start + 100)); /* Wait 100ms */
+
+ return -ETIMEDOUT;
+}
+
+static unsigned long samsung_dsim_pll_find_pms(struct samsung_dsi *dsi,
+ unsigned long fin,
+ unsigned long fout,
+ u8 *p, u16 *m, u8 *s)
+{
+ const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
+ unsigned long best_freq = 0;
+ u32 min_delta = 0xffffffff;
+ u8 p_min, p_max;
+ u8 _p, best_p;
+ u16 _m, best_m;
+ u8 _s, best_s;
+
+ p_min = DIV_ROUND_UP(fin, (MHZ(12)));
+ p_max = fin / (MHZ(6));
+
+ for (_p = p_min; _p <= p_max; ++_p) {
+ for (_s = 0; _s <= 5; ++_s) {
+ u64 tmp;
+ u32 delta;
+
+ tmp = (u64)fout * (_p << _s);
+ do_div(tmp, fin);
+ _m = tmp;
+ if (_m < driver_data->m_min || _m > driver_data->m_max)
+ continue;
+
+ tmp = (u64)_m * fin;
+ do_div(tmp, _p);
+ if (tmp < driver_data->min_freq * MHZ(1) ||
+ tmp > driver_data->max_freq * MHZ(1))
+ continue;
+
+ tmp = (u64)_m * fin;
+ do_div(tmp, _p << _s);
+
+ delta = abs(fout - tmp);
+ if (delta < min_delta) {
+ best_p = _p;
+ best_m = _m;
+ best_s = _s;
+ min_delta = delta;
+ best_freq = tmp;
+ }
+ }
+ }
+
+ if (best_freq) {
+ *p = best_p;
+ *m = best_m;
+ *s = best_s;
+ }
+
+ return best_freq;
+}
+
+static int samsung_dsi_config_pll(struct samsung_dsi *dsim)
+{
+ int ret;
+ u32 pllctrl = 0, status, data_lanes_en, stop;
+
+ writel(dsim->driver_data->reg_values[PLL_TIMER],
+ dsim->reg_base + dsim->driver_data->plltmr_reg);
+
+ /* TODO: config dp/dn swap if requires */
+
+ pllctrl |= PLLCTRL_SET_PMS(dsim->pms) | PLLCTRL_PLLEN;
+ samsung_dsim_write(dsim, DSIM_PLLCTRL_REG, pllctrl);
+
+ ret = samsung_dsi_wait_pll_stable(dsim);
+ if (ret) {
+ log_err("wait for pll stable time out\n");
+ return ret;
+ }
+
+ /* wait for clk & data lanes to go to stop state */
+ mdelay(1);
+
+ data_lanes_en = (0x1 << dsim->lanes) - 1;
+ status = samsung_dsim_read(dsim, DSIM_STATUS_REG);
+ if (!(status & DSIM_STOP_STATE_CLK)) {
+ log_err("clock is not in stop state\n");
+ return -EBUSY;
+ }
+
+ stop = DSIM_STOP_STATE_DAT(status);
+ if ((stop & data_lanes_en) != data_lanes_en) {
+ log_err("one or more data lanes is not in stop state\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static void samsung_dsi_set_main_mode(struct samsung_dsi *dsim)
+{
+ u32 bpp, hfp_wc, hbp_wc, hsa_wc, wc;
+ u32 mdresol = 0, mvporch = 0, mhporch = 0, msync = 0;
+ struct display_timing *timings = &dsim->timings;
+ unsigned int num_bits_resol = dsim->driver_data->num_bits_resol;
+
+ mdresol |= DSIM_MAIN_VRESOL(timings->vactive.typ, num_bits_resol) |
+ DSIM_MAIN_HRESOL(timings->hactive.typ, num_bits_resol);
+ samsung_dsim_write(dsim, DSIM_MDRESOL_REG, mdresol);
+
+ mvporch |= MVPORCH_SET_MAINVBP(timings->vback_porch.typ) |
+ MVPORCH_SET_STABLEVFP(timings->vfront_porch.typ) |
+ MVPORCH_SET_CMDALLOW(0x0);
+ samsung_dsim_write(dsim, DSIM_MVPORCH_REG, mvporch);
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsim->format);
+
+ wc = DIV_ROUND_UP(timings->hfront_porch.typ * (bpp >> 3), dsim->lanes);
+ hfp_wc = wc > MIPI_HFP_PKT_OVERHEAD ?
+ wc - MIPI_HFP_PKT_OVERHEAD : timings->hfront_porch.typ;
+ wc = DIV_ROUND_UP(timings->hback_porch.typ * (bpp >> 3), dsim->lanes);
+ hbp_wc = wc > MIPI_HBP_PKT_OVERHEAD ?
+ wc - MIPI_HBP_PKT_OVERHEAD : timings->hback_porch.typ;
+
+ mhporch |= MHPORCH_SET_MAINHFP(hfp_wc) |
+ MHPORCH_SET_MAINHBP(hbp_wc);
+
+ samsung_dsim_write(dsim, DSIM_MHPORCH_REG, mhporch);
+
+ wc = DIV_ROUND_UP(timings->hsync_len.typ * (bpp >> 3), dsim->lanes);
+ hsa_wc = wc > MIPI_HSA_PKT_OVERHEAD ?
+ wc - MIPI_HSA_PKT_OVERHEAD : timings->hsync_len.typ;
+
+ msync |= MSYNC_SET_MAINVSA(timings->vsync_len.typ) |
+ MSYNC_SET_MAINHSA(hsa_wc);
+
+ debug("hfp_wc %u hbp_wc %u hsa_wc %u\n", hfp_wc, hbp_wc, hsa_wc);
+
+ samsung_dsim_write(dsim, DSIM_MSYNC_REG, msync);
+}
+
+static void samsung_dsi_config_dpi(struct samsung_dsi *dsim)
+{
+ u32 config = 0, rgb_status = 0, data_lanes_en;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO)
+ rgb_status &= ~RGB_STATUS_CMDMODE_INSEL;
+ else
+ rgb_status |= RGB_STATUS_CMDMODE_INSEL;
+
+ samsung_dsim_write(dsim, DSIM_RGB_STATUS_REG, rgb_status);
+
+ if (dsim->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
+ config |= DSIM_CLKLANE_STOP;
+ config |= DSIM_NON_CONTINUOUS_CLKLANE;
+ }
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH)
+ config |= DSIM_MFLUSH_VS;
+
+ /* disable EoT packets in HS mode */
+ if (dsim->mode_flags & MIPI_DSI_MODE_EOT_PACKET)
+ config |= DSIM_EOT_DISABLE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) {
+ config |= DSIM_VIDEO_MODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+ config |= DSIM_BURST_MODE;
+
+ else if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ config |= DSIM_SYNC_INFORM;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT)
+ config |= DSIM_AUTO_MODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HSE)
+ config |= DSIM_HSE_DISABLE_MODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HFP)
+ config |= DSIM_HFP_DISABLE_MODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HBP)
+ config |= DSIM_HBP_DISABLE_MODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HSA)
+ config |= DSIM_HSA_DISABLE_MODE;
+ }
+
+ config |= DSIM_MAIN_VC(dsim->channel);
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) {
+ switch (dsim->format) {
+ case MIPI_DSI_FMT_RGB888:
+ config |= DSIM_MAIN_PIX_FORMAT_RGB888;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ config |= DSIM_MAIN_PIX_FORMAT_RGB666;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ config |= DSIM_MAIN_PIX_FORMAT_RGB666_P;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ config |= DSIM_MAIN_PIX_FORMAT_RGB565;
+ break;
+ default:
+ log_err("invalid pixel format\n");
+ break;
+ }
+ }
+
+ /* config data lanes number and enable lanes */
+ data_lanes_en = BIT(dsim->lanes) - 1;
+ config |= (DSIM_NUM_OF_DATA_LANE(dsim->lanes - 1) | DSIM_LANE_EN_CLK |
+ DSIM_LANE_EN(data_lanes_en));
+
+ debug("DSIM config 0x%x\n", config);
+
+ samsung_dsim_write(dsim, DSIM_CONFIG_REG, config);
+}
+
+static void samsung_dsi_config_cmd_lpm(struct samsung_dsi *dsim, bool enable)
+{
+ u32 escmode;
+
+ escmode = samsung_dsim_read(dsim, DSIM_ESCMODE_REG);
+
+ if (enable)
+ escmode |= ESCMODE_CMDLPDT;
+ else
+ escmode &= ~ESCMODE_CMDLPDT;
+
+ samsung_dsim_write(dsim, DSIM_ESCMODE_REG, escmode);
+}
+
+static void samsung_dsi_config_dphy(struct samsung_dsi *dsim)
+{
+ const struct samsung_dsim_driver_data *driver_data = dsim->driver_data;
+ const unsigned int *reg_values = driver_data->reg_values;
+ u32 reg;
+ struct phy_configure_opts_mipi_dphy cfg;
+ int clk_prepare, lpx, clk_zero, clk_post, clk_trail;
+ int hs_exit, hs_prepare, hs_zero, hs_trail;
+ unsigned long long byte_clock = dsim->hs_clock / 8;
+
+ if (driver_data->has_freqband)
+ return;
+
+ phy_mipi_dphy_get_default_config_for_hsclk(dsim->hs_clock, dsim->lanes, &cfg);
+
+ /*
+ * TODO:
+ * The tech Applications Processor manuals for i.MX8M Mini, Nano,
+ * and Plus don't state what the definition of the PHYTIMING
+ * bits are beyond their address and bit position.
+ * After reviewing NXP's downstream code, it appears
+ * that the various PHYTIMING registers take the number
+ * of cycles and use various dividers on them. This
+ * calculation does not result in an exact match to the
+ * downstream code, but it is very close to the values
+ * generated by their lookup table, and it appears
+ * to sync at a variety of resolutions. If someone
+ * can get a more accurate mathematical equation needed
+ * for these registers, this should be updated.
+ */
+
+ lpx = PS_TO_CYCLE(cfg.lpx, byte_clock);
+ hs_exit = PS_TO_CYCLE(cfg.hs_exit, byte_clock);
+ clk_prepare = PS_TO_CYCLE(cfg.clk_prepare, byte_clock);
+ clk_zero = PS_TO_CYCLE(cfg.clk_zero, byte_clock);
+ clk_post = PS_TO_CYCLE(cfg.clk_post, byte_clock);
+ clk_trail = PS_TO_CYCLE(cfg.clk_trail, byte_clock);
+ hs_prepare = PS_TO_CYCLE(cfg.hs_prepare, byte_clock);
+ hs_zero = PS_TO_CYCLE(cfg.hs_zero, byte_clock);
+ hs_trail = PS_TO_CYCLE(cfg.hs_trail, byte_clock);
+
+ /* B D-PHY: D-PHY Master & Slave Analog Block control */
+ reg = reg_values[PHYCTRL_ULPS_EXIT] | reg_values[PHYCTRL_VREG_LP] |
+ reg_values[PHYCTRL_SLEW_UP];
+
+ samsung_dsim_write(dsim, DSIM_PHYCTRL_REG, reg);
+
+ /*
+ * T LPX: Transmitted length of any Low-Power state period
+ * T HS-EXIT: Time that the transmitter drives LP-11 following a HS
+ * burst
+ */
+
+ reg = DSIM_PHYTIMING_LPX(lpx) | DSIM_PHYTIMING_HS_EXIT(hs_exit);
+ samsung_dsim_write(dsim, DSIM_PHYTIMING_REG, reg);
+
+ /*
+ * T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00
+ * Line state immediately before the HS-0 Line state starting the
+ * HS transmission
+ * T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to
+ * transmitting the Clock.
+ * T CLK_POST: Time that the transmitter continues to send HS clock
+ * after the last associated Data Lane has transitioned to LP Mode
+ * Interval is defined as the period from the end of T HS-TRAIL to
+ * the beginning of T CLK-TRAIL
+ * T CLK-TRAIL: Time that the transmitter drives the HS-0 state after
+ * the last payload clock bit of a HS transmission burst
+ */
+
+ reg = DSIM_PHYTIMING1_CLK_PREPARE(clk_prepare) |
+ DSIM_PHYTIMING1_CLK_ZERO(clk_zero) |
+ DSIM_PHYTIMING1_CLK_POST(clk_post) |
+ DSIM_PHYTIMING1_CLK_TRAIL(clk_trail);
+
+ samsung_dsim_write(dsim, DSIM_PHYTIMING1_REG, reg);
+
+ /*
+ * T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00
+ * Line state immediately before the HS-0 Line state starting the
+ * HS transmission
+ * T HS-ZERO: Time that the transmitter drives the HS-0 state prior to
+ * transmitting the Sync sequence.
+ * T HS-TRAIL: Time that the transmitter drives the flipped differential
+ * state after last payload data bit of a HS transmission burst
+ */
+
+ reg = DSIM_PHYTIMING2_HS_PREPARE(hs_prepare) |
+ DSIM_PHYTIMING2_HS_ZERO(hs_zero) |
+ DSIM_PHYTIMING2_HS_TRAIL(hs_trail);
+
+ samsung_dsim_write(dsim, DSIM_PHYTIMING2_REG, reg);
+
+ reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff);
+
+ samsung_dsim_write(dsim, DSIM_TIMEOUT_REG, reg);
+}
+
+static void samsung_dsim_write_pl_to_sfr_fifo(struct samsung_dsi *dsim,
+ const void *payload,
+ size_t length)
+{
+ u32 pl_data;
+
+ if (!length)
+ return;
+
+ while (length >= 4) {
+ pl_data = get_unaligned_le32(payload);
+ samsung_dsim_write(dsim, DSIM_PAYLOAD_REG, pl_data);
+ payload += 4;
+ length -= 4;
+ }
+
+ pl_data = 0;
+ switch (length) {
+ case 3:
+ pl_data |= ((u8 *)payload)[2] << 16;
+ case 2:
+ pl_data |= ((u8 *)payload)[1] << 8;
+ case 1:
+ pl_data |= ((u8 *)payload)[0];
+ samsung_dsim_write(dsim, DSIM_PAYLOAD_REG, pl_data);
+ break;
+ }
+}
+
+static void samsung_dsim_write_ph_to_sfr_fifo(struct samsung_dsi *dsim,
+ void *header, bool use_lpm)
+{
+ u32 pkthdr;
+
+ pkthdr = PKTHDR_SET_DATA1(((u8 *)header)[2]) | /* WC MSB */
+ PKTHDR_SET_DATA0(((u8 *)header)[1]) | /* WC LSB */
+ PKTHDR_SET_DI(((u8 *)header)[0]); /* Data ID */
+
+ samsung_dsim_write(dsim, DSIM_PKTHDR_REG, pkthdr);
+}
+
+static int samsung_dsim_read_pl_from_sfr_fifo(struct samsung_dsi *dsim,
+ void *payload, size_t length)
+{
+ u8 data_type;
+ u16 word_count = 0;
+ u32 fifoctrl, ph, pl;
+
+ fifoctrl = samsung_dsim_read(dsim, DSIM_FIFOCTRL_REG);
+
+ if (WARN_ON(fifoctrl & FIFOCTRL_EMPTYRX))
+ return -EINVAL;
+
+ ph = samsung_dsim_read(dsim, DSIM_RXFIFO_REG);
+ data_type = PKTHDR_GET_DT(ph);
+ switch (data_type) {
+ case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
+ dev_err(dsim->device->dev, "peripheral report error: (0-7)%x, (8-15)%x\n",
+ PKTHDR_GET_DATA0(ph), PKTHDR_GET_DATA1(ph));
+ return -EPROTO;
+ case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
+ case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
+ if (!WARN_ON(length < 2)) {
+ ((u8 *)payload)[1] = PKTHDR_GET_DATA1(ph);
+ word_count++;
+ }
+ fallthrough;
+ case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
+ case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
+ ((u8 *)payload)[0] = PKTHDR_GET_DATA0(ph);
+ word_count++;
+ length = word_count;
+ break;
+ case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE:
+ case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE:
+ word_count = PKTHDR_GET_WC(ph);
+ if (word_count > length) {
+ dev_err(dsim->device->dev, "invalid receive buffer length\n");
+ return -EINVAL;
+ }
+
+ length = word_count;
+
+ while (word_count >= 4) {
+ pl = samsung_dsim_read(dsim, DSIM_RXFIFO_REG);
+ ((u8 *)payload)[0] = pl & 0xff;
+ ((u8 *)payload)[1] = (pl >> 8) & 0xff;
+ ((u8 *)payload)[2] = (pl >> 16) & 0xff;
+ ((u8 *)payload)[3] = (pl >> 24) & 0xff;
+ payload += 4;
+ word_count -= 4;
+ }
+
+ if (word_count > 0) {
+ pl = samsung_dsim_read(dsim, DSIM_RXFIFO_REG);
+
+ switch (word_count) {
+ case 3:
+ ((u8 *)payload)[2] = (pl >> 16) & 0xff;
+ case 2:
+ ((u8 *)payload)[1] = (pl >> 8) & 0xff;
+ case 1:
+ ((u8 *)payload)[0] = pl & 0xff;
+ break;
+ }
+ }
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return length;
+}
+
+static void samsung_dsi_init_fifo_pointers(struct samsung_dsi *dsim)
+{
+ u32 fifoctrl, fifo_ptrs;
+
+ fifoctrl = samsung_dsim_read(dsim, DSIM_FIFOCTRL_REG);
+
+ fifo_ptrs = FIFOCTRL_NINITRX |
+ FIFOCTRL_NINITSFR |
+ FIFOCTRL_NINITI80 |
+ FIFOCTRL_NINITSUB |
+ FIFOCTRL_NINITMAIN;
+
+ fifoctrl &= ~fifo_ptrs;
+ samsung_dsim_write(dsim, DSIM_FIFOCTRL_REG, fifoctrl);
+ udelay(500);
+
+ fifoctrl |= fifo_ptrs;
+ samsung_dsim_write(dsim, DSIM_FIFOCTRL_REG, fifoctrl);
+ udelay(500);
+}
+
+static void samsung_dsi_config_clkctrl(struct samsung_dsi *dsim)
+{
+ u32 clkctrl = 0, data_lanes_en;
+ u64 byte_clk, esc_prescaler;
+
+ clkctrl |= DSIM_TX_REQUEST_HSCLK;
+
+ /* using 1.5Gbps PHY */
+ clkctrl |= CLKCTRL_DPHY_SEL_1P5G;
+ clkctrl |= DSIM_ESC_CLKEN;
+ clkctrl &= ~CLKCTRL_PLLBYPASS;
+ clkctrl |= CLKCTRL_BYTECLKSRC_DPHY_PLL;
+ clkctrl |= DSIM_BYTE_CLKEN;
+
+ data_lanes_en = (0x1 << dsim->lanes) - 1;
+ clkctrl |= CLKCTRL_SET_LANEESCCLKEN(0x1 | data_lanes_en << 1);
+
+ /* calculate esc prescaler from byte clock:
+ * EscClk = ByteClk / EscPrescaler;
+ */
+ byte_clk = dsim->bit_clk >> 3;
+ esc_prescaler = DIV_ROUND_UP_ULL(byte_clk, MAX_ESC_CLK_FREQ);
+
+ clkctrl |= CLKCTRL_SET_ESCPRESCALER(esc_prescaler);
+
+ debug("DSIM clkctrl 0x%x\n", clkctrl);
+
+ samsung_dsim_write(dsim, DSIM_CLKCTRL_REG, clkctrl);
+}
+
+static void samsung_dsi_set_standby(struct samsung_dsi *dsim, bool standby)
+{
+ u32 mdresol = 0;
+
+ mdresol = samsung_dsim_read(dsim, DSIM_MDRESOL_REG);
+
+ if (standby)
+ mdresol |= DSIM_MAIN_STAND_BY;
+ else
+ mdresol &= ~DSIM_MAIN_STAND_BY;
+
+ samsung_dsim_write(dsim, DSIM_MDRESOL_REG, mdresol);
+}
+
+static void samsung_dsi_disable_clock(struct samsung_dsi *dsim)
+{
+ u32 reg;
+
+ reg = samsung_dsim_read(dsim, DSIM_CLKCTRL_REG);
+ reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK |
+ DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN);
+ samsung_dsim_write(dsim, DSIM_CLKCTRL_REG, reg);
+
+ reg = samsung_dsim_read(dsim, DSIM_PLLCTRL_REG);
+ reg &= ~DSIM_PLL_EN;
+ samsung_dsim_write(dsim, DSIM_PLLCTRL_REG, reg);
+}
+
+static inline struct samsung_dsi *host_to_dsi(struct mipi_dsi_host *host)
+{
+ return container_of(host, struct samsung_dsi, dsi_host);
+}
+
+static int samsung_dsi_bridge_clk_set(struct samsung_dsi *dsim_host)
+{
+ int bpp;
+ unsigned long pix_clk, bit_clk;
+ unsigned long fin, fout;
+ u8 p, s;
+ u16 m;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsim_host->format);
+ if (bpp < 0)
+ return -EINVAL;
+
+ pix_clk = dsim_host->timings.pixelclock.typ;
+ bit_clk = DIV_ROUND_UP_ULL(pix_clk * bpp, dsim_host->lanes);
+
+ dsim_host->pix_clk = DIV_ROUND_UP_ULL(pix_clk, 1000);
+ dsim_host->bit_clk = DIV_ROUND_UP_ULL(bit_clk, 1000);
+
+ fout = dsim_host->bit_clk;
+ fin = clk_get_rate(&dsim_host->sclk_mipi);
+ if (fin == 0) {
+ log_err("Error: DSI PHY reference clock is disabled\n");
+ return -EINVAL;
+ }
+
+ fout = samsung_dsim_pll_find_pms(dsim_host, fin, bit_clk, &p, &m, &s);
+ if (!fout) {
+ log_err("failed to find PLL PMS for requested frequency\n");
+ return -EINVAL;
+ }
+ dsim_host->pms = PLLCTRL_SET_P(p) | PLLCTRL_SET_M(m) |
+ PLLCTRL_SET_S(s);
+ dsim_host->hs_clock = fout;
+
+ debug("%s: bitclk %llu pixclk %llu pms 0x%x\n", __func__,
+ dsim_host->bit_clk, dsim_host->pix_clk, dsim_host->pms);
+
+ return 0;
+}
+
+static int samsung_dsi_bridge_prepare(struct samsung_dsi *dsim_host)
+{
+ int ret;
+
+ /* At this moment, the dsim bridge's preceding encoder has
+ * already been enabled. So the dsim can be configed here
+ */
+
+ /* config main display mode */
+ samsung_dsi_set_main_mode(dsim_host);
+
+ /* config dsim dpi */
+ samsung_dsi_config_dpi(dsim_host);
+
+ /* config dsim pll */
+ ret = samsung_dsi_config_pll(dsim_host);
+ if (ret) {
+ log_err("dsim pll config failed: %d\n", ret);
+ return ret;
+ }
+
+ /* config dphy timings */
+ samsung_dsi_config_dphy(dsim_host);
+
+ samsung_dsi_init_fifo_pointers(dsim_host);
+
+ /* config esc clock, byte clock and etc */
+ samsung_dsi_config_clkctrl(dsim_host);
+
+ return 0;
+}
+
+static int samsung_dsi_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *device)
+{
+ struct samsung_dsi *dsi = host_to_dsi(host);
+
+ if (!device->lanes || device->lanes > dsi->max_data_lanes) {
+ log_err("invalid data lanes number\n");
+ return -EINVAL;
+ }
+
+ if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO) ||
+ !((device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) ||
+ (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))) {
+ log_err("unsupported dsi mode\n");
+ return -EINVAL;
+ }
+
+ if (device->format != MIPI_DSI_FMT_RGB888 &&
+ device->format != MIPI_DSI_FMT_RGB565 &&
+ device->format != MIPI_DSI_FMT_RGB666 &&
+ device->format != MIPI_DSI_FMT_RGB666_PACKED) {
+ log_err("unsupported pixel format: %#x\n", device->format);
+ return -EINVAL;
+ }
+
+ dsi->lanes = device->lanes;
+ dsi->channel = device->channel;
+ dsi->format = device->format;
+ dsi->mode_flags = device->mode_flags;
+
+ debug("lanes %u, channel %u, format 0x%x, mode_flags 0x%lx\n", dsi->lanes,
+ dsi->channel, dsi->format, dsi->mode_flags);
+
+ samsung_dsi_bridge_clk_set(dsi);
+ samsung_dsi_bridge_prepare(dsi);
+
+ return 0;
+}
+
+static ssize_t samsung_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct samsung_dsi *dsim = host_to_dsi(host);
+ int ret, nb_bytes;
+ bool use_lpm;
+ struct mipi_dsi_packet packet;
+
+ ret = mipi_dsi_create_packet(&packet, msg);
+ if (ret) {
+ dev_err(dsim->device->dev, "failed to create dsi packet: %d\n", ret);
+ return ret;
+ }
+
+ /* config LPM for CMD TX */
+ use_lpm = msg->flags & MIPI_DSI_MSG_USE_LPM ? true : false;
+ samsung_dsi_config_cmd_lpm(dsim, use_lpm);
+
+ if (packet.payload_length) { /* Long Packet case */
+ /* write packet payload */
+ samsung_dsim_write_pl_to_sfr_fifo(dsim, packet.payload,
+ packet.payload_length);
+
+ /* write packet header */
+ samsung_dsim_write_ph_to_sfr_fifo(dsim, packet.header, use_lpm);
+
+ ret = samsung_dsi_wait_for_pkt_done(dsim, MIPI_FIFO_TIMEOUT);
+ if (ret) {
+ dev_err(dsim->device->dev, "wait tx done timeout!\n");
+ return -EBUSY;
+ }
+ } else {
+ /* write packet header */
+ samsung_dsim_write_ph_to_sfr_fifo(dsim, packet.header, use_lpm);
+
+ ret = samsung_dsi_wait_for_hdr_done(dsim, MIPI_FIFO_TIMEOUT);
+ if (ret) {
+ dev_err(dsim->device->dev, "wait pkthdr tx done time out\n");
+ return -EBUSY;
+ }
+ }
+
+ /* read packet payload */
+ if (unlikely(msg->rx_buf)) {
+ ret = samsung_dsi_wait_for_rx_done(dsim, MIPI_FIFO_TIMEOUT);
+ if (ret) {
+ dev_err(dsim->device->dev, "wait rx done time out\n");
+ return -EBUSY;
+ }
+
+ ret = samsung_dsim_read_pl_from_sfr_fifo(dsim, msg->rx_buf,
+ msg->rx_len);
+ 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 samsung_dsi_host_ops = {
+ .attach = samsung_dsi_host_attach,
+ .transfer = samsung_dsi_host_transfer,
+};
+
+static int samsung_dsi_init(struct udevice *dev,
+ struct mipi_dsi_device *device,
+ struct display_timing *timings,
+ unsigned int max_data_lanes,
+ const struct mipi_dsi_phy_ops *phy_ops)
+{
+ struct samsung_dsi *dsi = dev_get_priv(dev);
+ struct udevice *dsi_bridge = device->dev;
+ enum samsung_dsim_type hw_type = (enum samsung_dsim_type)dev_get_driver_data(dsi_bridge);
+
+ dsi->max_data_lanes = max_data_lanes;
+ dsi->device = device;
+ dsi->dsi_host.ops = &samsung_dsi_host_ops;
+ dsi->driver_data = samsung_dsim_types[hw_type];
+ device->host = &dsi->dsi_host;
+
+ dsi->reg_base = (void *)dev_read_addr(device->dev);
+ if ((fdt_addr_t)dsi->reg_base == FDT_ADDR_T_NONE) {
+ dev_err(device->dev, "dsi dt register address error\n");
+ return -EINVAL;
+ }
+
+ dsi->timings = *timings;
+
+ return 0;
+}
+
+static int samsung_dsi_enable(struct udevice *dev)
+{
+ struct samsung_dsi *dsim_host = dev_get_priv(dev);
+
+ /* enable data transfer of dsim */
+ samsung_dsi_set_standby(dsim_host, true);
+
+ return 0;
+}
+
+static int samsung_dsi_disable(struct udevice *dev)
+{
+ u32 intsrc;
+ struct samsung_dsi *dsim_host = dev_get_priv(dev);
+
+ /* disable data transfer of dsim */
+ samsung_dsi_set_standby(dsim_host, false);
+
+ /* disable esc clock & byte clock & dsim pll */
+ samsung_dsi_disable_clock(dsim_host);
+
+ /* Clear all intsrc */
+ intsrc = samsung_dsim_read(dsim_host, DSIM_INTSRC_REG);
+ samsung_dsim_write(dsim_host, DSIM_INTSRC_REG, intsrc);
+
+ return 0;
+}
+
+struct dsi_host_ops samsung_dsi_ops = {
+ .init = samsung_dsi_init,
+ .enable = samsung_dsi_enable,
+ .disable = samsung_dsi_disable,
+};
+
+static int samsung_dsi_probe(struct udevice *dev)
+{
+ struct samsung_dsi *dsim_host = dev_get_priv(dev);
+ int ret;
+
+ ret = clk_get_by_name(dev, "sclk_mipi", &dsim_host->sclk_mipi);
+ if (ret)
+ debug("Failed to get sclk_mipi clock\n");
+
+ return ret;
+}
+
+static const struct udevice_id samsung_dsi_ids[] = {
+ { .compatible = "samsung,sec-mipi-dsi" },
+ { }
+};
+
+U_BOOT_DRIVER(samsung_dsi) = {
+ .name = "samsung_dsi",
+ .id = UCLASS_DSI_HOST,
+ .of_match = samsung_dsi_ids,
+ .probe = samsung_dsi_probe,
+ .remove = samsung_dsi_disable,
+ .ops = &samsung_dsi_ops,
+ .priv_auto = sizeof(struct samsung_dsi),
+};
diff --git a/drivers/video/bridge/samsung-dsim.c b/drivers/video/bridge/samsung-dsim.c
new file mode 100644
index 000000000000..986f1d830844
--- /dev/null
+++ b/drivers/video/bridge/samsung-dsim.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 Amarula Solutions
+ * Copyright 2019 NXP
+ *
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dsi_host.h>
+#include <mipi_dsi.h>
+#include <panel.h>
+#include <video.h>
+#include <video_bridge.h>
+#include <video_link.h>
+#include <asm/io.h>
+#include <asm/arch/gpio.h>
+#include <dm/device-internal.h>
+#include <linux/iopoll.h>
+#include <linux/err.h>
+#include <power/regulator.h>
+#include "samsung-dsim.h"
+
+struct samsung_dsim_priv {
+ struct mipi_dsi_device device;
+ void __iomem *base;
+ struct udevice *panel;
+ struct udevice *dsi_host;
+};
+
+static int samsung_dsim_attach(struct udevice *dev)
+{
+ struct samsung_dsim_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ struct mipi_dsi_panel_plat *mplat;
+ struct display_timing timings;
+ int ret;
+
+ priv->panel = video_link_get_next_device(dev);
+ if (!priv->panel || device_get_uclass_id(priv->panel) != UCLASS_PANEL) {
+ dev_err(dev, "get panel device error\n");
+ return -ENODEV;
+ }
+
+ mplat = dev_get_plat(priv->panel);
+ mplat->device = &priv->device;
+
+ ret = video_link_get_display_timings(&timings);
+ if (ret) {
+ dev_err(dev, "decode display timing error %d\n", ret);
+ return ret;
+ }
+
+ ret = uclass_get_device(UCLASS_DSI_HOST, 0, &priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "No video dsi host detected %d\n", ret);
+ return ret;
+ }
+
+ /* allow to use the compatible */
+ device->dev = dev;
+ ret = dsi_host_init(priv->dsi_host, device, &timings, 4,
+ NULL);
+ if (ret) {
+ dev_err(dev, "failed to initialize mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int samsung_dsim_set_backlight(struct udevice *dev, int percent)
+{
+ struct samsung_dsim_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = panel_enable_backlight(priv->panel);
+ if (ret) {
+ dev_err(dev, "panel %s enable backlight error %d\n",
+ priv->panel->name, ret);
+ return ret;
+ }
+
+ ret = dsi_host_enable(priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "failed to enable mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int samsung_dsim_probe(struct udevice *dev)
+{
+ struct samsung_dsim_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+
+ device->dev = dev;
+
+ return 0;
+}
+
+static int samsung_dsim_remove(struct udevice *dev)
+{
+ struct samsung_dsim_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (priv->panel)
+ device_remove(priv->panel, DM_REMOVE_NORMAL);
+
+ ret = dsi_host_disable(priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "failed to enable mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int samsung_dsim_check_timing(struct udevice *dev, struct display_timing *timings)
+{
+ timings->flags &= ~DISPLAY_FLAGS_DE_HIGH;
+ return 0;
+}
+
+struct video_bridge_ops samsung_dsim_ops = {
+ .attach = samsung_dsim_attach,
+ .set_backlight = samsung_dsim_set_backlight,
+ .check_timing = samsung_dsim_check_timing,
+};
+
+static const struct udevice_id samsung_dsim_ids[] = {
+ { .compatible = "fsl,imx8mm-mipi-dsim", .data = DSIM_TYPE_IMX8MM },
+ { .compatible = "fsl,imx8mn-mipi-dsim", .data = DSIM_TYPE_IMX8MM },
+ { }
+};
+
+U_BOOT_DRIVER(samsung_dsim) = {
+ .name = "samsung_dsim",
+ .id = UCLASS_VIDEO_BRIDGE,
+ .of_match = samsung_dsim_ids,
+ .bind = dm_scan_fdt_dev,
+ .remove = samsung_dsim_remove,
+ .probe = samsung_dsim_probe,
+ .ops = &samsung_dsim_ops,
+ .priv_auto = sizeof(struct samsung_dsim_priv),
+};
diff --git a/drivers/video/bridge/samsung-dsim.h b/drivers/video/bridge/samsung-dsim.h
new file mode 100644
index 000000000000..9bb2a379589b
--- /dev/null
+++ b/drivers/video/bridge/samsung-dsim.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2024 Amarula Solutions
+ */
+
+#ifndef SAMSUNG_DSIM_H
+#define SAMSUNG_DSIM_H
+
+enum samsung_dsim_type {
+ DSIM_TYPE_EXYNOS3250,
+ DSIM_TYPE_EXYNOS4210,
+ DSIM_TYPE_EXYNOS5410,
+ DSIM_TYPE_EXYNOS5422,
+ DSIM_TYPE_EXYNOS5433,
+ DSIM_TYPE_IMX8MM,
+ DSIM_TYPE_IMX8MP,
+ DSIM_TYPE_COUNT,
+};
+
+#endif
--
2.43.0