SN65DSI83: SN65DSI83 驱动支持问题

Part Number: SN65DSI83
Other Parts Discussed in Thread: SN65DSI84, SN65DSI85

非常感谢支持,如下是问题和背景(第3点)详细描述

1、验证目标:

希望通过IMX8MM 初始化SN65DSI83后实现其DSI to LVDS 的功能来点亮屏幕,主要是验证转接功能,即便是屏幕不亮,如果可以验证SN65DSI83转接功能正常也可以达到验证目标。

2、问题描述

问题1: SN65DSI83 驱动报错如下log,尝试修改过lcd参数,没有任何效果,即便是屏幕不亮,有没有debug验证方法来验证 SN65DSI83转接功能是正常的?比如读写SN65DSI83哪个寄存器?或者测量哪些信号

root@imx8mm-lpddr4-evk:~# dmesg | grep -Ei "Debug|mipi|i2c|drm|sdi|sn65|failed"
[    0.098335] platform 32e00000.lcdif: Fixed dependency cycle(s) with /soc@0/bus@32c00000/mipi_dsi@32e10000
[    0.098559] platform 32e00000.lcdif: Fixed dependency cycle(s) with /soc@0/bus@32c00000/mipi_dsi@32e10000
[    0.098674] platform 32e10000.mipi_dsi: Fixed dependency cycle(s) with /soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c
[    0.098702] platform 32e10000.mipi_dsi: Fixed dependency cycle(s) with /soc@0/bus@32c00000/lcdif@32e00000
[    0.099310] platform 32e40000.usb: Fixed dependency cycle(s) with /soc@0/bus@30800000/i2c@30a30000/tcpc@50/connector
[    0.103172] platform panel@0: Fixed dependency cycle(s) with /soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c
[    0.246847] imx-drm display-subsystem: bound imx-lcdif-crtc.0 (ops lcdif_crtc_ops)
[    0.279313] i2c_dev: i2c /dev entries driver
[    0.363694] i2c i2c-0: using DT '/soc@0/bus@30800000/i2c@30a20000' for 'scl' GPIO lookup
[    0.363717] of_get_named_gpiod_flags: can't parse 'scl-gpios' property of node '/soc@0/bus@30800000/i2c@30a20000[0]'
[    0.363740] of_get_named_gpiod_flags: can't parse 'scl-gpio' property of node '/soc@0/bus@30800000/i2c@30a20000[0]'
[    0.363757] i2c i2c-0: using lookup tables for GPIO lookup
[    0.363764] i2c i2c-0: No GPIO consumer scl found
[    0.363783] i2c i2c-0: using DT '/soc@0/bus@30800000/i2c@30a20000' for 'sda' GPIO lookup
[    0.363804] of_get_named_gpiod_flags: can't parse 'sda-gpios' property of node '/soc@0/bus@30800000/i2c@30a20000[0]'
[    0.363825] of_get_named_gpiod_flags: can't parse 'sda-gpio' property of node '/soc@0/bus@30800000/i2c@30a20000[0]'
[    0.363841] i2c i2c-0: using lookup tables for GPIO lookup
[    0.363848] i2c i2c-0: No GPIO consumer sda found
[    0.364245] i2c i2c-0: IMX I2C adapter registered
[    0.365239] i2c i2c-1: using DT '/soc@0/bus@30800000/i2c@30a30000' for 'scl' GPIO lookup
[    0.365274] of_get_named_gpiod_flags: can't parse 'scl-gpios' property of node '/soc@0/bus@30800000/i2c@30a30000[0]'
[    0.365298] of_get_named_gpiod_flags: can't parse 'scl-gpio' property of node '/soc@0/bus@30800000/i2c@30a30000[0]'
[    0.365317] i2c i2c-1: using lookup tables for GPIO lookup
[    0.365324] i2c i2c-1: No GPIO consumer scl found
[    0.365342] i2c i2c-1: using DT '/soc@0/bus@30800000/i2c@30a30000' for 'sda' GPIO lookup
[    0.365363] of_get_named_gpiod_flags: can't parse 'sda-gpios' property of node '/soc@0/bus@30800000/i2c@30a30000[0]'
[    0.365383] of_get_named_gpiod_flags: can't parse 'sda-gpio' property of node '/soc@0/bus@30800000/i2c@30a30000[0]'
[    0.365397] i2c i2c-1: using lookup tables for GPIO lookup
[    0.365403] i2c i2c-1: No GPIO consumer sda found
[    0.368364] i2c i2c-1: IMX I2C adapter registered
[    0.369351] i2c i2c-2: using DT '/soc@0/bus@30800000/i2c@30a40000' for 'scl' GPIO lookup
[    0.369385] of_get_named_gpiod_flags: can't parse 'scl-gpios' property of node '/soc@0/bus@30800000/i2c@30a40000[0]'
[    0.369406] of_get_named_gpiod_flags: can't parse 'scl-gpio' property of node '/soc@0/bus@30800000/i2c@30a40000[0]'
[    0.369421] i2c i2c-2: using lookup tables for GPIO lookup
[    0.369428] i2c i2c-2: No GPIO consumer scl found
[    0.369447] i2c i2c-2: using DT '/soc@0/bus@30800000/i2c@30a40000' for 'sda' GPIO lookup
[    0.369467] of_get_named_gpiod_flags: can't parse 'sda-gpios' property of node '/soc@0/bus@30800000/i2c@30a40000[0]'
[    0.369489] of_get_named_gpiod_flags: can't parse 'sda-gpio' property of node '/soc@0/bus@30800000/i2c@30a40000[0]'
[    0.369507] i2c i2c-2: using lookup tables for GPIO lookup
[    0.369515] i2c i2c-2: No GPIO consumer sda found
[    0.369830] platform panel@0: Fixed dependency cycle(s) with /soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c
[    0.369954] i2c 2-002c: Fixed dependency cycle(s) with /panel@0
[    0.370259] sn65dsi83 2-002c: Debug: DT matched - compatible=ti,sn65dsi83, model=0
[    0.370270] sn65dsi83 2-002c: using DT '/soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c' for 'enable' GPIO lookup
[    0.370309] of_get_named_gpiod_flags: parsed 'enable-gpios' property of node '/soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c[0]' - status (0)
[    0.370357] sn65dsi83 2-002c: Debug: Enable GPIO acquired successfully
[    0.377976] nxp-pca9450 0-0025: using DT '/soc@0/bus@30800000/i2c@30a20000/pca9450@25' for 'sd-vsel' GPIO lookup
[    0.378009] of_get_named_gpiod_flags: can't parse 'sd-vsel-gpios' property of node '/soc@0/bus@30800000/i2c@30a20000/pca9450@25[0]'
[    0.378036] of_get_named_gpiod_flags: can't parse 'sd-vsel-gpio' property of node '/soc@0/bus@30800000/i2c@30a20000/pca9450@25[0]'
[    0.381381] sn65dsi83 2-002c: Debug: Parsing device tree for model 0
[    0.381408] sn65dsi83 2-002c: Debug: Panel bridge (port 2) not ready, deferring probe (ret=-517)
[    0.381416] sn65dsi83 2-002c: Debug: Failed to parse device tree, ret=-517
[    0.388532] i2c i2c-2: IMX I2C adapter registered
[    0.389586] i2c i2c-3: using DT '/soc@0/bus@30800000/i2c@30a50000' for 'scl' GPIO lookup
[    0.389615] of_get_named_gpiod_flags: can't parse 'scl-gpios' property of node '/soc@0/bus@30800000/i2c@30a50000[0]'
[    0.389635] of_get_named_gpiod_flags: can't parse 'scl-gpio' property of node '/soc@0/bus@30800000/i2c@30a50000[0]'
[    0.389651] i2c i2c-3: using lookup tables for GPIO lookup
[    0.389657] i2c i2c-3: No GPIO consumer scl found
[    0.389675] i2c i2c-3: using DT '/soc@0/bus@30800000/i2c@30a50000' for 'sda' GPIO lookup
[    0.389693] of_get_named_gpiod_flags: can't parse 'sda-gpios' property of node '/soc@0/bus@30800000/i2c@30a50000[0]'
[    0.389711] of_get_named_gpiod_flags: can't parse 'sda-gpio' property of node '/soc@0/bus@30800000/i2c@30a50000[0]'
[    0.389725] i2c i2c-3: using lookup tables for GPIO lookup
[    0.389731] i2c i2c-3: No GPIO consumer sda found
[    0.390166] i2c i2c-3: IMX I2C adapter registered
[    0.394183] imx-drm display-subsystem: bound imx-lcdif-crtc.0 (ops lcdif_crtc_ops)
[    0.518539] galcore: clk_get vg clock failed, disable vg!
[    0.560767] [drm] Initialized vivante 1.0.0 for 38000000.gpu on minor 0
[    0.571441] sn65dsi83 2-002c: Debug: DT matched - compatible=ti,sn65dsi83, model=0
[    0.571463] sn65dsi83 2-002c: using DT '/soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c' for 'enable' GPIO lookup
[    0.571496] of_get_named_gpiod_flags: parsed 'enable-gpios' property of node '/soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c[0]' - status (0)
[    0.571537] sn65dsi83 2-002c: Debug: Enable GPIO acquired successfully
[    0.582560] sn65dsi83 2-002c: Debug: Parsing device tree for model 0
[    0.582593] sn65dsi83 2-002c: Debug: Panel bridge (port 2) not ready, deferring probe (ret=-517)
[    0.582603] sn65dsi83 2-002c: Debug: Failed to parse device tree, ret=-517
[    0.592182] imx-drm display-subsystem: bound imx-lcdif-crtc.0 (ops lcdif_crtc_ops)
[    0.593326] sn65dsi83 2-002c: Debug: DT matched - compatible=ti,sn65dsi83, model=0
[    0.593349] sn65dsi83 2-002c: using DT '/soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c' for 'enable' GPIO lookup
[    0.593393] of_get_named_gpiod_flags: parsed 'enable-gpios' property of node '/soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c[0]' - status (0)
[    0.593443] sn65dsi83 2-002c: Debug: Enable GPIO acquired successfully
[    0.604319] sn65dsi83 2-002c: Debug: Parsing device tree for model 0
[    0.604347] sn65dsi83 2-002c: Debug: Panel bridge (port 2) not ready, deferring probe (ret=-517)
[    0.604355] sn65dsi83 2-002c: Debug: Failed to parse device tree, ret=-517
[    0.610644] mmc0: new ultra high speed SDR104 SDIO card at address 0001
[    0.613636] imx-drm display-subsystem: bound imx-lcdif-crtc.0 (ops lcdif_crtc_ops)
[    1.647980] systemd[1]: Mounting Kernel Debug File System...
[    1.719821] systemd[1]: Starting Load Kernel Module drm...
[    1.885080] systemd[1]: Mounted Kernel Debug File System.
[    1.933444] systemd[1]: modprobe@drm.service: Deactivated successfully.
[    1.934051] systemd[1]: Finished Load Kernel Module drm.
[   10.725256] sn65dsi83 2-002c: Debug: DT matched - compatible=ti,sn65dsi83, model=0
[   10.725282] sn65dsi83 2-002c: using DT '/soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c' for 'enable' GPIO lookup
[   10.725327] of_get_named_gpiod_flags: parsed 'enable-gpios' property of node '/soc@0/bus@30800000/i2c@30a40000/sn65dsi83@2c[0]' - status (0)
[   10.725380] sn65dsi83 2-002c: Debug: Enable GPIO acquired successfully
[   10.736713] sn65dsi83 2-002c: Debug: Parsing device tree for model 0
[   10.736755] sn65dsi83 2-002c: Debug: Panel bridge (port 2) not ready, deferring probe (ret=-517)
[   10.736764] sn65dsi83 2-002c: Debug: Failed to parse device tree, ret=-517
[   10.748066] imx-drm display-subsystem: bound imx-lcdif-crtc.0 (ops lcdif_crtc_ops)
[   10.748967] i2c 2-002c: deferred probe pending: (reason unknown)
root@imx8mm-lpddr4-evk:~# i2cdetect -l
i2c-0   i2c             30a20000.i2c                            I2C adapter
i2c-1   i2c             30a30000.i2c                            I2C adapter
i2c-2   i2c             30a40000.i2c                            I2C adapter
i2c-3   i2c             30a50000.i2c                            I2C adapter
root@imx8mm-lpddr4-evk:~# i2cdetect -y 2
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- 2c -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
root@imx8mm-lpddr4-evk:~#
 

