aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/xilinx/xilinx_drm_sdi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/xilinx/xilinx_drm_sdi.c')
-rw-r--r--drivers/gpu/drm/xilinx/xilinx_drm_sdi.c1452
1 files changed, 1452 insertions, 0 deletions
diff --git a/drivers/gpu/drm/xilinx/xilinx_drm_sdi.c b/drivers/gpu/drm/xilinx/xilinx_drm_sdi.c
new file mode 100644
index 000000000000..c33b3dfb6809
--- /dev/null
+++ b/drivers/gpu/drm/xilinx/xilinx_drm_sdi.c
@@ -0,0 +1,1452 @@
+/*
+ * Xilinx FPGA SDI Tx Controller driver.
+ *
+ * Copyright (c) 2017 Xilinx Pvt., Ltd
+ *
+ * Contacts: Saurabh Sengar <saurabhs@xilinx.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drmP.h>
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/phy/phy.h>
+#include <video/videomode.h>
+#include "xilinx_drm_sdi.h"
+#include "xilinx_vtc.h"
+
+/* SDI register offsets */
+#define XSDI_TX_RST_CTRL 0x00
+#define XSDI_TX_MDL_CTRL 0x04
+#define XSDI_TX_GLBL_IER 0x0C
+#define XSDI_TX_ISR_STAT 0x10
+#define XSDI_TX_IER_STAT 0x14
+#define XSDI_TX_ST352_LINE 0x18
+#define XSDI_TX_ST352_DATA_CH0 0x1C
+#define XSDI_TX_VER 0x3C
+#define XSDI_TX_SYS_CFG 0x40
+#define XSDI_TX_STS_SB_TDATA 0x60
+#define XSDI_TX_AXI4S_STS1 0x68
+#define XSDI_TX_AXI4S_STS2 0x6C
+
+/* MODULE_CTRL register masks */
+#define XSDI_TX_CTRL_MDL_EN_MASK BIT(0)
+#define XSDI_TX_CTRL_OUT_EN_MASK BIT(1)
+#define XSDI_TX_CTRL_M_MASK BIT(7)
+#define XSDI_TX_CTRL_INS_CRC_MASK BIT(12)
+#define XSDI_TX_CTRL_INS_ST352_MASK BIT(13)
+#define XSDI_TX_CTRL_OVR_ST352_MASK BIT(14)
+#define XSDI_TX_CTRL_INS_SYNC_BIT_MASK BIT(16)
+#define XSDI_TX_CTRL_SD_BITREP_BYPASS_MASK BIT(17)
+#define XSDI_TX_CTRL_USE_ANC_IN_MASK BIT(18)
+#define XSDI_TX_CTRL_INS_LN_MASK BIT(19)
+#define XSDI_TX_CTRL_INS_EDH_MASK BIT(20)
+#define XSDI_TX_CTRL_MODE_MASK 0x7
+#define XSDI_TX_CTRL_MUX_MASK 0x7
+#define XSDI_TX_CTRL_MODE_SHIFT 4
+#define XSDI_TX_CTRL_M_SHIFT 7
+#define XSDI_TX_CTRL_MUX_SHIFT 8
+#define XSDI_TX_CTRL_INS_CRC_SHIFT 12
+#define XSDI_TX_CTRL_INS_ST352_SHIFT 13
+#define XSDI_TX_CTRL_OVR_ST352_SHIFT 14
+#define XSDI_TX_CTRL_ST352_F2_EN_SHIFT 15
+#define XSDI_TX_CTRL_INS_SYNC_BIT_SHIFT 16
+#define XSDI_TX_CTRL_SD_BITREP_BYPASS_SHIFT 17
+#define XSDI_TX_CTRL_USE_ANC_IN_SHIFT 18
+#define XSDI_TX_CTRL_INS_LN_SHIFT 19
+#define XSDI_TX_CTRL_INS_EDH_SHIFT 20
+
+/* TX_ST352_LINE register masks */
+#define XSDI_TX_ST352_LINE_MASK GENMASK(10, 0)
+#define XSDI_TX_ST352_LINE_F2_SHIFT 16
+
+/* ISR STAT register masks */
+#define XSDI_GTTX_RSTDONE_INTR_MASK BIT(0)
+#define XSDI_TX_CE_ALIGN_ERR_INTR_MASK BIT(1)
+#define XSDI_AXI4S_VID_LOCK_INTR_MASK BIT(8)
+#define XSDI_OVERFLOW_INTR_MASK BIT(9)
+#define XSDI_UNDERFLOW_INTR_MASK BIT(10)
+#define XSDI_IER_EN_MASK (XSDI_GTTX_RSTDONE_INTR_MASK | \
+ XSDI_TX_CE_ALIGN_ERR_INTR_MASK | \
+ XSDI_OVERFLOW_INTR_MASK | \
+ XSDI_UNDERFLOW_INTR_MASK)
+
+/* RST_CTRL_OFFSET masks */
+#define XSDI_TX_BRIDGE_CTRL_EN_MASK BIT(8)
+#define XSDI_TX_AXI4S_CTRL_EN_MASK BIT(9)
+#define XSDI_TX_CTRL_EN_MASK BIT(0)
+
+/* STS_SB_TX_TDATA masks */
+#define XSDI_TX_TDATA_DONE_MASK BIT(0)
+#define XSDI_TX_TDATA_FAIL_MASK BIT(1)
+#define XSDI_TX_TDATA_GT_RESETDONE_MASK BIT(2)
+#define XSDI_TX_TDATA_SLEW_RATE_MASK BIT(3)
+#define XSDI_TX_TDATA_TXPLLCLKSEL_MASK GENMASK(5, 4)
+#define XSDI_TX_TDATA_GT_SYSCLKSEL_MASK GENMASK(7, 6)
+#define XSDI_TX_TDATA_FABRIC_RST_MASK BIT(8)
+#define XSDI_TX_TDATA_DRP_FAIL_MASK BIT(9)
+#define XSDI_TX_TDATA_FAIL_CODE_MASK GENMASK(14, 12)
+#define XSDI_TX_TDATA_DRP_FAIL_CNT_MASK 0xFF0000
+#define XSDI_TX_TDATA_GT_QPLL0LOCK_MASK BIT(24)
+#define XSDI_TX_TDATA_GT_QPLL1LOCK_MASK BIT(25)
+
+#define SDI_MAX_DATASTREAM 8
+
+#define XSDI_TX_MUX_SD_HD_3GA 0
+#define XSDI_TX_MUX_3GB 1
+#define XSDI_TX_MUX_8STREAM_6G_12G 2
+#define XSDI_TX_MUX_4STREAM_6G 3
+#define XSDI_TX_MUX_16STREAM_12G 4
+
+#define PIXELS_PER_CLK 2
+#define XSDI_CH_SHIFT 29
+#define XST352_PROG_PIC_MASK BIT(6)
+#define XST352_PROG_TRANS_MASK BIT(7)
+#define XST352_2048_SHIFT BIT(6)
+#define ST352_BYTE3 0x00
+#define ST352_BYTE4 0x01
+#define INVALID_VALUE -1
+#define GT_TIMEOUT 500
+
+static LIST_HEAD(xilinx_sdi_list);
+static DEFINE_MUTEX(xilinx_sdi_lock);
+/**
+ * enum payload_line_1 - Payload Ids Line 1 number
+ * @PAYLD_LN1_HD_3_6_12G: line 1 HD,3G,6G or 12G mode value
+ * @PAYLD_LN1_SDPAL: line 1 SD PAL mode value
+ * @PAYLD_LN1_SDNTSC: line 1 SD NTSC mode value
+ */
+enum payload_line_1 {
+ PAYLD_LN1_HD_3_6_12G = 10,
+ PAYLD_LN1_SDPAL = 9,
+ PAYLD_LN1_SDNTSC = 13
+};
+
+/**
+ * enum payload_line_2 - Payload Ids Line 2 number
+ * @PAYLD_LN2_HD_3_6_12G: line 2 HD,3G,6G or 12G mode value
+ * @PAYLD_LN2_SDPAL: line 2 SD PAL mode value
+ * @PAYLD_LN2_SDNTSC: line 2 SD NTSC mode value
+ */
+enum payload_line_2 {
+ PAYLD_LN2_HD_3_6_12G = 572,
+ PAYLD_LN2_SDPAL = 322,
+ PAYLD_LN2_SDNTSC = 276
+};
+
+/**
+ * enum sdi_modes - SDI modes
+ * @XSDI_MODE_HD: HD mode
+ * @XSDI_MODE_SD: SD mode
+ * @XSDI_MODE_3GA: 3GA mode
+ * @XSDI_MODE_3GB: 3GB mode
+ * @XSDI_MODE_6G: 6G mode
+ * @XSDI_MODE_12G: 12G mode
+ */
+enum sdi_modes {
+ XSDI_MODE_HD = 0,
+ XSDI_MODE_SD,
+ XSDI_MODE_3GA,
+ XSDI_MODE_3GB,
+ XSDI_MODE_6G,
+ XSDI_MODE_12G
+};
+
+/**
+ * struct xilinx_sdi - Core configuration SDI Tx subsystem device structure
+ * @encoder: DRM encoder structure
+ * @connector: DRM connector structure
+ * @vtc: Pointer to VTC structure
+ * @dev: device structure
+ * @base: Base address of SDI subsystem
+ * @mode_flags: SDI operation mode related flags
+ * @wait_event: wait event
+ * @event_received: wait event status
+ * @list: entry in the global SDI subsystem list
+ * @vblank_fn: vblank handler
+ * @vblank_data: vblank data to be used in vblank_fn
+ * @sdi_mode: configurable SDI mode parameter, supported values are:
+ * 0 - HD
+ * 1 - SD
+ * 2 - 3GA
+ * 3 - 3GB
+ * 4 - 6G
+ * 5 - 12G
+ * @sdi_mod_prop_val: configurable SDI mode parameter value
+ * @sdi_data_strm: configurable SDI data stream parameter
+ * @sdi_data_strm_prop_val: configurable number of SDI data streams
+ * value currently supported are 2, 4 and 8
+ * @is_frac_prop: configurable SDI fractional fps parameter
+ * @is_frac_prop_val: configurable SDI fractional fps parameter value
+ */
+struct xilinx_sdi {
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+ struct xilinx_vtc *vtc;
+ struct device *dev;
+ void __iomem *base;
+ u32 mode_flags;
+ wait_queue_head_t wait_event;
+ bool event_received;
+ struct list_head list;
+ void (*vblank_fn)(void *);
+ void *vblank_data;
+ struct drm_property *sdi_mode;
+ u32 sdi_mod_prop_val;
+ struct drm_property *sdi_data_strm;
+ u32 sdi_data_strm_prop_val;
+ struct drm_property *is_frac_prop;
+ bool is_frac_prop_val;
+};
+
+/**
+ * struct xilinx_sdi_display_config - SDI supported modes structure
+ * @mode: drm display mode
+ * @st352_byt2: st352 byte 2 value
+ * index 0 : value for integral fps
+ * index 1 : value for fractional fps
+ * @st352_byt1: st352 byte 1 value
+ * index 0 : value for HD mode
+ * index 1 : value for SD mode
+ * index 2 : value for 3GA
+ * index 3 : value for 3GB
+ * index 4 : value for 6G
+ * index 5 : value for 12G
+ */
+struct xlnx_sdi_display_config {
+ struct drm_display_mode mode;
+ u8 st352_byt2[2];
+ u8 st352_byt1[6];
+};
+
+/*
+ * xlnx_sdi_modes - SDI DRM modes
+ */
+static const struct xlnx_sdi_display_config xlnx_sdi_modes[] = {
+ /* 0 - dummy, VICs start at 1 */
+ { },
+ /* SD: 720x480i@60Hz */
+ {{ DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500, 720, 739,
+ 801, 858, 0, 240, 244, 247, 262, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ .vrefresh = 60, }, {0x7, 0x6},
+ {0x81, 0x81, 0x81, 0x81, 0x81, 0x81} },
+ /* SD: 720x576i@50Hz */
+ {{ DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500, 720, 732,
+ 795, 864, 0, 288, 290, 293, 312, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ .vrefresh = 50, }, {0x9, 0x9},
+ {0x81, 0x81, 0x81, 0x81, 0x81, 0x81} },
+ /* HD: 1280x720@25Hz */
+ {{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 2250,
+ 2990, 3960, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, }, {0x5, 0x5},
+ {0x84, 0x84, 0x88, 0x84, 0x84, 0x84} },
+ /* HD: 1280x720@24Hz */
+ {{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 2250,
+ 3155, 4125, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, }, {0x3, 0x2},
+ {0x84, 0x84, 0x88, 0x84, 0x84, 0x84} },
+ /* HD: 1280x720@30Hz */
+ {{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 2250,
+ 2330, 3300, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, }, {0x7, 0x6},
+ {0x84, 0x84, 0x88, 0x84, 0x84, 0x84} },
+ /* HD: 1280x720@50Hz */
+ {{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720,
+ 1760, 1980, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, }, {0x9, 0x9},
+ {0x84, 0x84, 0x88, 0x84, 0x84, 0x84} },
+ /* HD: 1280x720@60Hz */
+ {{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390,
+ 1430, 1650, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, }, {0xB, 0xA},
+ {0x84, 0x84, 0x88, 0x84, 0x84, 0x84} },
+ /* HD: 1920x1080@24Hz */
+ {{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558,
+ 2602, 2750, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, }, {0x3, 0x2},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 1920x1080@25Hz */
+ {{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, }, {0x5, 0x5},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 1920x1080@30Hz */
+ {{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, }, {0x7, 0x6},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 1920x1080i@48Hz */
+ {{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2291,
+ 2379, 2750, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 48, }, {0x3, 0x2},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 1920x1080i@50Hz */
+ {{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448,
+ 2492, 2640, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 50, }, {0x5, 0x5},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 1920x1080i@60Hz */
+ {{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008,
+ 2052, 2200, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 60, }, {0x7, 0x6},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 1920x1080sf@24Hz */
+ {{ DRM_MODE("1920x1080sf", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2291,
+ 2379, 2750, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLSCAN),
+ .vrefresh = 48, }, {0x3, 0x2},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 1920x1080sf@25Hz */
+ {{ DRM_MODE("1920x1080sf", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448,
+ 2492, 2640, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLSCAN),
+ .vrefresh = 50, }, {0x5, 0x5},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 1920x1080sf@30Hz */
+ {{ DRM_MODE("1920x1080sf", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008,
+ 2052, 2200, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLSCAN),
+ .vrefresh = 60, }, {0x7, 0x6},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080i@48Hz */
+ {{ DRM_MODE("2048x1080i", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2377,
+ 2421, 2750, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 48, }, {0x3, 0x2},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080i@50Hz */
+ {{ DRM_MODE("2048x1080i", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2322,
+ 2366, 2640, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 50, }, {0x5, 0x5},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080i@60Hz */
+ {{ DRM_MODE("2048x1080i", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2114,
+ 2134, 2200, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 60, }, {0x7, 0x6},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080sf@24Hz */
+ {{ DRM_MODE("2048x1080sf", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2377,
+ 2421, 2750, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLSCAN),
+ .vrefresh = 48, }, {0x3, 0x2},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080sf@25Hz */
+ {{ DRM_MODE("2048x1080sf", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2322,
+ 2366, 2640, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLSCAN),
+ .vrefresh = 50, }, {0x5, 0x5},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080sf@30Hz */
+ {{ DRM_MODE("2048x1080sf", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2114,
+ 2134, 2200, 0, 540, 542, 547, 562, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLSCAN),
+ .vrefresh = 60, }, {0x7, 0x6},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080@30Hz */
+ {{ DRM_MODE("2048x1080", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2114,
+ 2134, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, }, {0x7, 0x6},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080@25Hz */
+ {{ DRM_MODE("2048x1080", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2448,
+ 2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, }, {0x5, 0x5},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* HD: 2048x1080@24Hz */
+ {{ DRM_MODE("2048x1080", DRM_MODE_TYPE_DRIVER, 74250, 2048, 2558,
+ 2602, 2750, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, }, {0x3, 0x2},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G: 1920x1080@48Hz */
+ {{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2558,
+ 2602, 2750, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 48, }, {0x8, 0x4},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G: 1920x1080@50Hz */
+ {{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, }, {0x9, 0x9},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G: 1920x1080@60Hz */
+ {{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, }, {0xB, 0xA},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G: 2048x1080@60Hz */
+ {{ DRM_MODE("2048x1080", DRM_MODE_TYPE_DRIVER, 148500, 2048, 2136,
+ 2180, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, }, {0xB, 0xA},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G: 2048x1080@50Hz */
+ {{ DRM_MODE("2048x1080", DRM_MODE_TYPE_DRIVER, 148500, 2048, 2448,
+ 2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, }, {0x9, 0x9},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G: 2048x1080@48Hz */
+ {{ DRM_MODE("2048x1080", DRM_MODE_TYPE_DRIVER, 148500, 2048, 2558,
+ 2602, 2750, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 48, }, {0x8, 0x4},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G-B: 1920x1080i@96Hz */
+ {{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2291,
+ 2379, 2750, 0, 1080, 1084, 1094, 1124, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 96, }, {0x8, 0x4},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G-B: 1920x1080i@100Hz */
+ {{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1094, 1124, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 100, }, {0x9, 0x9},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G-B: 1920x1080i@120Hz */
+ {{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1094, 1124, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 120, }, {0xB, 0xA},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G-B: 2048x1080i@96Hz */
+ {{ DRM_MODE("2048x1080i", DRM_MODE_TYPE_DRIVER, 148500, 2048, 2377,
+ 2421, 2750, 0, 1080, 1084, 1094, 1124, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 96, }, {0x8, 0x4},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G-B: 2048x1080i@100Hz */
+ {{ DRM_MODE("2048x1080i", DRM_MODE_TYPE_DRIVER, 148500, 2048, 2322,
+ 2366, 2640, 0, 1080, 1084, 1094, 1124, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 100, }, {0x9, 0x9},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 3G-B: 2048x1080i@120Hz */
+ {{ DRM_MODE("2048x1080i", DRM_MODE_TYPE_DRIVER, 148500, 2048, 2114,
+ 2134, 2200, 0, 1080, 1084, 1094, 1124, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ .vrefresh = 120, }, {0xB, 0xA},
+ {0x85, 0x85, 0x89, 0x8A, 0xC1, 0xC1} },
+ /* 6G: 3840x2160@30Hz */
+ {{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4016,
+ 4104, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, }, {0x7, 0x6},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 6G: 3840x2160@25Hz */
+ {{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4896,
+ 4984, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, }, {0x5, 0x5},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 6G: 3840x2160@24Hz */
+ {{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 5116,
+ 5204, 5500, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, }, {0x3, 0x2},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 6G: 4096x2160@24Hz */
+ {{ DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 296704, 4096, 5116,
+ 5204, 5500, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, }, {0x3, 0x2},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 6G: 4096x2160@25Hz */
+ {{ DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 297000, 4096, 5064,
+ 5152, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, }, {0x5, 0x5},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 6G: 4096x2160@30Hz */
+ {{ DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 296704, 4096, 4184,
+ 4272, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, }, {0x7, 0x6},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 12G: 3840x2160@48Hz */
+ {{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 5116,
+ 5204, 5500, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 48, }, {0x8, 0x4},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 12G: 3840x2160@50Hz */
+ {{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4896,
+ 4984, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, }, {0x9, 0x9},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 12G: 3840x2160@60Hz */
+ {{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016,
+ 4104, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, }, {0xB, 0xA},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 12G: 4096x2160@48Hz */
+ {{ DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 5116,
+ 5204, 5500, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 48, }, {0x8, 0x4},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 12G: 4096x2160@50Hz */
+ {{ DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 5064,
+ 5152, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, }, {0x9, 0x9},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+ /* 12G: 4096x2160@60Hz */
+ {{ DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 593408, 4096, 4184,
+ 4272, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, }, {0xB, 0xA},
+ {0x98, 0x98, 0x97, 0x98, 0xC0, 0xCE} },
+};
+
+#define connector_to_sdi(c) container_of(c, struct xilinx_sdi, connector)
+#define encoder_to_sdi(e) container_of(e, struct xilinx_sdi, encoder)
+
+/**
+ * xilinx_sdi_writel - Memory mapped SDI Tx register write
+ * @base: Pointer to SDI Tx registers base
+ * @offset: Register offset
+ * @val: value to be written
+ *
+ * This function writes the value to SDI TX registers
+ */
+static inline void xilinx_sdi_writel(void __iomem *base, int offset, u32 val)
+{
+ writel(val, base + offset);
+}
+
+/**
+ * xilinx_sdi_readl - Memory mapped SDI Tx register read
+ * @base: Pointer to SDI Tx registers base
+ * @offset: Register offset
+ *
+ * Return: The contents of the SDI Tx register
+ *
+ * This function returns the contents of the corresponding SDI Tx register.
+ */
+static inline u32 xilinx_sdi_readl(void __iomem *base, int offset)
+{
+ return readl(base + offset);
+}
+
+/**
+ * xilinx_en_axi4s - Enable SDI Tx AXI4S-to-Video core
+ * @sdi: Pointer to SDI Tx structure
+ *
+ * This function enables the SDI Tx AXI4S-to-Video core.
+ */
+static void xilinx_en_axi4s(struct xilinx_sdi *sdi)
+{
+ u32 data;
+
+ data = xilinx_sdi_readl(sdi->base, XSDI_TX_RST_CTRL);
+ data |= XSDI_TX_AXI4S_CTRL_EN_MASK;
+ xilinx_sdi_writel(sdi->base, XSDI_TX_RST_CTRL, data);
+}
+
+/**
+ * xilinx_en_bridge - Enable SDI Tx bridge
+ * @sdi: Pointer to SDI Tx structure
+ *
+ * This function enables the SDI Tx bridge.
+ */
+static void xilinx_en_bridge(struct xilinx_sdi *sdi)
+{
+ u32 data;
+
+ data = xilinx_sdi_readl(sdi->base, XSDI_TX_RST_CTRL);
+ data |= XSDI_TX_BRIDGE_CTRL_EN_MASK;
+ xilinx_sdi_writel(sdi->base, XSDI_TX_RST_CTRL, data);
+}
+
+/**
+ * xilinx_sdi_set_default_drm_properties - Configure SDI DRM
+ * properties with their default values
+ * @sdi: SDI structure having the updated user parameters
+ */
+static void
+xilinx_sdi_set_default_drm_properties(struct xilinx_sdi *sdi)
+{
+ drm_object_property_set_value(&sdi->connector.base,
+ sdi->sdi_mode, 0);
+ drm_object_property_set_value(&sdi->connector.base,
+ sdi->sdi_data_strm, 0);
+ drm_object_property_set_value(&sdi->connector.base,
+ sdi->is_frac_prop, 0);
+}
+
+/**
+ * xilinx_sdi_irq_handler - SDI Tx interrupt
+ * @irq: irq number
+ * @data: irq data
+ *
+ * Return: IRQ_HANDLED for all cases.
+ *
+ * This is the compact GT ready interrupt.
+ */
+static irqreturn_t xilinx_sdi_irq_handler(int irq, void *data)
+{
+ struct xilinx_sdi *sdi = (struct xilinx_sdi *)data;
+ u32 reg;
+
+ reg = xilinx_sdi_readl(sdi->base, XSDI_TX_ISR_STAT);
+
+ if (reg & XSDI_GTTX_RSTDONE_INTR_MASK)
+ dev_dbg(sdi->dev, "GT reset interrupt received\n");
+ if (reg & XSDI_TX_CE_ALIGN_ERR_INTR_MASK)
+ dev_err_ratelimited(sdi->dev, "SDI SD CE align error\n");
+ if (reg & XSDI_OVERFLOW_INTR_MASK)
+ dev_err_ratelimited(sdi->dev, "AXI-4 Stream Overflow error\n");
+ if (reg & XSDI_UNDERFLOW_INTR_MASK)
+ dev_err_ratelimited(sdi->dev, "AXI-4 Stream Underflow error\n");
+ xilinx_sdi_writel(sdi->base, XSDI_TX_ISR_STAT,
+ reg & ~(XSDI_AXI4S_VID_LOCK_INTR_MASK));
+
+ reg = xilinx_sdi_readl(sdi->base, XSDI_TX_STS_SB_TDATA);
+ if (reg & XSDI_TX_TDATA_GT_RESETDONE_MASK) {
+ sdi->event_received = true;
+ wake_up_interruptible(&sdi->wait_event);
+ }
+ return IRQ_HANDLED;
+}
+
+/**
+ * xilinx_sdi_set_payload_line - set ST352 packet line number
+ * @sdi: Pointer to SDI Tx structure
+ * @line_1: line number used to insert st352 packet for field 1.
+ * @line_2: line number used to insert st352 packet for field 2.
+ *
+ * This function set 352 packet line number.
+ */
+static void xilinx_sdi_set_payload_line(struct xilinx_sdi *sdi,
+ u32 line_1, u32 line_2)
+{
+ u32 data;
+
+ data = ((line_1 & XSDI_TX_ST352_LINE_MASK) |
+ ((line_2 & XSDI_TX_ST352_LINE_MASK) <<
+ XSDI_TX_ST352_LINE_F2_SHIFT));
+
+ xilinx_sdi_writel(sdi->base, XSDI_TX_ST352_LINE, data);
+
+ data = xilinx_sdi_readl(sdi->base, XSDI_TX_MDL_CTRL);
+ data |= (1 << XSDI_TX_CTRL_ST352_F2_EN_SHIFT);
+
+ xilinx_sdi_writel(sdi->base, XSDI_TX_MDL_CTRL, data);
+}
+
+/**
+ * xilinx_sdi_set_payload_data - set ST352 packet payload
+ * @sdi: Pointer to SDI Tx structure
+ * @data_strm: data stream number
+ * @payload: st352 packet payload
+ *
+ * This function set ST352 payload data to corresponding stream.
+ */
+static void xilinx_sdi_set_payload_data(struct xilinx_sdi *sdi,
+ u32 data_strm, u32 payload)
+{
+ xilinx_sdi_writel(sdi->base,
+ (XSDI_TX_ST352_DATA_CH0 + (data_strm * 4)), payload);
+}
+
+/**
+ * xilinx_sdi_set_display_disable - Disable the SDI Tx IP core enable
+ * register bit
+ * @sdi: SDI structure having the updated user parameters
+ *
+ * This function takes the SDI strucure and disables the core enable bit
+ * of core configuration register.
+ */
+static void xilinx_sdi_set_display_disable(struct xilinx_sdi *sdi)
+{
+ u32 i;
+
+ for (i = 0; i < SDI_MAX_DATASTREAM; i++)
+ xilinx_sdi_set_payload_data(sdi, i, 0);
+
+ xilinx_sdi_writel(sdi->base, XSDI_TX_GLBL_IER, 0);
+ xilinx_sdi_writel(sdi->base, XSDI_TX_RST_CTRL, 0);
+}
+
+/**
+ * xilinx_sdi_payload_config - config the SDI payload parameters
+ * @sdi: pointer Xilinx SDI Tx structure
+ * @mode: display mode
+ *
+ * This function config the SDI st352 payload parameter.
+ */
+static void xilinx_sdi_payload_config(struct xilinx_sdi *sdi, u32 mode)
+{
+ u32 payload_1, payload_2;
+
+ switch (mode) {
+ case XSDI_MODE_SD:
+ payload_1 = PAYLD_LN1_SDPAL;
+ payload_2 = PAYLD_LN2_SDPAL;
+ break;
+ case XSDI_MODE_HD:
+ case XSDI_MODE_3GA:
+ case XSDI_MODE_3GB:
+ case XSDI_MODE_6G:
+ case XSDI_MODE_12G:
+ payload_1 = PAYLD_LN1_HD_3_6_12G;
+ payload_2 = PAYLD_LN2_HD_3_6_12G;
+ break;
+ default:
+ payload_1 = 0;
+ payload_2 = 0;
+ break;
+ }
+
+ xilinx_sdi_set_payload_line(sdi, payload_1, payload_2);
+}
+
+/**
+ * xilinx_set_sdi_mode - Set mode parameters in SDI Tx
+ * @sdi: pointer Xilinx SDI Tx structure
+ * @mode: SDI Tx display mode
+ * @is_frac: 0 - integer 1 - fractional
+ * @mux_ptrn: specifiy the data stream interleaving pattern to be used
+ * This function config the SDI st352 payload parameter.
+ */
+static void xilinx_set_sdi_mode(struct xilinx_sdi *sdi, u32 mode,
+ bool is_frac, u32 mux_ptrn)
+{
+ u32 data;
+
+ xilinx_sdi_payload_config(sdi, mode);
+
+ data = xilinx_sdi_readl(sdi->base, XSDI_TX_MDL_CTRL);
+ data &= ~((XSDI_TX_CTRL_MODE_MASK << XSDI_TX_CTRL_MODE_SHIFT) |
+ (XSDI_TX_CTRL_M_MASK) | (XSDI_TX_CTRL_MUX_MASK
+ << XSDI_TX_CTRL_MUX_SHIFT));
+
+ data |= (((mode & XSDI_TX_CTRL_MODE_MASK)
+ << XSDI_TX_CTRL_MODE_SHIFT) |
+ (is_frac << XSDI_TX_CTRL_M_SHIFT) |
+ ((mux_ptrn & XSDI_TX_CTRL_MUX_MASK) << XSDI_TX_CTRL_MUX_SHIFT));
+
+ xilinx_sdi_writel(sdi->base, XSDI_TX_MDL_CTRL, data);
+}
+
+/**
+ * xilinx_sdi_set_config_parameters - Configure SDI Tx registers with parameters
+ * given from user application.
+ * @sdi: SDI structure having the updated user parameters
+ *
+ * This function takes the SDI structure having drm_property parameters
+ * configured from user application and writes them into SDI IP registers.
+ */
+static void xilinx_sdi_set_config_parameters(struct xilinx_sdi *sdi)
+{
+ u32 mode;
+ int mux_ptrn = INVALID_VALUE;
+ bool is_frac;
+
+ mode = sdi->sdi_mod_prop_val;
+ is_frac = sdi->is_frac_prop_val;
+
+ switch (mode) {
+ case XSDI_MODE_3GA:
+ mux_ptrn = XSDI_TX_MUX_SD_HD_3GA;
+ break;
+ case XSDI_MODE_3GB:
+ mux_ptrn = XSDI_TX_MUX_3GB;
+ break;
+ case XSDI_MODE_6G:
+ if (sdi->sdi_data_strm_prop_val == 4)
+ mux_ptrn = XSDI_TX_MUX_4STREAM_6G;
+ else if (sdi->sdi_data_strm_prop_val == 8)
+ mux_ptrn = XSDI_TX_MUX_8STREAM_6G_12G;
+ break;
+ case XSDI_MODE_12G:
+ if (sdi->sdi_data_strm_prop_val == 8)
+ mux_ptrn = XSDI_TX_MUX_8STREAM_6G_12G;
+ break;
+ default:
+ mux_ptrn = 0;
+ break;
+ }
+ if (mux_ptrn == INVALID_VALUE) {
+ dev_err(sdi->dev, "%d data stream not supported for %d mode",
+ sdi->sdi_data_strm_prop_val, mode);
+ return;
+ }
+ xilinx_set_sdi_mode(sdi, mode, is_frac, mux_ptrn);
+}
+
+/**
+ * xilinx_sdi_connector_set_property - implementation of drm_connector_funcs
+ * set_property invoked by IOCTL call to DRM_IOCTL_MODE_OBJ_SETPROPERTY
+ *
+ * @base_connector: pointer Xilinx SDI connector
+ * @property: pointer to the drm_property structure
+ * @value: SDI parameter value that is configured from user application
+ *
+ * This function takes a drm_property name and value given from user application
+ * and update the SDI structure property varabiles with the values.
+ * These values are later used to configure the SDI Rx IP.
+ *
+ * Return: 0 on success OR -EINVAL if setting property fails
+ */
+static int
+xilinx_sdi_connector_set_property(struct drm_connector *base_connector,
+ struct drm_property *property,
+ u64 value)
+{
+ struct xilinx_sdi *sdi = connector_to_sdi(base_connector);
+
+ if (property == sdi->sdi_mode)
+ sdi->sdi_mod_prop_val = (unsigned int)value;
+ else if (property == sdi->sdi_data_strm)
+ sdi->sdi_data_strm_prop_val = (unsigned int)value;
+ else if (property == sdi->is_frac_prop)
+ sdi->is_frac_prop_val = !!value;
+ else
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * xilinx_sdi_get_mode_id - Search for a video mode in the supported modes table
+ *
+ * @mode: mode being searched
+ *
+ * Return: true if mode is found
+ */
+static int xilinx_sdi_get_mode_id(struct drm_display_mode *mode)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(xlnx_sdi_modes); i++)
+ if (drm_mode_equal(&xlnx_sdi_modes[i].mode, mode))
+ return i;
+ return -EINVAL;
+}
+
+/**
+ * xilinx_sdi_drm_add_modes - Adds SDI supported modes
+ * @connector: pointer Xilinx SDI connector
+ *
+ * Return: Count of modes added
+ *
+ * This function adds the SDI modes supported and returns its count
+ */
+static int xilinx_sdi_drm_add_modes(struct drm_connector *connector)
+{
+ int i, num_modes = 0;
+ struct drm_display_mode *mode;
+ struct drm_device *dev = connector->dev;
+
+ for (i = 0; i < ARRAY_SIZE(xlnx_sdi_modes); i++) {
+ const struct drm_display_mode *ptr = &xlnx_sdi_modes[i].mode;
+
+ mode = drm_mode_duplicate(dev, ptr);
+ if (mode) {
+ drm_mode_probed_add(connector, mode);
+ num_modes++;
+ }
+ }
+ return num_modes;
+}
+
+static int xilinx_sdi_connector_dpms(struct drm_connector *connector,
+ int mode)
+{
+ return drm_helper_connector_dpms(connector, mode);
+}
+
+static enum drm_connector_status
+xilinx_sdi_detect(struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static void xilinx_sdi_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+ connector->dev = NULL;
+}
+
+static const struct drm_connector_funcs xilinx_sdi_connector_funcs = {
+ .dpms = xilinx_sdi_connector_dpms,
+ .detect = xilinx_sdi_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = xilinx_sdi_connector_destroy,
+ .set_property = xilinx_sdi_connector_set_property,
+};
+
+static struct drm_encoder *
+xilinx_sdi_best_encoder(struct drm_connector *connector)
+{
+ return &(connector_to_sdi(connector)->encoder);
+}
+
+static int xilinx_sdi_get_modes(struct drm_connector *connector)
+{
+ return xilinx_sdi_drm_add_modes(connector);
+}
+
+static struct drm_connector_helper_funcs xilinx_sdi_connector_helper_funcs = {
+ .get_modes = xilinx_sdi_get_modes,
+ .best_encoder = xilinx_sdi_best_encoder,
+};
+
+/**
+ * xilinx_sdi_drm_connector_create_property - create SDI connector properties
+ *
+ * @base_connector: pointer to Xilinx SDI connector
+ *
+ * This function takes the xilinx SDI connector component and defines
+ * the drm_property variables with their default values.
+ */
+static void
+xilinx_sdi_drm_connector_create_property(struct drm_connector *base_connector)
+{
+ struct drm_device *dev = base_connector->dev;
+ struct xilinx_sdi *sdi = connector_to_sdi(base_connector);
+
+ sdi->is_frac_prop = drm_property_create_bool(dev, 1, "is_frac");
+ sdi->sdi_mode = drm_property_create_range(dev, 0,
+ "sdi_mode", 0, 5);
+ sdi->sdi_data_strm = drm_property_create_range(dev, 0,
+ "sdi_data_stream", 2, 8);
+}
+
+/**
+ * xilinx_sdi_drm_connector_attach_property - attach SDI connector
+ * properties
+ *
+ * @base_connector: pointer to Xilinx SDI connector
+ */
+static void
+xilinx_sdi_drm_connector_attach_property(struct drm_connector *base_connector)
+{
+ struct xilinx_sdi *sdi = connector_to_sdi(base_connector);
+ struct drm_mode_object *obj = &base_connector->base;
+
+ if (sdi->sdi_mode)
+ drm_object_attach_property(obj, sdi->sdi_mode, 0);
+
+ if (sdi->sdi_data_strm)
+ drm_object_attach_property(obj, sdi->sdi_data_strm, 0);
+
+ if (sdi->is_frac_prop)
+ drm_object_attach_property(obj, sdi->is_frac_prop, 0);
+}
+
+static int xilinx_sdi_create_connector(struct drm_encoder *encoder)
+{
+ struct xilinx_sdi *sdi = encoder_to_sdi(encoder);
+ struct drm_connector *connector = &sdi->connector;
+ int ret;
+
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+ connector->interlace_allowed = true;
+ connector->doublescan_allowed = true;
+
+ ret = drm_connector_init(encoder->dev, connector,
+ &xilinx_sdi_connector_funcs,
+ DRM_MODE_CONNECTOR_Unknown);
+ if (ret) {
+ dev_err(sdi->dev, "Failed to initialize connector with drm\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(connector, &xilinx_sdi_connector_helper_funcs);
+ drm_connector_register(connector);
+ drm_connector_attach_encoder(connector, encoder);
+ xilinx_sdi_drm_connector_create_property(connector);
+ xilinx_sdi_drm_connector_attach_property(connector);
+
+ return 0;
+}
+
+static bool xilinx_sdi_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+/**
+ * xilinx_sdi_set_display_enable - Enables the SDI Tx IP core enable
+ * register bit
+ * @sdi: SDI structure having the updated user parameters
+ *
+ * This function takes the SDI strucure and enables the core enable bit
+ * of core configuration register.
+ */
+static void xilinx_sdi_set_display_enable(struct xilinx_sdi *sdi)
+{
+ u32 data;
+
+ data = xilinx_sdi_readl(sdi->base, XSDI_TX_RST_CTRL);
+ data |= XSDI_TX_CTRL_EN_MASK;
+ /* start sdi stream */
+ xilinx_sdi_writel(sdi->base, XSDI_TX_RST_CTRL, data);
+}
+
+static void xilinx_sdi_encoder_dpms(struct drm_encoder *encoder,
+ int mode)
+{
+ struct xilinx_sdi *sdi = encoder_to_sdi(encoder);
+
+ dev_dbg(sdi->dev, "encoder dpms state: %d\n", mode);
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ xilinx_sdi_set_display_enable(sdi);
+ return;
+ default:
+ xilinx_sdi_set_display_disable(sdi);
+ xilinx_sdi_set_default_drm_properties(sdi);
+ }
+}
+
+/**
+ * xilinx_sdi_calc_st352_payld - calculate the st352 payload
+ *
+ * @sdi: pointer to SDI Tx structure
+ * @mode: DRM display mode
+ *
+ * This function calculates the st352 payload to be configured.
+ * Please refer to SMPTE ST352 documents for it.
+ * Return: return st352 payload
+ */
+static u32 xilinx_sdi_calc_st352_payld(struct xilinx_sdi *sdi,
+ struct drm_display_mode *mode)
+{
+ u8 byt1, byt2;
+ u16 is_p;
+ u32 id, sdi_mode = sdi->sdi_mod_prop_val;
+ bool is_frac = sdi->is_frac_prop_val;
+ u32 byt3 = ST352_BYTE3;
+
+ id = xilinx_sdi_get_mode_id(mode);
+ dev_dbg(sdi->dev, "mode id: %d\n", id);
+ if (mode->hdisplay == 2048 || mode->hdisplay == 4096)
+ byt3 |= XST352_2048_SHIFT;
+ /* byte 2 calculation */
+ is_p = !(mode->flags & DRM_MODE_FLAG_INTERLACE);
+ byt2 = xlnx_sdi_modes[id].st352_byt2[is_frac];
+ if ((sdi_mode == XSDI_MODE_3GB) ||
+ (mode->flags & DRM_MODE_FLAG_DBLSCAN) || is_p)
+ byt2 |= XST352_PROG_PIC_MASK;
+ if (is_p && (mode->vtotal >= 1125))
+ byt2 |= XST352_PROG_TRANS_MASK;
+
+ /* byte 1 calculation */
+ byt1 = xlnx_sdi_modes[id].st352_byt1[sdi_mode];
+
+ return (ST352_BYTE4 << 24 | byt3 << 16 | byt2 << 8 | byt1);
+}
+
+/**
+ * xilinx_sdi_mode_set - drive the SDI timing parameters
+ *
+ * @encoder: pointer to Xilinx DRM encoder
+ * @mode: DRM kernel-internal display mode structure
+ * @adjusted_mode: SDI panel timing parameters
+ *
+ * This function derives the SDI IP timing parameters from the timing
+ * values given by VTC driver.
+ */
+static void xilinx_sdi_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct xilinx_sdi *sdi = encoder_to_sdi(encoder);
+ struct videomode vm;
+ u32 payload, i;
+
+ xilinx_sdi_set_config_parameters(sdi);
+
+ /* set st352 payloads */
+ payload = xilinx_sdi_calc_st352_payld(sdi, adjusted_mode);
+ dev_dbg(sdi->dev, "payload : %0x\n", payload);
+
+ for (i = 0; i < sdi->sdi_data_strm_prop_val / 2; i++) {
+ if (sdi->sdi_mod_prop_val == XSDI_MODE_3GB)
+ payload |= (i << 1) << XSDI_CH_SHIFT;
+ xilinx_sdi_set_payload_data(sdi, i, payload);
+ }
+
+ /* UHDSDI is fixed 2 pixels per clock, horizontal timings div by 2 */
+ vm.hactive = adjusted_mode->hdisplay / PIXELS_PER_CLK;
+ vm.hfront_porch = (adjusted_mode->hsync_start -
+ adjusted_mode->hdisplay) / PIXELS_PER_CLK;
+ vm.hback_porch = (adjusted_mode->htotal -
+ adjusted_mode->hsync_end) / PIXELS_PER_CLK;
+ vm.hsync_len = (adjusted_mode->hsync_end -
+ adjusted_mode->hsync_start) / PIXELS_PER_CLK;
+
+ vm.vactive = adjusted_mode->vdisplay;
+ vm.vfront_porch = adjusted_mode->vsync_start -
+ adjusted_mode->vdisplay;
+ vm.vback_porch = adjusted_mode->vtotal -
+ adjusted_mode->vsync_end;
+ vm.vsync_len = adjusted_mode->vsync_end -
+ adjusted_mode->vsync_start;
+ vm.flags = 0;
+ if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE)
+ vm.flags |= DISPLAY_FLAGS_INTERLACED;
+ if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC)
+ vm.flags |= DISPLAY_FLAGS_HSYNC_LOW;
+ if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
+ vm.flags |= DISPLAY_FLAGS_VSYNC_LOW;
+
+ xilinx_vtc_config_sig(sdi->vtc, &vm);
+}
+
+static void xilinx_sdi_prepare(struct drm_encoder *encoder)
+{
+ struct xilinx_sdi *sdi = encoder_to_sdi(encoder);
+ u32 reg;
+
+ dev_dbg(sdi->dev, "%s\n", __func__);
+
+ reg = xilinx_sdi_readl(sdi->base, XSDI_TX_MDL_CTRL);
+ reg |= XSDI_TX_CTRL_INS_CRC_MASK | XSDI_TX_CTRL_INS_ST352_MASK |
+ XSDI_TX_CTRL_OVR_ST352_MASK | XSDI_TX_CTRL_INS_SYNC_BIT_MASK |
+ XSDI_TX_CTRL_INS_EDH_MASK;
+ xilinx_sdi_writel(sdi->base, XSDI_TX_MDL_CTRL, reg);
+ xilinx_sdi_writel(sdi->base, XSDI_TX_IER_STAT, XSDI_IER_EN_MASK);
+ xilinx_sdi_writel(sdi->base, XSDI_TX_GLBL_IER, 1);
+ xilinx_vtc_reset(sdi->vtc);
+}
+
+static void xilinx_sdi_commit(struct drm_encoder *encoder)
+{
+ struct xilinx_sdi *sdi = encoder_to_sdi(encoder);
+ u32 ret;
+
+ dev_dbg(sdi->dev, "%s\n", __func__);
+ xilinx_sdi_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+
+ ret = wait_event_interruptible_timeout(sdi->wait_event,
+ sdi->event_received,
+ usecs_to_jiffies(GT_TIMEOUT));
+ if (!ret) {
+ dev_err(sdi->dev, "Timeout: GT interrupt not received\n");
+ return;
+ }
+ sdi->event_received = false;
+ /* enable sdi bridge, vtc and Axi4s_vid_out_ctrl */
+ xilinx_en_bridge(sdi);
+ xilinx_vtc_enable(sdi->vtc);
+ xilinx_en_axi4s(sdi);
+}
+
+static const struct drm_encoder_helper_funcs xilinx_sdi_encoder_helper_funcs = {
+ .dpms = xilinx_sdi_encoder_dpms,
+ .mode_fixup = xilinx_sdi_mode_fixup,
+ .mode_set = xilinx_sdi_mode_set,
+ .prepare = xilinx_sdi_prepare,
+ .commit = xilinx_sdi_commit,
+};
+
+static const struct drm_encoder_funcs xilinx_sdi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static int xilinx_sdi_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct xilinx_sdi *sdi = dev_get_drvdata(dev);
+ struct drm_encoder *encoder = &sdi->encoder;
+ struct drm_device *drm_dev = data;
+ int ret;
+
+ /*
+ * TODO: The possible CRTCs are 1 now as per current implementation of
+ * SDI tx drivers. DRM framework can support more than one CRTCs and
+ * SDI driver can be enhanced for that.
+ */
+ encoder->possible_crtcs = 1;
+
+ drm_encoder_init(drm_dev, encoder, &xilinx_sdi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+
+ drm_encoder_helper_add(encoder, &xilinx_sdi_encoder_helper_funcs);
+
+ ret = xilinx_sdi_create_connector(encoder);
+ if (ret) {
+ dev_err(sdi->dev, "fail creating connector, ret = %d\n", ret);
+ drm_encoder_cleanup(encoder);
+ }
+ return ret;
+}
+
+static void xilinx_sdi_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct xilinx_sdi *sdi = dev_get_drvdata(dev);
+
+ xilinx_sdi_encoder_dpms(&sdi->encoder, DRM_MODE_DPMS_OFF);
+ drm_encoder_cleanup(&sdi->encoder);
+ drm_connector_cleanup(&sdi->connector);
+}
+
+static const struct component_ops xilinx_sdi_component_ops = {
+ .bind = xilinx_sdi_bind,
+ .unbind = xilinx_sdi_unbind,
+};
+
+static irqreturn_t xilinx_sdi_vblank_handler(int irq, void *data)
+{
+ struct xilinx_sdi *sdi = (struct xilinx_sdi *)data;
+ u32 intr = xilinx_vtc_intr_get(sdi->vtc);
+
+ if (!intr)
+ return IRQ_NONE;
+
+ if (sdi->vblank_fn)
+ sdi->vblank_fn(sdi->vblank_data);
+
+ xilinx_vtc_intr_clear(sdi->vtc, intr);
+ return IRQ_HANDLED;
+}
+
+/**
+ * xilinx_drm_sdi_enable_vblank - Enable the vblank handling
+ * @sdi: SDI subsystem
+ * @vblank_fn: callback to be called on vblank event
+ * @vblank_data: data to be used in @vblank_fn
+ *
+ * This function register the vblank handler, and the handler will be triggered
+ * on vblank event after.
+ */
+void xilinx_drm_sdi_enable_vblank(struct xilinx_sdi *sdi,
+ void (*vblank_fn)(void *),
+ void *vblank_data)
+{
+ sdi->vblank_fn = vblank_fn;
+ sdi->vblank_data = vblank_data;
+ xilinx_vtc_vblank_enable(sdi->vtc);
+}
+EXPORT_SYMBOL_GPL(xilinx_drm_sdi_enable_vblank);
+
+/**
+ * xilinx_drm_sdi_disable_vblank - Disable the vblank handling
+ * @sdi: SDI subsystem
+ *
+ * Disable the vblank handler. The vblank handler and data are unregistered.
+ */
+void xilinx_drm_sdi_disable_vblank(struct xilinx_sdi *sdi)
+{
+ sdi->vblank_fn = NULL;
+ sdi->vblank_data = NULL;
+ xilinx_vtc_vblank_disable(sdi->vtc);
+}
+
+/**
+ * xilinx_sdi_register_device - Register the SDI subsystem to the global list
+ * @sdi: SDI subsystem
+ *
+ * Register the SDI subsystem instance to the global list
+ */
+static void xilinx_sdi_register_device(struct xilinx_sdi *sdi)
+{
+ mutex_lock(&xilinx_sdi_lock);
+ list_add_tail(&sdi->list, &xilinx_sdi_list);
+ mutex_unlock(&xilinx_sdi_lock);
+}
+
+/**
+ * xilinx_drm_sdi_of_get - Get the SDI subsystem instance
+ * @np: parent device node
+ *
+ * This function searches and returns a SDI subsystem structure for
+ * the parent device node, @np. The SDI subsystem node should be a child node
+ * of @np, with 'xlnx,sdi' property pointing to the SDI device node.
+ * An instance can be shared by multiple users.
+ *
+ * Return: corresponding SDI subsystem structure if found. NULL if
+ * the device node doesn't have 'xlnx,sdi' property, or -EPROBE_DEFER error
+ * pointer if the the SDI subsystem isn't found.
+ */
+struct xilinx_sdi *xilinx_drm_sdi_of_get(struct device_node *np)
+{
+ struct xilinx_sdi *found = NULL;
+ struct xilinx_sdi *sdi;
+ struct device_node *sdi_node;
+
+ if (!of_find_property(np, "xlnx,sdi", NULL))
+ return NULL;
+
+ sdi_node = of_parse_phandle(np, "xlnx,sdi", 0);
+ if (!sdi_node)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&xilinx_sdi_lock);
+ list_for_each_entry(sdi, &xilinx_sdi_list, list) {
+ if (sdi->dev->of_node == sdi_node) {
+ found = sdi;
+ break;
+ }
+ }
+ mutex_unlock(&xilinx_sdi_lock);
+
+ of_node_put(sdi_node);
+ if (!found)
+ return ERR_PTR(-EPROBE_DEFER);
+ return found;
+}
+
+/**
+ * xilinx_sdi_unregister_device - Unregister the SDI subsystem instance
+ * @sdi: SDI subsystem
+ *
+ * Unregister the SDI subsystem instance from the global list
+ */
+static void xilinx_sdi_unregister_device(struct xilinx_sdi *sdi)
+{
+ mutex_lock(&xilinx_sdi_lock);
+ list_del(&sdi->list);
+ mutex_unlock(&xilinx_sdi_lock);
+}
+
+static int xilinx_sdi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct xilinx_sdi *sdi;
+ struct device_node *vtc_node;
+ int ret, irq;
+
+ sdi = devm_kzalloc(dev, sizeof(*sdi), GFP_KERNEL);
+ if (!sdi)
+ return -ENOMEM;
+
+ sdi->dev = dev;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ sdi->base = devm_ioremap_resource(dev, res);
+
+ if (IS_ERR(sdi->base)) {
+ dev_err(dev, "failed to remap io region\n");
+ return PTR_ERR(sdi->base);
+ }
+ platform_set_drvdata(pdev, sdi);
+
+ vtc_node = of_parse_phandle(sdi->dev->of_node, "xlnx,vtc", 0);
+ if (!vtc_node) {
+ dev_err(dev, "vtc node not present\n");
+ return PTR_ERR(vtc_node);
+ }
+ sdi->vtc = xilinx_vtc_probe(sdi->dev, vtc_node);
+ of_node_put(vtc_node);
+ if (IS_ERR(sdi->vtc)) {
+ dev_err(dev, "failed to probe VTC\n");
+ return PTR_ERR(sdi->vtc);
+ }
+
+ /* disable interrupt */
+ xilinx_sdi_writel(sdi->base, XSDI_TX_GLBL_IER, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(sdi->dev, irq, NULL,
+ xilinx_sdi_irq_handler, IRQF_ONESHOT,
+ dev_name(sdi->dev), sdi);
+ if (ret < 0)
+ return ret;
+
+ irq = platform_get_irq(pdev, 1); /* vblank interrupt */
+ if (irq < 0)
+ return irq;
+ ret = devm_request_threaded_irq(sdi->dev, irq, NULL,
+ xilinx_sdi_vblank_handler, IRQF_ONESHOT,
+ "sdiTx-vblank", sdi);
+ if (ret < 0)
+ return ret;
+
+ init_waitqueue_head(&sdi->wait_event);
+ sdi->event_received = false;
+
+ xilinx_sdi_register_device(sdi);
+ return component_add(dev, &xilinx_sdi_component_ops);
+}
+
+static int xilinx_sdi_remove(struct platform_device *pdev)
+{
+ struct xilinx_sdi *sdi = platform_get_drvdata(pdev);
+
+ xilinx_sdi_unregister_device(sdi);
+ component_del(&pdev->dev, &xilinx_sdi_component_ops);
+
+ return 0;
+}
+
+static const struct of_device_id xilinx_sdi_of_match[] = {
+ { .compatible = "xlnx,v-smpte-uhdsdi-tx-ss"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, xilinx_sdi_of_match);
+
+static struct platform_driver sdi_tx_driver = {
+ .probe = xilinx_sdi_probe,
+ .remove = xilinx_sdi_remove,
+ .driver = {
+ .name = "xlnx,uhdsdi-tx",
+ .of_match_table = xilinx_sdi_of_match,
+ },
+};
+
+module_platform_driver(sdi_tx_driver);
+
+MODULE_AUTHOR("Saurabh Sengar <saurabhs@xilinx.com>");
+MODULE_DESCRIPTION("Xilinx FPGA SDI Tx Driver");