问题2 有没有原厂屏幕的购买信息?

想要购买SN65DSI83 转接板的lcd屏幕来验证(排除lcd 屏的差异), 能否提供购买方法?

 

问题3   后续量产硬件连接 ,SN65DSI83 外接的不是屏幕是FPGA 的lvds 接口

 

SN65DSI83 现有驱动是否支持这种FPGA的连接方式?

 

3、如下是软硬件环境信息

3.1 硬件环境:

IMX8MM 开发板 MIPI DSI output 接 电平转换后再接TiSN65DSI83官方实验板再飞线接BOE

Imx8侧确认硬件没有问题,换个DSI转HDMI的转接板接显示器可以出图,BOE屏单独测试可以点亮 ,如下连接设置驱动报错

环境连接拓扑如下

 2474b368-d340-47af-88cd-08fd5f475aeb.png

没有找到和SN65DSI83 对应的原厂屏幕,所以采用的飞线的方式接BOE的屏进行测试

 

3.2、软件环境

NXP yocto 编译,LINUX 内核版本6.12

设备树配置如下

image.png

 

image.png

image.png

  • 已经收到了您的案例,调查需要些时间,感谢您的耐心等待。

  • 非常感谢支持:

    问题1 补充:使用的驱动代码文件是kernel-source\drivers\gpu\drm\bridge\ti-sn65dsi83.c    linux 内核版本6.12, 在源代码基础上仅仅加了些debug打印,654行会出问题1 的log

    // SPDX-License-Identifier: GPL-2.0
    /*
     * TI SN65DSI83,84,85 driver
     *
     * Currently supported:
     * - SN65DSI83
     *   = 1x Single-link DSI ~ 1x Single-link LVDS
     *   - Supported
     *   - Single-link LVDS mode tested
     * - SN65DSI84
     *   = 1x Single-link DSI ~ 2x Single-link or 1x Dual-link LVDS
     *   - Supported
     *   - Dual-link LVDS mode tested
     *   - 2x Single-link LVDS mode unsupported
     *     (should be easy to add by someone who has the HW)
     * - SN65DSI85
     *   = 2x Single-link or 1x Dual-link DSI ~ 2x Single-link or 1x Dual-link LVDS
     *   - Unsupported
     *     (should be easy to add by someone who has the HW)
     *
     * Copyright (C) 2021 Marek Vasut <marex@denx.de>
     *
     * Based on previous work of:
     * Valentin Raevsky <valentin@compulab.co.il>
     * Philippe Schenker <philippe.schenker@toradex.com>
     */
    
    #include <linux/bits.h>
    #include <linux/clk.h>
    #include <linux/gpio/consumer.h>
    #include <linux/i2c.h>
    #include <linux/media-bus-format.h>
    #include <linux/module.h>
    #include <linux/of.h>
    #include <linux/of_graph.h>
    #include <linux/of_device.h>  // Added for of_match_device()
    #include <linux/regmap.h>
    #include <linux/regulator/consumer.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_panel.h>
    #include <drm/drm_print.h>
    #include <drm/drm_probe_helper.h>
    
    /* ID registers */
    #define REG_ID(n)				(0x00 + (n))
    /* Reset and clock registers */
    #define REG_RC_RESET				0x09
    #define  REG_RC_RESET_SOFT_RESET		BIT(0)
    #define REG_RC_LVDS_PLL				0x0a
    #define  REG_RC_LVDS_PLL_PLL_EN_STAT		BIT(7)
    #define  REG_RC_LVDS_PLL_LVDS_CLK_RANGE(n)	(((n) & 0x7) << 1)
    #define  REG_RC_LVDS_PLL_HS_CLK_SRC_DPHY	BIT(0)
    #define REG_RC_DSI_CLK				0x0b
    #define  REG_RC_DSI_CLK_DSI_CLK_DIVIDER(n)	(((n) & 0x1f) << 3)
    #define  REG_RC_DSI_CLK_REFCLK_MULTIPLIER(n)	((n) & 0x3)
    #define REG_RC_PLL_EN				0x0d
    #define  REG_RC_PLL_EN_PLL_EN			BIT(0)
    /* DSI registers */
    #define REG_DSI_LANE				0x10
    #define  REG_DSI_LANE_LEFT_RIGHT_PIXELS		BIT(7)	/* DSI85-only */
    #define  REG_DSI_LANE_DSI_CHANNEL_MODE_DUAL	0	/* DSI85-only */
    #define  REG_DSI_LANE_DSI_CHANNEL_MODE_2SINGLE	BIT(6)	/* DSI85-only */
    #define  REG_DSI_LANE_DSI_CHANNEL_MODE_SINGLE	BIT(5)
    #define  REG_DSI_LANE_CHA_DSI_LANES(n)		(((n) & 0x3) << 3)
    #define  REG_DSI_LANE_CHB_DSI_LANES(n)		(((n) & 0x3) << 1)
    #define  REG_DSI_LANE_SOT_ERR_TOL_DIS		BIT(0)
    #define REG_DSI_EQ				0x11
    #define  REG_DSI_EQ_CHA_DSI_DATA_EQ(n)		(((n) & 0x3) << 6)
    #define  REG_DSI_EQ_CHA_DSI_CLK_EQ(n)		(((n) & 0x3) << 2)
    #define REG_DSI_CLK				0x12
    #define  REG_DSI_CLK_CHA_DSI_CLK_RANGE(n)	((n) & 0xff)
    /* LVDS registers */
    #define REG_LVDS_FMT				0x18
    #define  REG_LVDS_FMT_DE_NEG_POLARITY		BIT(7)
    #define  REG_LVDS_FMT_HS_NEG_POLARITY		BIT(6)
    #define  REG_LVDS_FMT_VS_NEG_POLARITY		BIT(5)
    #define  REG_LVDS_FMT_LVDS_LINK_CFG		BIT(4)	/* 0:AB 1:A-only */
    #define  REG_LVDS_FMT_CHA_24BPP_MODE		BIT(3)
    #define  REG_LVDS_FMT_CHB_24BPP_MODE		BIT(2)
    #define  REG_LVDS_FMT_CHA_24BPP_FORMAT1		BIT(1)
    #define  REG_LVDS_FMT_CHB_24BPP_FORMAT1		BIT(0)
    #define REG_LVDS_VCOM				0x19
    #define  REG_LVDS_VCOM_CHA_LVDS_VOCM		BIT(6)
    #define  REG_LVDS_VCOM_CHB_LVDS_VOCM		BIT(4)
    #define  REG_LVDS_VCOM_CHA_LVDS_VOD_SWING(n)	(((n) & 0x3) << 2)
    #define  REG_LVDS_VCOM_CHB_LVDS_VOD_SWING(n)	((n) & 0x3)
    #define REG_LVDS_LANE				0x1a
    #define  REG_LVDS_LANE_EVEN_ODD_SWAP		BIT(6)
    #define  REG_LVDS_LANE_CHA_REVERSE_LVDS		BIT(5)
    #define  REG_LVDS_LANE_CHB_REVERSE_LVDS		BIT(4)
    #define  REG_LVDS_LANE_CHA_LVDS_TERM		BIT(1)
    #define  REG_LVDS_LANE_CHB_LVDS_TERM		BIT(0)
    #define REG_LVDS_CM				0x1b
    #define  REG_LVDS_CM_CHA_LVDS_CM_ADJUST(n)	(((n) & 0x3) << 4)
    #define  REG_LVDS_CM_CHB_LVDS_CM_ADJUST(n)	((n) & 0x3)
    /* Video registers */
    #define REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW	0x20
    #define REG_VID_CHA_ACTIVE_LINE_LENGTH_HIGH	0x21
    #define REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW	0x24
    #define REG_VID_CHA_VERTICAL_DISPLAY_SIZE_HIGH	0x25
    #define REG_VID_CHA_SYNC_DELAY_LOW		0x28
    #define REG_VID_CHA_SYNC_DELAY_HIGH		0x29
    #define REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW	0x2c
    #define REG_VID_CHA_HSYNC_PULSE_WIDTH_HIGH	0x2d
    #define REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW	0x30
    #define REG_VID_CHA_VSYNC_PULSE_WIDTH_HIGH	0x31
    #define REG_VID_CHA_HORIZONTAL_BACK_PORCH	0x34
    #define REG_VID_CHA_VERTICAL_BACK_PORCH		0x36
    #define REG_VID_CHA_HORIZONTAL_FRONT_PORCH	0x38
    #define REG_VID_CHA_VERTICAL_FRONT_PORCH	0x3a
    #define REG_VID_CHA_TEST_PATTERN		0x3c
    /* IRQ registers */
    #define REG_IRQ_GLOBAL				0xe0
    #define  REG_IRQ_GLOBAL_IRQ_EN			BIT(0)
    #define REG_IRQ_EN				0xe1
    #define  REG_IRQ_EN_CHA_SYNCH_ERR_EN		BIT(7)
    #define  REG_IRQ_EN_CHA_CRC_ERR_EN		BIT(6)
    #define  REG_IRQ_EN_CHA_UNC_ECC_ERR_EN		BIT(5)
    #define  REG_IRQ_EN_CHA_COR_ECC_ERR_EN		BIT(4)
    #define  REG_IRQ_EN_CHA_LLP_ERR_EN		BIT(3)
    #define  REG_IRQ_EN_CHA_SOT_BIT_ERR_EN		BIT(2)
    #define  REG_IRQ_EN_CHA_PLL_UNLOCK_EN		BIT(0)
    #define REG_IRQ_STAT				0xe5
    #define  REG_IRQ_STAT_CHA_SYNCH_ERR		BIT(7)
    #define  REG_IRQ_STAT_CHA_CRC_ERR		BIT(6)
    #define  REG_IRQ_STAT_CHA_UNC_ECC_ERR		BIT(5)
    #define  REG_IRQ_STAT_CHA_COR_ECC_ERR		BIT(4)
    #define  REG_IRQ_STAT_CHA_LLP_ERR		BIT(3)
    #define  REG_IRQ_STAT_CHA_SOT_BIT_ERR		BIT(2)
    #define  REG_IRQ_STAT_CHA_PLL_UNLOCK		BIT(0)
    
    enum sn65dsi83_model {
    	MODEL_SN65DSI83,
    	MODEL_SN65DSI84,
    };
    
    struct sn65dsi83 {
    	struct drm_bridge		bridge;
    	struct device			*dev;
    	struct regmap			*regmap;
    	struct mipi_dsi_device		*dsi;
    	struct drm_bridge		*panel_bridge;
    	struct gpio_desc		*enable_gpio;
    	struct regulator		*vcc;
    	bool				lvds_dual_link;
    	bool				lvds_dual_link_even_odd_swap;
    };
    
    static const struct regmap_range sn65dsi83_readable_ranges[] = {
    	regmap_reg_range(REG_ID(0), REG_ID(8)),
    	regmap_reg_range(REG_RC_LVDS_PLL, REG_RC_DSI_CLK),
    	regmap_reg_range(REG_RC_PLL_EN, REG_RC_PLL_EN),
    	regmap_reg_range(REG_DSI_LANE, REG_DSI_CLK),
    	regmap_reg_range(REG_LVDS_FMT, REG_LVDS_CM),
    	regmap_reg_range(REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW,
    			 REG_VID_CHA_ACTIVE_LINE_LENGTH_HIGH),
    	regmap_reg_range(REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW,
    			 REG_VID_CHA_VERTICAL_DISPLAY_SIZE_HIGH),
    	regmap_reg_range(REG_VID_CHA_SYNC_DELAY_LOW,
    			 REG_VID_CHA_SYNC_DELAY_HIGH),
    	regmap_reg_range(REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW,
    			 REG_VID_CHA_HSYNC_PULSE_WIDTH_HIGH),
    	regmap_reg_range(REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW,
    			 REG_VID_CHA_VSYNC_PULSE_WIDTH_HIGH),
    	regmap_reg_range(REG_VID_CHA_HORIZONTAL_BACK_PORCH,
    			 REG_VID_CHA_HORIZONTAL_BACK_PORCH),
    	regmap_reg_range(REG_VID_CHA_VERTICAL_BACK_PORCH,
    			 REG_VID_CHA_VERTICAL_BACK_PORCH),
    	regmap_reg_range(REG_VID_CHA_HORIZONTAL_FRONT_PORCH,
    			 REG_VID_CHA_HORIZONTAL_FRONT_PORCH),
    	regmap_reg_range(REG_VID_CHA_VERTICAL_FRONT_PORCH,
    			 REG_VID_CHA_VERTICAL_FRONT_PORCH),
    	regmap_reg_range(REG_VID_CHA_TEST_PATTERN, REG_VID_CHA_TEST_PATTERN),
    	regmap_reg_range(REG_IRQ_GLOBAL, REG_IRQ_EN),
    	regmap_reg_range(REG_IRQ_STAT, REG_IRQ_STAT),
    };
    
    static const struct regmap_access_table sn65dsi83_readable_table = {
    	.yes_ranges = sn65dsi83_readable_ranges,
    	.n_yes_ranges = ARRAY_SIZE(sn65dsi83_readable_ranges),
    };
    
    static const struct regmap_range sn65dsi83_writeable_ranges[] = {
    	regmap_reg_range(REG_RC_RESET, REG_RC_DSI_CLK),
    	regmap_reg_range(REG_RC_PLL_EN, REG_RC_PLL_EN),
    	regmap_reg_range(REG_DSI_LANE, REG_DSI_CLK),
    	regmap_reg_range(REG_LVDS_FMT, REG_LVDS_CM),
    	regmap_reg_range(REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW,
    			 REG_VID_CHA_ACTIVE_LINE_LENGTH_HIGH),
    	regmap_reg_range(REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW,
    			 REG_VID_CHA_VERTICAL_DISPLAY_SIZE_HIGH),
    	regmap_reg_range(REG_VID_CHA_SYNC_DELAY_LOW,
    			 REG_VID_CHA_SYNC_DELAY_HIGH),
    	regmap_reg_range(REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW,
    			 REG_VID_CHA_HSYNC_PULSE_WIDTH_HIGH),
    	regmap_reg_range(REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW,
    			 REG_VID_CHA_VSYNC_PULSE_WIDTH_HIGH),
    	regmap_reg_range(REG_VID_CHA_HORIZONTAL_BACK_PORCH,
    			 REG_VID_CHA_HORIZONTAL_BACK_PORCH),
    	regmap_reg_range(REG_VID_CHA_VERTICAL_BACK_PORCH,
    			 REG_VID_CHA_VERTICAL_BACK_PORCH),
    	regmap_reg_range(REG_VID_CHA_HORIZONTAL_FRONT_PORCH,
    			 REG_VID_CHA_HORIZONTAL_FRONT_PORCH),
    	regmap_reg_range(REG_VID_CHA_VERTICAL_FRONT_PORCH,
    			 REG_VID_CHA_VERTICAL_FRONT_PORCH),
    	regmap_reg_range(REG_VID_CHA_TEST_PATTERN, REG_VID_CHA_TEST_PATTERN),
    	regmap_reg_range(REG_IRQ_GLOBAL, REG_IRQ_EN),
    	regmap_reg_range(REG_IRQ_STAT, REG_IRQ_STAT),
    };
    
    static const struct regmap_access_table sn65dsi83_writeable_table = {
    	.yes_ranges = sn65dsi83_writeable_ranges,
    	.n_yes_ranges = ARRAY_SIZE(sn65dsi83_writeable_ranges),
    };
    
    static const struct regmap_range sn65dsi83_volatile_ranges[] = {
    	regmap_reg_range(REG_RC_RESET, REG_RC_RESET),
    	regmap_reg_range(REG_RC_LVDS_PLL, REG_RC_LVDS_PLL),
    	regmap_reg_range(REG_IRQ_STAT, REG_IRQ_STAT),
    };
    
    static const struct regmap_access_table sn65dsi83_volatile_table = {
    	.yes_ranges = sn65dsi83_volatile_ranges,
    	.n_yes_ranges = ARRAY_SIZE(sn65dsi83_volatile_ranges),
    };
    
    static const struct regmap_config sn65dsi83_regmap_config = {
    	.reg_bits = 8,
    	.val_bits = 8,
    	.rd_table = &sn65dsi83_readable_table,
    	.wr_table = &sn65dsi83_writeable_table,
    	.volatile_table = &sn65dsi83_volatile_table,
    	.cache_type = REGCACHE_MAPLE,
    	.max_register = REG_IRQ_STAT,
    };
    
    static struct sn65dsi83 *bridge_to_sn65dsi83(struct drm_bridge *bridge)
    {
    	return container_of(bridge, struct sn65dsi83, bridge);
    }
    
    static int sn65dsi83_attach(struct drm_bridge *bridge,
    			    enum drm_bridge_attach_flags flags)
    {
    	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
    
    	return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
    				 &ctx->bridge, flags);
    }
    
    static void sn65dsi83_detach(struct drm_bridge *bridge)
    {
    	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
    
    	if (!ctx->dsi)
    		return;
    
    	ctx->dsi = NULL;
    }
    
    static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx,
    				   const struct drm_display_mode *mode)
    {
    	/*
    	 * The encoding of the LVDS_CLK_RANGE is as follows:
    	 * 000 - 25 MHz <= LVDS_CLK < 37.5 MHz
    	 * 001 - 37.5 MHz <= LVDS_CLK < 62.5 MHz
    	 * 010 - 62.5 MHz <= LVDS_CLK < 87.5 MHz
    	 * 011 - 87.5 MHz <= LVDS_CLK < 112.5 MHz
    	 * 100 - 112.5 MHz <= LVDS_CLK < 137.5 MHz
    	 * 101 - 137.5 MHz <= LVDS_CLK <= 154 MHz
    	 * which is a range of 12.5MHz..162.5MHz in 50MHz steps, except that
    	 * the ends of the ranges are clamped to the supported range. Since
    	 * sn65dsi83_mode_valid() already filters the valid modes and limits
    	 * the clock to 25..154 MHz, the range calculation can be simplified
    	 * as follows:
    	 */
    	int mode_clock = mode->clock;
    
    	if (ctx->lvds_dual_link)
    		mode_clock /= 2;
    
    	return (mode_clock - 12500) / 25000;
    }
    
    static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx,
    				  const struct drm_display_mode *mode)
    {
    	/*
    	 * The encoding of the CHA_DSI_CLK_RANGE is as follows:
    	 * 0x00 through 0x07 - Reserved
    	 * 0x08 - 40 <= DSI_CLK < 45 MHz
    	 * 0x09 - 45 <= DSI_CLK < 50 MHz
    	 * ...
    	 * 0x63 - 495 <= DSI_CLK < 500 MHz
    	 * 0x64 - 500 MHz
    	 * 0x65 through 0xFF - Reserved
    	 * which is DSI clock in 5 MHz steps, clamped to 40..500 MHz.
    	 * The DSI clock are calculated as:
    	 *  DSI_CLK = mode clock * bpp / dsi_data_lanes / 2
    	 * the 2 is there because the bus is DDR.
    	 */
    	return DIV_ROUND_UP(clamp((unsigned int)mode->clock *
    			    mipi_dsi_pixel_format_to_bpp(ctx->dsi->format) /
    			    ctx->dsi->lanes / 2, 40000U, 500000U), 5000U);
    }
    
    static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx)
    {
    	/* The divider is (DSI_CLK / LVDS_CLK) - 1, which really is: */
    	unsigned int dsi_div = mipi_dsi_pixel_format_to_bpp(ctx->dsi->format);
    
    	dsi_div /= ctx->dsi->lanes;
    
    	if (!ctx->lvds_dual_link)
    		dsi_div /= 2;
    
    	return dsi_div - 1;
    }
    
    static void sn65dsi83_atomic_pre_enable(struct drm_bridge *bridge,
    					struct drm_bridge_state *old_bridge_state)
    {
    	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
    	struct drm_atomic_state *state = old_bridge_state->base.state;
    	const struct drm_bridge_state *bridge_state;
    	const struct drm_crtc_state *crtc_state;
    	const struct drm_display_mode *mode;
    	struct drm_connector *connector;
    	struct drm_crtc *crtc;
    	bool lvds_format_24bpp;
    	bool lvds_format_jeida;
    	unsigned int pval;
    	__le16 le16val;
    	u16 val;
    	int ret;
    
    	ret = regulator_enable(ctx->vcc);
    	if (ret) {
    		dev_err(ctx->dev, "Failed to enable vcc: %d\n", ret);
    		return;
    	}
    
    	/* Deassert reset */
    	gpiod_set_value_cansleep(ctx->enable_gpio, 1);
    	usleep_range(10000, 11000);
    
    	/* Get the LVDS format from the bridge state. */
    	bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
    
    	switch (bridge_state->output_bus_cfg.format) {
    	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
    		lvds_format_24bpp = false;
    		lvds_format_jeida = true;
    		break;
    	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
    		lvds_format_24bpp = true;
    		lvds_format_jeida = true;
    		break;
    	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
    		lvds_format_24bpp = true;
    		lvds_format_jeida = false;
    		break;
    	default:
    		/*
    		 * Some bridges still don't set the correct
    		 * LVDS bus pixel format, use SPWG24 default
    		 * format until those are fixed.
    		 */
    		lvds_format_24bpp = true;
    		lvds_format_jeida = false;
    		dev_warn(ctx->dev,
    			 "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
    			 bridge_state->output_bus_cfg.format);
    		break;
    	}
    
    	/*
    	 * Retrieve the CRTC adjusted mode. This requires a little dance to go
    	 * from the bridge to the encoder, to the connector and to the CRTC.
    	 */
    	connector = drm_atomic_get_new_connector_for_encoder(state,
    							     bridge->encoder);
    	crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
    	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
    	mode = &crtc_state->adjusted_mode;
    
    	/* Clear reset, disable PLL */
    	regmap_write(ctx->regmap, REG_RC_RESET, 0x00);
    	regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
    
    	/* Reference clock derived from DSI link clock. */
    	regmap_write(ctx->regmap, REG_RC_LVDS_PLL,
    		     REG_RC_LVDS_PLL_LVDS_CLK_RANGE(sn65dsi83_get_lvds_range(ctx, mode)) |
    		     REG_RC_LVDS_PLL_HS_CLK_SRC_DPHY);
    	regmap_write(ctx->regmap, REG_DSI_CLK,
    		     REG_DSI_CLK_CHA_DSI_CLK_RANGE(sn65dsi83_get_dsi_range(ctx, mode)));
    	regmap_write(ctx->regmap, REG_RC_DSI_CLK,
    		     REG_RC_DSI_CLK_DSI_CLK_DIVIDER(sn65dsi83_get_dsi_div(ctx)));
    
    	/* Set number of DSI lanes and LVDS link config. */
    	regmap_write(ctx->regmap, REG_DSI_LANE,
    		     REG_DSI_LANE_DSI_CHANNEL_MODE_SINGLE |
    		     REG_DSI_LANE_CHA_DSI_LANES(~(ctx->dsi->lanes - 1)) |
    		     /* CHB is DSI85-only, set to default on DSI83/DSI84 */
    		     REG_DSI_LANE_CHB_DSI_LANES(3));
    	/* No equalization. */
    	regmap_write(ctx->regmap, REG_DSI_EQ, 0x00);
    
    	/* Set up sync signal polarity. */
    	val = (mode->flags & DRM_MODE_FLAG_NHSYNC ?
    	       REG_LVDS_FMT_HS_NEG_POLARITY : 0) |
    	      (mode->flags & DRM_MODE_FLAG_NVSYNC ?
    	       REG_LVDS_FMT_VS_NEG_POLARITY : 0);
    
    	/* Set up bits-per-pixel, 18bpp or 24bpp. */
    	if (lvds_format_24bpp) {
    		val |= REG_LVDS_FMT_CHA_24BPP_MODE;
    		if (ctx->lvds_dual_link)
    			val |= REG_LVDS_FMT_CHB_24BPP_MODE;
    	}
    
    	/* Set up LVDS format, JEIDA/Format 1 or SPWG/Format 2 */
    	if (lvds_format_jeida) {
    		val |= REG_LVDS_FMT_CHA_24BPP_FORMAT1;
    		if (ctx->lvds_dual_link)
    			val |= REG_LVDS_FMT_CHB_24BPP_FORMAT1;
    	}
    
    	/* Set up LVDS output config (DSI84,DSI85) */
    	if (!ctx->lvds_dual_link)
    		val |= REG_LVDS_FMT_LVDS_LINK_CFG;
    
    	regmap_write(ctx->regmap, REG_LVDS_FMT, val);
    	regmap_write(ctx->regmap, REG_LVDS_VCOM, 0x05);
    	regmap_write(ctx->regmap, REG_LVDS_LANE,
    		     (ctx->lvds_dual_link_even_odd_swap ?
    		      REG_LVDS_LANE_EVEN_ODD_SWAP : 0) |
    		     REG_LVDS_LANE_CHA_LVDS_TERM |
    		     REG_LVDS_LANE_CHB_LVDS_TERM);
    	regmap_write(ctx->regmap, REG_LVDS_CM, 0x00);
    
    	le16val = cpu_to_le16(mode->hdisplay);
    	regmap_bulk_write(ctx->regmap, REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW,
    			  &le16val, 2);
    	le16val = cpu_to_le16(mode->vdisplay);
    	regmap_bulk_write(ctx->regmap, REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW,
    			  &le16val, 2);
    	/* 32 + 1 pixel clock to ensure proper operation */
    	le16val = cpu_to_le16(32 + 1);
    	regmap_bulk_write(ctx->regmap, REG_VID_CHA_SYNC_DELAY_LOW, &le16val, 2);
    	le16val = cpu_to_le16(mode->hsync_end - mode->hsync_start);
    	regmap_bulk_write(ctx->regmap, REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW,
    			  &le16val, 2);
    	le16val = cpu_to_le16(mode->vsync_end - mode->vsync_start);
    	regmap_bulk_write(ctx->regmap, REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW,
    			  &le16val, 2);
    	regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_BACK_PORCH,
    		     mode->htotal - mode->hsync_end);
    	regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_BACK_PORCH,
    		     mode->vtotal - mode->vsync_end);
    	regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_FRONT_PORCH,
    		     mode->hsync_start - mode->hdisplay);
    	regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_FRONT_PORCH,
    		     mode->vsync_start - mode->vdisplay);
    	regmap_write(ctx->regmap, REG_VID_CHA_TEST_PATTERN, 0x00);
    
    	/* Enable PLL */
    	regmap_write(ctx->regmap, REG_RC_PLL_EN, REG_RC_PLL_EN_PLL_EN);
    	usleep_range(3000, 4000);
    	ret = regmap_read_poll_timeout(ctx->regmap, REG_RC_LVDS_PLL, pval,
    				       pval & REG_RC_LVDS_PLL_PLL_EN_STAT,
    				       1000, 100000);
    	if (ret) {
    		dev_err(ctx->dev, "failed to lock PLL, ret=%i\n", ret);
    		/* On failure, disable PLL again and exit. */
    		regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
    		return;
    	}
    
    	/* Trigger reset after CSR register update. */
    	regmap_write(ctx->regmap, REG_RC_RESET, REG_RC_RESET_SOFT_RESET);
    
    	/* Wait for 10ms after soft reset as specified in datasheet */
    	usleep_range(10000, 12000);
    }
    
    static void sn65dsi83_atomic_enable(struct drm_bridge *bridge,
    				    struct drm_bridge_state *old_bridge_state)
    {
    	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
    	unsigned int pval;
    
    	/* Clear all errors that got asserted during initialization. */
    	regmap_read(ctx->regmap, REG_IRQ_STAT, &pval);
    	regmap_write(ctx->regmap, REG_IRQ_STAT, pval);
    
    	/* Wait for 1ms and check for errors in status register */
    	usleep_range(1000, 1100);
    	regmap_read(ctx->regmap, REG_IRQ_STAT, &pval);
    	if (pval)
    		dev_err(ctx->dev, "Unexpected link status 0x%02x\n", pval);
    }
    
    static void sn65dsi83_atomic_disable(struct drm_bridge *bridge,
    				     struct drm_bridge_state *old_bridge_state)
    {
    	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
    	int ret;
    
    	/* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */
    	gpiod_set_value_cansleep(ctx->enable_gpio, 0);
    	usleep_range(10000, 11000);
    
    	ret = regulator_disable(ctx->vcc);
    	if (ret)
    		dev_err(ctx->dev, "Failed to disable vcc: %d\n", ret);
    
    	regcache_mark_dirty(ctx->regmap);
    }
    
    static enum drm_mode_status
    sn65dsi83_mode_valid(struct drm_bridge *bridge,
    		     const struct drm_display_info *info,
    		     const struct drm_display_mode *mode)
    {
    	/* LVDS output clock range 25..154 MHz */
    	if (mode->clock < 25000)
    		return MODE_CLOCK_LOW;
    	if (mode->clock > 154000)
    		return MODE_CLOCK_HIGH;
    
    	return MODE_OK;
    }
    
    #define MAX_INPUT_SEL_FORMATS	1
    
    static u32 *
    sn65dsi83_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)
    {
    	u32 *input_fmts;
    
    	*num_input_fmts = 0;
    
    	input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
    			     GFP_KERNEL);
    	if (!input_fmts)
    		return NULL;
    
    	/* This is the DSI-end bus format */
    	input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
    	*num_input_fmts = 1;
    
    	return input_fmts;
    }
    
    static const struct drm_bridge_funcs sn65dsi83_funcs = {
    	.attach			= sn65dsi83_attach,
    	.detach			= sn65dsi83_detach,
    	.atomic_enable		= sn65dsi83_atomic_enable,
    	.atomic_pre_enable	= sn65dsi83_atomic_pre_enable,
    	.atomic_disable		= sn65dsi83_atomic_disable,
    	.mode_valid		= sn65dsi83_mode_valid,
    
    	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
    	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
    	.atomic_reset = drm_atomic_helper_bridge_reset,
    	.atomic_get_input_bus_fmts = sn65dsi83_atomic_get_input_bus_fmts,
    };
    
    // static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
    // {
    // 	struct drm_bridge *panel_bridge;
    // 	struct device *dev = ctx->dev;
    
    // 	ctx->lvds_dual_link = false;
    // 	ctx->lvds_dual_link_even_odd_swap = false;
    // 	if (model != MODEL_SN65DSI83) {
    // 		struct device_node *port2, *port3;
    // 		int dual_link;
    
    // 		port2 = of_graph_get_port_by_id(dev->of_node, 2);
    // 		port3 = of_graph_get_port_by_id(dev->of_node, 3);
    // 		dual_link = drm_of_lvds_get_dual_link_pixel_order(port2, port3);
    // 		of_node_put(port2);
    // 		of_node_put(port3);
    
    // 		if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) {
    // 			ctx->lvds_dual_link = true;
    // 			/* Odd pixels to LVDS Channel A, even pixels to B */
    // 			ctx->lvds_dual_link_even_odd_swap = false;
    // 		} else if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) {
    // 			ctx->lvds_dual_link = true;
    // 			/* Even pixels to LVDS Channel A, odd pixels to B */
    // 			ctx->lvds_dual_link_even_odd_swap = true;
    // 		}
    // 	}
    
    // 	panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 2, 0);
    // 	if (IS_ERR(panel_bridge))
    // 		return PTR_ERR(panel_bridge);
    
    // 	ctx->panel_bridge = panel_bridge;
    
    // 	ctx->vcc = devm_regulator_get(dev, "vcc");
    // 	if (IS_ERR(ctx->vcc))
    // 		return dev_err_probe(dev, PTR_ERR(ctx->vcc),
    // 				     "Failed to get supply 'vcc'\n");
    
    // 	return 0;
    // }
    
    static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
    {
        struct drm_bridge *panel_bridge;
        struct device *dev = ctx->dev;
        int ret;
    
        ctx->lvds_dual_link = false;
        ctx->lvds_dual_link_even_odd_swap = false;
        if (model != MODEL_SN65DSI83) {
            struct device_node *port2, *port3;
            int dual_link;
    
            port2 = of_graph_get_port_by_id(dev->of_node, 2);
            port3 = of_graph_get_port_by_id(dev->of_node, 3);
            dual_link = drm_of_lvds_get_dual_link_pixel_order(port2, port3);
            of_node_put(port2);
            of_node_put(port3);
    
            if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) {
                ctx->lvds_dual_link = true;
                ctx->lvds_dual_link_even_odd_swap = false;
            } else if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) {
                ctx->lvds_dual_link = true;
                ctx->lvds_dual_link_even_odd_swap = true;
            }
        }
    
        //获取面板桥,显式处理 EPROBE_DEFER
        panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 2, 0);
        if (IS_ERR(panel_bridge)) {
            ret = PTR_ERR(panel_bridge);
            if (ret == -EPROBE_DEFER) {
                dev_info(dev, "Debug: Panel bridge (port 2) not ready, deferring probe (ret=%d)\n", ret);
                return ret; // 返给内核触发重试
            }
            return dev_err_probe(dev, ret, "Failed to get panel bridge from port 2\n");
        }
        ctx->panel_bridge = panel_bridge;
    
        // 获取电源稳压器,处理 EPROBE_DEFER
        ctx->vcc = devm_regulator_get(dev, "vcc");
        if (IS_ERR(ctx->vcc)) {
            ret = PTR_ERR(ctx->vcc);
            if (ret == -EPROBE_DEFER) {
                dev_info(dev, "Debug: VCC regulator not ready, deferring probe (ret=%d)\n", ret);
                return ret;
            }
            return dev_err_probe(dev, ret, "Failed to get supply 'vcc'\n");
        }
    
        return 0;
    }
    
    static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
    {
    	struct device *dev = ctx->dev;
    	struct device_node *host_node;
    	struct device_node *endpoint;
    	struct mipi_dsi_device *dsi;
    	struct mipi_dsi_host *host;
    	const struct mipi_dsi_device_info info = {
    		.type = "sn65dsi83",
    		.channel = 0,
    		.node = NULL,
    	};
    	int dsi_lanes, ret;
    
    	endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
    	dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4);
    	host_node = of_graph_get_remote_port_parent(endpoint);
    	host = of_find_mipi_dsi_host_by_node(host_node);
    	of_node_put(host_node);
    	of_node_put(endpoint);
    
    	if (!host)
    		return -EPROBE_DEFER;
    
    	if (dsi_lanes < 0)
    		return dsi_lanes;
    
    	dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
    	if (IS_ERR(dsi))
    		return dev_err_probe(dev, PTR_ERR(dsi),
    				     "failed to create dsi device\n");
    
    	ctx->dsi = dsi;
    
    	dsi->lanes = dsi_lanes;
    	dsi->format = MIPI_DSI_FMT_RGB888;
    	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
    			  MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP |
    			  MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_NO_EOT_PACKET;
    
    	ret = devm_mipi_dsi_attach(dev, dsi);
    	if (ret < 0) {
    		dev_err(dev, "failed to attach dsi to host: %d\n", ret);
    		return ret;
    	}
    
    	return 0;
    }
    
    // Forward declaration of of_match_table
    static const struct of_device_id sn65dsi83_match_table[];
    
    static int sn65dsi83_probe(struct i2c_client *client)
    {
    	const struct i2c_device_id *id = i2c_client_get_device_id(client);
    	const struct of_device_id *of_id;
    	struct device *dev = &client->dev;
    	enum sn65dsi83_model model;
    	struct sn65dsi83 *ctx;
    	int ret;
    
    	/* Priority: Device Tree match first, then I2C ID match */
    	of_id = of_match_device(sn65dsi83_match_table, dev);
    	if (of_id) {
    		model = (enum sn65dsi83_model)(uintptr_t)of_id->data;
    		dev_info(dev, "Debug: DT matched - compatible=%s, model=%d\n",
    			 of_id->compatible, model);
    	} else if (id) {
    		model = id->driver_data;
    		dev_info(dev, "Debug: I2C ID matched - name=%s, driver_data=%ld\n",
    			 id->name, id->driver_data);
    	} else {
    		dev_err(dev, "Debug: No matching ID or device tree node found\n");
    		return -ENODEV;
    	}
    
    	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
    	if (!ctx)
    		return -ENOMEM;
    
    	ctx->dev = dev;
    
    	/* Get optional enable GPIO (active high, default low for reset) */
    	ctx->enable_gpio = devm_gpiod_get_optional(ctx->dev, "enable",
    						   GPIOD_OUT_LOW);
    	if (IS_ERR(ctx->enable_gpio))
    		return dev_err_probe(dev, PTR_ERR(ctx->enable_gpio), "Debug: Failed to get enable GPIO\n");
    	else if (ctx->enable_gpio)
    		dev_info(dev, "Debug: Enable GPIO acquired successfully\n");
    	else
    		dev_info(dev, "Debug: Enable GPIO is optional and not provided\n");
    
    	/* Ensure reset low timing (10ms) as per datasheet */
    	usleep_range(10000, 11000);
    
    	/* Parse device tree configuration */
    	dev_info(dev, "Debug: Parsing device tree for model %d\n", model);
    	ret = sn65dsi83_parse_dt(ctx, model);
    	if (ret) {
    		dev_err(dev, "Debug: Failed to parse device tree, ret=%d\n", ret);
    		return ret;
    	}
    	dev_info(dev, "Debug: Device tree parsing completed successfully\n");
    
    	/* Initialize register map */
    	ctx->regmap = devm_regmap_init_i2c(client, &sn65dsi83_regmap_config);
    	if (IS_ERR(ctx->regmap))
    		return dev_err_probe(dev, PTR_ERR(ctx->regmap), "Debug: Failed to get regmap\n");
    
    	/* Set driver data for device and I2C client */
    	dev_info(dev, "Debug: Setting driver data for device and I2C client\n");
    	dev_set_drvdata(dev, ctx);
    	i2c_set_clientdata(client, ctx);
    	dev_info(dev, "Debug: Driver data set successfully\n");
    
    	/* Initialize DRM bridge */
    	ctx->bridge.funcs = &sn65dsi83_funcs;
    	ctx->bridge.of_node = dev->of_node;
    	ctx->bridge.pre_enable_prev_first = true;
    	drm_bridge_add(&ctx->bridge);
    
    	/* Attach DSI host */
    	ret = sn65dsi83_host_attach(ctx);
    	if (ret) {
    		dev_err_probe(dev, ret, "Debug: Failed to attach DSI host\n");
    		goto err_remove_bridge;
    	}
    
    	return 0;
    
    err_remove_bridge:
    	drm_bridge_remove(&ctx->bridge);
    	return ret;
    }
    
    static void sn65dsi83_remove(struct i2c_client *client)
    {
    	struct sn65dsi83 *ctx = i2c_get_clientdata(client);
    
    	drm_bridge_remove(&ctx->bridge);
    }
    
    static struct i2c_device_id sn65dsi83_id[] = {
    	{ "ti,sn65dsi83", MODEL_SN65DSI83 },
    	{ "ti,sn65dsi84", MODEL_SN65DSI84 },
    	{},
    };
    MODULE_DEVICE_TABLE(i2c, sn65dsi83_id);
    
    static const struct of_device_id sn65dsi83_match_table[] = {
    	{ .compatible = "ti,sn65dsi83", .data = (void *)MODEL_SN65DSI83 },
    	{ .compatible = "ti,sn65dsi84", .data = (void *)MODEL_SN65DSI84 },
    	{},
    };
    MODULE_DEVICE_TABLE(of, sn65dsi83_match_table);
    
    static struct i2c_driver sn65dsi83_driver = {
    	.probe = sn65dsi83_probe,
    	.remove = sn65dsi83_remove,
    	.id_table = sn65dsi83_id,
    	.driver = {
    		.name = "sn65dsi83",
    		.of_match_table = sn65dsi83_match_table,
    	},
    };
    module_i2c_driver(sn65dsi83_driver);
    
    MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
    MODULE_DESCRIPTION("TI SN65DSI83 DSI to LVDS bridge driver");
    MODULE_LICENSE("GPL v2");

  • 问题2 补充: 

    问题中提的原厂屏,指的是《SN65DSI83, SN65DSI84, and SN65DSI85 EVM User’s Manual and Implementation Guide》 展示的屏幕

    这块屏幕如下:

    或者是只要符合这个文档中原理图 J2  I-PEX线序的 屏幕推荐都可以,(网上找了没有找到匹配的),非常感谢

    问题3 补充:

    后续量产硬件连接 ,SN65DSI83 外接的不是屏幕是FPGA 的lvds 接口,即

    即SN65DSI83 LVDS out 不直接连接一块屏幕而是一块FPGA ,现有ti-sn65dsi83.c 驱动是否可以支持?

    现有连接示意结构大致如下:

    MIPI DSI 桥接器(转换协议)→ SN65SDI83(转 LVDS)→ FPGA LVDS 接口  -> 接外部显示屏

  • 问题1: SN65DSI83 驱动报错如下log,尝试修改过lcd参数,没有任何效果,即便是屏幕不亮,有没有debug验证方法来验证 SN65DSI83转接功能是正常的?比如读写SN65DSI83哪个寄存器?或者测量哪些信号

    数据手册中提供了启动设备并编程寄存器的初始化序列。您可以使用驱动程序进行编程,或通过DSI调谐器图形界面输入配置设置(DSI输入、LVDS输出及设备配置),从而生成写入寄存器的脚本。

    设备是否正在编程?请检查写入的寄存器以确认。此外,作为第一步,可尝试使用色条测试图案验证输出是否正常显示。

    LVDS通道是否有输出?可通过探测测试进行验证。

    问题2 有没有原厂屏幕的购买信息?

    想要购买SN65DSI83 转接板的lcd屏幕来验证(排除lcd 屏的差异), 能否提供购买方法?

    该芯片本身不配备LCD面板。只要具备相同LVDS通道连接方式,且分辨率与像素时钟频率在适用范围内,通用型LVDS显示屏均可与之兼容。目前我们尚未为该显示屏分配产品编号。请问您是否备有连接此EVM输出端的连接线?

    问题3   后续量产硬件连接 ,SN65DSI83 外接的不是屏幕是FPGA 的lvds 接口

     

    SN65DSI83 现有驱动是否支持这种FPGA的连接方式?

    DSI83桥仅输出标准LVDS视频接口。客户或设计者需自行配置FPGA或任何接收器来处理LVDS视频信号。

    此处仅提供参考驱动程序:Question about SN65DSI84

  • Hi Eirwen

    非常感谢您的支持和答复;

    1,  色条测试图案的验证,即SN65SDI83的 test mode 出图,我理解如果驱动异常导致sdi clk 无法输出的话,需要加一个外部的参数时钟25MHz-154MHz再加上I2c 寄存器数据下发才能验证,这种理解是否正确?

    2,已经有EVM 板子了但仅仅只有个板子,没有EVM输出端的连接线,网上购买屏飞线连接,为了排除飞线线序的问题,EVM输出端的连接线可以提供购买方式吗?

    3,感谢提供参考驱动程序,后续会进行适配验证。

  • 您好,非常感谢您的支持,麻烦问下SN65SDI83 EVM输出端的连接线可以提供购买方式吗? EVM 色条测试图案LVDS输出必须要有RFCLK的的输入吗?

  • 1. 测试图案或视频输出时钟基于 REFCLK 或 DSI 时钟。若采用 DSI 时钟,则必须从 SoC 源进行设置。若改用 REFCLK,则必须符合数据手册中的 REFCLK 规格要求。使用 REFCLK 或 DSI 时钟时,需在寄存器中进行设置,选择时钟源、DSI 时钟范围,并根据需要设置分频器或倍频器。

    获取所需寄存器设置时,可使用DSI调谐工具输入参数,系统将自动生成对应的寄存器写入值。


    2. 我们提供一份易于使用的用户指南,请点击此处查看: www.ti.com/.../sllu221.pdf

    用户指南第3页标注了电路板上所有主要组件的名称。

    第6页提供了帮助客户快速启动并运行电路板的操作步骤。

    第8页展示了电路板上芯片的原理图。

    第9页则详细列出了J1至J6各连接线缆的原理图及引脚排列顺序。

  • Hi Eriwen

    作为第一步,目前使用SDI83色条测试图案来验证转接功能,使用外部参考时钟是50M的方式外部I2c 连接EVM SDI83 板子J10写入数据后EVM LVDS CLK没有任何输出,即J2 没有任何输出 ,麻烦再请帮支持分析下看哪里的配置或设置还有问题? 非常您的感谢支持

    1,  我们使用的imx8mm 板卡为提供 EVM SDI83 50M REFCLK时钟,确认有50M,接入进EVM 板子的,以下是evm 50M接入的原理图。

    2,DSI83 Tuner 配置如下,refclk 使用50M 倍频是2 ,即lvds 出的100M 时钟 

    CSR 写入数据是 //=====================================================================
    0x09 0x00
    0x0A 0x06
    0x0B 0x01
    0x0D 0x00
    0x10 0x26
    0x11 0x00
    0x12 0x00
    0x13 0x00
    0x18 0x7a
    0x19 0x00
    0x1A 0x03
    0x1B 0x00
    0x20 0x80
    0x21 0x07
    0x22 0x00
    0x23 0x00
    0x24 0x38
    0x25 0x04
    0x26 0x00
    0x27 0x00
    0x28 0x20
    0x29 0x00
    0x2A 0x00
    0x2B 0x00
    0x2C 0x2c
    0x2D 0x00
    0x2E 0x00
    0x2F 0x00
    0x30 0x05
    0x31 0x00
    0x32 0x00
    0x33 0x00
    0x34 0x94
    0x35 0x00
    0x36 0x24
    0x37 0x00
    0x38 0x58
    0x39 0x00
    0x3A 0x04
    0x3B 0x00
    0x3C 0x10
    0x3D 0x00
    0x3E 0x00

    用i2cdump dump 数据 看,确认以上数据是写进去了 ,可以证明i2c的功能是正常的 

    root@imx8mm-lpddr4-evk:~# i2cdump -y 2 0x2c
    No size specified (using byte-data access)
    0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
    00: 35 38 49 53 44 20 20 20 01 00 06 01 00 00 00 00 58ISD ?.??....   
    10: 26 00 00 00 00 00 00 00 7a 00 03 00 00 00 00 00 &.......z.?.....
    20: 80 07 00 00 38 04 00 00 20 00 00 00 2c 00 00 00 ??..8?.. ...,...
    30: 05 00 00 00 94 00 24 00 58 00 04 00 10 00 00 00 ?...?.$.X.?.?...
    40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    e0: 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 .....?..........
    f0: 00 00 00 00 40 00 00 80 00 00 00 00 00 00 00 00 ....@..?........

    这里有个疑问dump 产品为什么是DSI85 (像是00-07 寄存器拼出的ASCII码),但是EVM 芯片上写的确实DSI83,寄存器的配置是按照DSI83 配置的,这里的不同会有什么影响?

    3,以上配置,没有MIPI DSI的输入,我理解test mode 不需要 MIPI DSI,不确定这种理解是否正确, refclk 输入时钟,en pin 测试是高,下方i2c 配置后 如下J2 A通道没有时钟输出;

    4,还有PID SW 的拨码如下 即全在open(off)上, 测试BRD_RSTN 信号是高, 目前这个拨码设置是否存在问题?

    多谢支持,期待您的回复。