aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/platform/xilinx/xilinx-csi2rxss.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/xilinx/xilinx-csi2rxss.c')
-rw-r--r--drivers/media/platform/xilinx/xilinx-csi2rxss.c2023
1 files changed, 2023 insertions, 0 deletions
diff --git a/drivers/media/platform/xilinx/xilinx-csi2rxss.c b/drivers/media/platform/xilinx/xilinx-csi2rxss.c
new file mode 100644
index 000000000000..fc58a5268a9e
--- /dev/null
+++ b/drivers/media/platform/xilinx/xilinx-csi2rxss.c
@@ -0,0 +1,2023 @@
+/*
+ * Xilinx MIPI CSI2 Subsystem
+ *
+ * Copyright (C) 2016 Xilinx, Inc.
+ *
+ * Contacts: Vishal Sagar <vsagar@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <dt-bindings/media/xilinx-vip.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/compiler.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/spinlock_types.h>
+#include <linux/types.h>
+#include <linux/v4l2-subdev.h>
+#include <linux/xilinx-csi2rxss.h>
+#include <linux/xilinx-v4l2-controls.h>
+#include <media/media-entity.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+#include "xilinx-vip.h"
+
+/*
+ * MIPI CSI2 Rx register map, bitmask and offsets
+ */
+#define XCSI_CCR_OFFSET 0x00000000
+#define XCSI_CCR_SOFTRESET_SHIFT 1
+#define XCSI_CCR_COREENB_SHIFT 0
+#define XCSI_CCR_SOFTRESET_MASK BIT(XCSI_CCR_SOFTRESET_SHIFT)
+#define XCSI_CCR_COREENB_MASK BIT(XCSI_CCR_COREENB_SHIFT)
+
+#define XCSI_PCR_OFFSET 0x00000004
+#define XCSI_PCR_MAXLANES_MASK 0x00000018
+#define XCSI_PCR_ACTLANES_MASK 0x00000003
+#define XCSI_PCR_MAXLANES_SHIFT 3
+#define XCSI_PCR_ACTLANES_SHIFT 0
+
+#define XCSI_CSR_OFFSET 0x00000010
+#define XCSI_CSR_PKTCOUNT_SHIFT 16
+#define XCSI_CSR_SPFIFOFULL_SHIFT 3
+#define XCSI_CSR_SPFIFONE_SHIFT 2
+#define XCSI_CSR_SLBF_SHIFT 1
+#define XCSI_CSR_RIPCD_SHIFT 0
+#define XCSI_CSR_PKTCOUNT_MASK 0xFFFF0000
+#define XCSI_CSR_SPFIFOFULL_MASK BIT(XCSI_CSR_SPFIFOFULL_SHIFT)
+#define XCSI_CSR_SPFIFONE_MASK BIT(XCSI_CSR_SPFIFONE_SHIFT)
+#define XCSI_CSR_SLBF_MASK BIT(XCSI_CSR_SLBF_SHIFT)
+#define XCSI_CSR_RIPCD_MASK BIT(XCSI_CSR_RIPCD_SHIFT)
+
+#define XCSI_GIER_OFFSET 0x00000020
+#define XCSI_GIER_GIE_SHIFT 0
+#define XCSI_GIER_GIE_MASK BIT(XCSI_GIER_GIE_SHIFT)
+#define XCSI_GIER_SET 1
+#define XCSI_GIER_RESET 0
+
+#define XCSI_ISR_OFFSET 0x00000024
+#define XCSI_ISR_FR_SHIFT 31
+#define XCSI_ISR_VCX_SHIFT 30
+#define XCSI_ISR_ILC_SHIFT 21
+#define XCSI_ISR_SPFIFOF_SHIFT 20
+#define XCSI_ISR_SPFIFONE_SHIFT 19
+#define XCSI_ISR_SLBF_SHIFT 18
+#define XCSI_ISR_STOP_SHIFT 17
+#define XCSI_ISR_SOTERR_SHIFT 13
+#define XCSI_ISR_SOTSYNCERR_SHIFT 12
+#define XCSI_ISR_ECC2BERR_SHIFT 11
+#define XCSI_ISR_ECC1BERR_SHIFT 10
+#define XCSI_ISR_CRCERR_SHIFT 9
+#define XCSI_ISR_DATAIDERR_SHIFT 8
+#define XCSI_ISR_VC3FSYNCERR_SHIFT 7
+#define XCSI_ISR_VC3FLVLERR_SHIFT 6
+#define XCSI_ISR_VC2FSYNCERR_SHIFT 5
+#define XCSI_ISR_VC2FLVLERR_SHIFT 4
+#define XCSI_ISR_VC1FSYNCERR_SHIFT 3
+#define XCSI_ISR_VC1FLVLERR_SHIFT 2
+#define XCSI_ISR_VC0FSYNCERR_SHIFT 1
+#define XCSI_ISR_VC0FLVLERR_SHIFT 0
+#define XCSI_ISR_FR_MASK BIT(XCSI_ISR_FR_SHIFT)
+#define XCSI_ISR_VCX_MASK BIT(XCSI_ISR_VCX_SHIFT)
+#define XCSI_ISR_ILC_MASK BIT(XCSI_ISR_ILC_SHIFT)
+#define XCSI_ISR_SPFIFOF_MASK BIT(XCSI_ISR_SPFIFOF_SHIFT)
+#define XCSI_ISR_SPFIFONE_MASK BIT(XCSI_ISR_SPFIFONE_SHIFT)
+#define XCSI_ISR_SLBF_MASK BIT(XCSI_ISR_SLBF_SHIFT)
+#define XCSI_ISR_STOP_MASK BIT(XCSI_ISR_STOP_SHIFT)
+#define XCSI_ISR_SOTERR_MASK BIT(XCSI_ISR_SOTERR_SHIFT)
+#define XCSI_ISR_SOTSYNCERR_MASK BIT(XCSI_ISR_SOTSYNCERR_SHIFT)
+#define XCSI_ISR_ECC2BERR_MASK BIT(XCSI_ISR_ECC2BERR_SHIFT)
+#define XCSI_ISR_ECC1BERR_MASK BIT(XCSI_ISR_ECC1BERR_SHIFT)
+#define XCSI_ISR_CRCERR_MASK BIT(XCSI_ISR_CRCERR_SHIFT)
+#define XCSI_ISR_DATAIDERR_MASK BIT(XCSI_ISR_DATAIDERR_SHIFT)
+#define XCSI_ISR_VC3FSYNCERR_MASK BIT(XCSI_ISR_VC3FSYNCERR_SHIFT)
+#define XCSI_ISR_VC3FLVLERR_MASK BIT(XCSI_ISR_VC3FLVLERR_SHIFT)
+#define XCSI_ISR_VC2FSYNCERR_MASK BIT(XCSI_ISR_VC2FSYNCERR_SHIFT)
+#define XCSI_ISR_VC2FLVLERR_MASK BIT(XCSI_ISR_VC2FLVLERR_SHIFT)
+#define XCSI_ISR_VC1FSYNCERR_MASK BIT(XCSI_ISR_VC1FSYNCERR_SHIFT)
+#define XCSI_ISR_VC1FLVLERR_MASK BIT(XCSI_ISR_VC1FLVLERR_SHIFT)
+#define XCSI_ISR_VC0FSYNCERR_MASK BIT(XCSI_ISR_VC0FSYNCERR_SHIFT)
+#define XCSI_ISR_VC0FLVLERR_MASK BIT(XCSI_ISR_VC0FLVLERR_SHIFT)
+#define XCSI_ISR_ALLINTR_MASK 0xC03FFFFF
+
+#define XCSI_INTR_PROT_MASK (XCSI_ISR_VC3FSYNCERR_MASK | \
+ XCSI_ISR_VC3FLVLERR_MASK | \
+ XCSI_ISR_VC2FSYNCERR_MASK | \
+ XCSI_ISR_VC2FLVLERR_MASK | \
+ XCSI_ISR_VC1FSYNCERR_MASK | \
+ XCSI_ISR_VC1FLVLERR_MASK | \
+ XCSI_ISR_VC0FSYNCERR_MASK | \
+ XCSI_ISR_VC0FLVLERR_MASK | \
+ XCSI_ISR_VCX_MASK)
+
+#define XCSI_INTR_PKTLVL_MASK (XCSI_ISR_ECC2BERR_MASK | \
+ XCSI_ISR_ECC1BERR_MASK | \
+ XCSI_ISR_CRCERR_MASK | \
+ XCSI_ISR_DATAIDERR_MASK)
+
+#define XCSI_INTR_DPHY_MASK (XCSI_ISR_SOTERR_MASK | \
+ XCSI_ISR_SOTSYNCERR_MASK)
+
+#define XCSI_INTR_SPKT_MASK (XCSI_ISR_SPFIFOF_MASK | \
+ XCSI_ISR_SPFIFONE_MASK)
+
+#define XCSI_INTR_FRAMERCVD_MASK (XCSI_ISR_FR_MASK)
+
+#define XCSI_INTR_ERR_MASK (XCSI_ISR_ILC_MASK | \
+ XCSI_ISR_SLBF_MASK | \
+ XCSI_ISR_STOP_MASK)
+
+#define XCSI_IER_OFFSET 0x00000028
+#define XCSI_IER_FR_SHIFT 31
+#define XCSI_IER_VCX_SHIFT 30
+#define XCSI_IER_ILC_SHIFT 21
+#define XCSI_IER_SPFIFOF_SHIFT 20
+#define XCSI_IER_SPFIFONE_SHIFT 19
+#define XCSI_IER_SLBF_SHIFT 18
+#define XCSI_IER_STOP_SHIFT 17
+#define XCSI_IER_SOTERR_SHIFT 13
+#define XCSI_IER_SOTSYNCERR_SHIFT 12
+#define XCSI_IER_ECC2BERR_SHIFT 11
+#define XCSI_IER_ECC1BERR_SHIFT 10
+#define XCSI_IER_CRCERR_SHIFT 9
+#define XCSI_IER_DATAIDERR_SHIFT 8
+#define XCSI_IER_VC3FSYNCERR_SHIFT 7
+#define XCSI_IER_VC3FLVLERR_SHIFT 6
+#define XCSI_IER_VC2FSYNCERR_SHIFT 5
+#define XCSI_IER_VC2FLVLERR_SHIFT 4
+#define XCSI_IER_VC1FSYNCERR_SHIFT 3
+#define XCSI_IER_VC1FLVLERR_SHIFT 2
+#define XCSI_IER_VC0FSYNCERR_SHIFT 1
+#define XCSI_IER_VC0FLVLERR_SHIFT 0
+#define XCSI_IER_FR_MASK BIT(XCSI_IER_FR_SHIFT)
+#define XCSI_IER_VCX_MASK BIT(XCSI_IER_VCX_SHIFT)
+#define XCSI_IER_ILC_MASK BIT(XCSI_IER_ILC_SHIFT)
+#define XCSI_IER_SPFIFOF_MASK BIT(XCSI_IER_SPFIFOF_SHIFT)
+#define XCSI_IER_SPFIFONE_MASK BIT(XCSI_IER_SPFIFONE_SHIFT)
+#define XCSI_IER_SLBF_MASK BIT(XCSI_IER_SLBF_SHIFT)
+#define XCSI_IER_STOP_MASK BIT(XCSI_IER_STOP_SHIFT)
+#define XCSI_IER_SOTERR_MASK BIT(XCSI_IER_SOTERR_SHIFT)
+#define XCSI_IER_SOTSYNCERR_MASK BIT(XCSI_IER_SOTSYNCERR_SHIFT)
+#define XCSI_IER_ECC2BERR_MASK BIT(XCSI_IER_ECC2BERR_SHIFT)
+#define XCSI_IER_ECC1BERR_MASK BIT(XCSI_IER_ECC1BERR_SHIFT)
+#define XCSI_IER_CRCERR_MASK BIT(XCSI_IER_CRCERR_SHIFT)
+#define XCSI_IER_DATAIDERR_MASK BIT(XCSI_IER_DATAIDERR_SHIFT)
+#define XCSI_IER_VC3FSYNCERR_MASK BIT(XCSI_IER_VC3FSYNCERR_SHIFT)
+#define XCSI_IER_VC3FLVLERR_MASK BIT(XCSI_IER_VC3FLVLERR_SHIFT)
+#define XCSI_IER_VC2FSYNCERR_MASK BIT(XCSI_IER_VC2FSYNCERR_SHIFT)
+#define XCSI_IER_VC2FLVLERR_MASK BIT(XCSI_IER_VC2FLVLERR_SHIFT)
+#define XCSI_IER_VC1FSYNCERR_MASK BIT(XCSI_IER_VC1FSYNCERR_SHIFT)
+#define XCSI_IER_VC1FLVLERR_MASK BIT(XCSI_IER_VC1FLVLERR_SHIFT)
+#define XCSI_IER_VC0FSYNCERR_MASK BIT(XCSI_IER_VC0FSYNCERR_SHIFT)
+#define XCSI_IER_VC0FLVLERR_MASK BIT(XCSI_IER_VC0FLVLERR_SHIFT)
+#define XCSI_IER_ALLINTR_MASK 0xC03FFFFF
+
+#define XCSI_SPKTR_OFFSET 0x00000030
+#define XCSI_SPKTR_DATA_SHIFT 8
+#define XCSI_SPKTR_VC_SHIFT 6
+#define XCSI_SPKTR_DT_SHIFT 0
+#define XCSI_SPKTR_DATA_MASK 0x00FFFF00
+#define XCSI_SPKTR_VC_MASK 0x000000C0
+#define XCSI_SPKTR_DT_MASK 0x0000003F
+
+#define XCSI_VCXR_OFFSET 0x00000034
+#define XCSI_VCXR_VC15FSYNCERR_MASK BIT(23)
+#define XCSI_VCXR_VC15FLVLERR_MASK BIT(22)
+#define XCSI_VCXR_VC14FSYNCERR_MASK BIT(21)
+#define XCSI_VCXR_VC14FLVLERR_MASK BIT(20)
+#define XCSI_VCXR_VC13FSYNCERR_MASK BIT(19)
+#define XCSI_VCXR_VC13FLVLERR_MASK BIT(18)
+#define XCSI_VCXR_VC12FSYNCERR_MASK BIT(17)
+#define XCSI_VCXR_VC12FLVLERR_MASK BIT(16)
+#define XCSI_VCXR_VC11FSYNCERR_MASK BIT(15)
+#define XCSI_VCXR_VC11FLVLERR_MASK BIT(14)
+#define XCSI_VCXR_VC10FSYNCERR_MASK BIT(13)
+#define XCSI_VCXR_VC10FLVLERR_MASK BIT(12)
+#define XCSI_VCXR_VC9FSYNCERR_MASK BIT(11)
+#define XCSI_VCXR_VC9FLVLERR_MASK BIT(10)
+#define XCSI_VCXR_VC8FSYNCERR_MASK BIT(9)
+#define XCSI_VCXR_VC8FLVLERR_MASK BIT(8)
+#define XCSI_VCXR_VC7FSYNCERR_MASK BIT(7)
+#define XCSI_VCXR_VC7FLVLERR_MASK BIT(6)
+#define XCSI_VCXR_VC6FSYNCERR_MASK BIT(5)
+#define XCSI_VCXR_VC6FLVLERR_MASK BIT(4)
+#define XCSI_VCXR_VC5FSYNCERR_MASK BIT(3)
+#define XCSI_VCXR_VC5FLVLERR_MASK BIT(2)
+#define XCSI_VCXR_VC4FSYNCERR_MASK BIT(1)
+#define XCSI_VCXR_VC4FLVLERR_MASK BIT(0)
+#define XCSI_VCXR_MASK 0x00FFFFFF
+
+#define XCSI_CLKINFR_OFFSET 0x0000003C
+#define XCSI_CLKINFR_STOP_SHIFT 1
+#define XCSI_CLKINFR_STOP_MASK BIT(XCSI_CLKINFR_STOP_SHIFT)
+
+#define XCSI_L0INFR_OFFSET 0x00000040
+#define XCSI_L1INFR_OFFSET 0x00000044
+#define XCSI_L2INFR_OFFSET 0x00000048
+#define XCSI_L3INFR_OFFSET 0x0000004C
+#define XCSI_LXINFR_STOP_SHIFT 5
+#define XCSI_LXINFR_SOTERR_SHIFT 1
+#define XCSI_LXINFR_SOTSYNCERR_SHIFT 0
+#define XCSI_LXINFR_STOP_MASK BIT(XCSI_LXINFR_STOP_SHIFT)
+#define XCSI_LXINFR_SOTERR_MASK BIT(XCSI_LXINFR_SOTERR_SHIFT)
+#define XCSI_LXINFR_SOTSYNCERR_MASK BIT(XCSI_LXINFR_SOTSYNCERR_SHIFT)
+
+#define XCSI_VC0INF1R_OFFSET 0x00000060
+#define XCSI_VC1INF1R_OFFSET 0x00000068
+#define XCSI_VC2INF1R_OFFSET 0x00000070
+#define XCSI_VC3INF1R_OFFSET 0x00000078
+#define XCSI_VC4INF1R_OFFSET 0x00000080
+#define XCSI_VC5INF1R_OFFSET 0x00000088
+#define XCSI_VC6INF1R_OFFSET 0x00000090
+#define XCSI_VC7INF1R_OFFSET 0x00000098
+#define XCSI_VC8INF1R_OFFSET 0x000000A0
+#define XCSI_VC9INF1R_OFFSET 0x000000A8
+#define XCSI_VC10INF1R_OFFSET 0x000000B0
+#define XCSI_VC11INF1R_OFFSET 0x000000B8
+#define XCSI_VC12INF1R_OFFSET 0x000000C0
+#define XCSI_VC13INF1R_OFFSET 0x000000C8
+#define XCSI_VC14INF1R_OFFSET 0x000000D0
+#define XCSI_VC15INF1R_OFFSET 0x000000D8
+#define XCSI_VCXINF1R_LINECOUNT_SHIFT 16
+#define XCSI_VCXINF1R_BYTECOUNT_SHIFT 0
+#define XCSI_VCXINF1R_LINECOUNT_MASK 0xFFFF0000
+#define XCSI_VCXINF1R_BYTECOUNT_MASK 0x0000FFFF
+
+#define XCSI_VC0INF2R_OFFSET 0x00000064
+#define XCSI_VC1INF2R_OFFSET 0x0000006C
+#define XCSI_VC2INF2R_OFFSET 0x00000074
+#define XCSI_VC3INF2R_OFFSET 0x0000007C
+#define XCSI_VC4INF2R_OFFSET 0x00000084
+#define XCSI_VC5INF2R_OFFSET 0x0000008C
+#define XCSI_VC6INF2R_OFFSET 0x00000094
+#define XCSI_VC7INF2R_OFFSET 0x0000009C
+#define XCSI_VC8INF2R_OFFSET 0x000000A4
+#define XCSI_VC9INF2R_OFFSET 0x000000AC
+#define XCSI_VC10INF2R_OFFSET 0x000000B4
+#define XCSI_VC11INF2R_OFFSET 0x000000BC
+#define XCSI_VC12INF2R_OFFSET 0x000000C4
+#define XCSI_VC13INF2R_OFFSET 0x000000CC
+#define XCSI_VC14INF2R_OFFSET 0x000000D4
+#define XCSI_VC15INF2R_OFFSET 0x000000DC
+#define XCSI_VCXINF2R_DATATYPE_SHIFT 0
+#define XCSI_VCXINF2R_DATATYPE_MASK 0x0000003F
+
+#define XDPHY_CTRLREG_OFFSET 0x0
+#define XDPHY_CTRLREG_DPHYEN_SHIFT 1
+#define XDPHY_CTRLREG_DPHYEN_MASK BIT(XDPHY_CTRLREG_DPHYEN_SHIFT)
+
+#define XDPHY_CLKSTATREG_OFFSET 0x18
+#define XDPHY_CLKSTATREG_MODE_SHIFT 0
+#define XDPHY_CLKSTATREG_MODE_MASK 0x3
+#define XDPHY_LOW_POWER_MODE 0x0
+#define XDPHY_HI_SPEED_MODE 0x1
+#define XDPHY_ESC_MODE 0x2
+
+/*
+ * Interrupt mask
+ */
+#define XCSI_INTR_MASK (XCSI_ISR_ALLINTR_MASK & ~XCSI_ISR_STOP_MASK)
+/*
+ * Timeout for reset
+ */
+#define XCSI_TIMEOUT_VAL (1000) /* us */
+
+/*
+ * Max string length for CSI Data type string
+ */
+#define MAX_XIL_CSIDT_STR_LENGTH 64
+
+/*
+ * Maximum number of short packet events per file handle.
+ */
+#define XCSI_MAX_SPKT (512)
+
+/* Number of media pads */
+#define XILINX_CSI_MEDIA_PADS (2)
+
+#define XCSI_DEFAULT_WIDTH (1920)
+#define XCSI_DEFAULT_HEIGHT (1080)
+
+#define XCSI_DPHY_CLK_MIN 197000000000UL
+#define XCSI_DPHY_CLK_MAX 203000000000UL
+#define XCSI_DPHY_CLK_REQ 200000000000UL
+
+/*
+ * Macro to return "true" or "false" string if bit is set
+ */
+#define XCSI_GET_BITSET_STR(val, mask) (val) & (mask) ? "true" : "false"
+
+#define XCSI_CLK_PROP BIT(0)
+
+/**
+ * struct xcsi2rxss_feature - dt or IP property structure
+ * @flags: Bitmask of properties enabled in IP or dt
+ */
+struct xcsi2rxss_feature {
+ u32 flags;
+};
+
+enum CSI_DataTypes {
+ MIPI_CSI_DT_FRAME_START_CODE = 0x00,
+ MIPI_CSI_DT_FRAME_END_CODE,
+ MIPI_CSI_DT_LINE_START_CODE,
+ MIPI_CSI_DT_LINE_END_CODE,
+ MIPI_CSI_DT_SYNC_RSVD_04,
+ MIPI_CSI_DT_SYNC_RSVD_05,
+ MIPI_CSI_DT_SYNC_RSVD_06,
+ MIPI_CSI_DT_SYNC_RSVD_07,
+ MIPI_CSI_DT_GSPKT_08,
+ MIPI_CSI_DT_GSPKT_09,
+ MIPI_CSI_DT_GSPKT_0A,
+ MIPI_CSI_DT_GSPKT_0B,
+ MIPI_CSI_DT_GSPKT_0C,
+ MIPI_CSI_DT_GSPKT_0D,
+ MIPI_CSI_DT_GSPKT_0E,
+ MIPI_CSI_DT_GSPKT_0F,
+ MIPI_CSI_DT_GLPKT_10,
+ MIPI_CSI_DT_GLPKT_11,
+ MIPI_CSI_DT_GLPKT_12,
+ MIPI_CSI_DT_GLPKT_13,
+ MIPI_CSI_DT_GLPKT_14,
+ MIPI_CSI_DT_GLPKT_15,
+ MIPI_CSI_DT_GLPKT_16,
+ MIPI_CSI_DT_GLPKT_17,
+ MIPI_CSI_DT_YUV_420_8B,
+ MIPI_CSI_DT_YUV_420_10B,
+ MIPI_CSI_DT_YUV_420_8B_LEGACY,
+ MIPI_CSI_DT_YUV_RSVD,
+ MIPI_CSI_DT_YUV_420_8B_CSPS,
+ MIPI_CSI_DT_YUV_420_10B_CSPS,
+ MIPI_CSI_DT_YUV_422_8B,
+ MIPI_CSI_DT_YUV_422_10B,
+ MIPI_CSI_DT_RGB_444,
+ MIPI_CSI_DT_RGB_555,
+ MIPI_CSI_DT_RGB_565,
+ MIPI_CSI_DT_RGB_666,
+ MIPI_CSI_DT_RGB_888,
+ MIPI_CSI_DT_RGB_RSVD_25,
+ MIPI_CSI_DT_RGB_RSVD_26,
+ MIPI_CSI_DT_RGB_RSVD_27,
+ MIPI_CSI_DT_RAW_6,
+ MIPI_CSI_DT_RAW_7,
+ MIPI_CSI_DT_RAW_8,
+ MIPI_CSI_DT_RAW_10,
+ MIPI_CSI_DT_RAW_12,
+ MIPI_CSI_DT_RAW_14,
+ MIPI_CSI_DT_RAW_16,
+ MIPI_CSI_DT_RAW_20,
+ MIPI_CSI_DT_USER_30,
+ MIPI_CSI_DT_USER_31,
+ MIPI_CSI_DT_USER_32,
+ MIPI_CSI_DT_USER_33,
+ MIPI_CSI_DT_USER_34,
+ MIPI_CSI_DT_USER_35,
+ MIPI_CSI_DT_USER_36,
+ MIPI_CSI_DT_USER_37,
+ MIPI_CSI_DT_RSVD_38,
+ MIPI_CSI_DT_RSVD_39,
+ MIPI_CSI_DT_RSVD_3A,
+ MIPI_CSI_DT_RSVD_3B,
+ MIPI_CSI_DT_RSVD_3C,
+ MIPI_CSI_DT_RSVD_3D,
+ MIPI_CSI_DT_RSVD_3E,
+ MIPI_CSI_DT_RSVD_3F
+};
+
+/**
+ * struct pixel_format - Data Type to string name structure
+ * @PixelFormat: MIPI CSI2 Data type
+ * @PixelFormatStr: String name of Data Type
+ */
+struct pixel_format {
+ enum CSI_DataTypes PixelFormat;
+ char PixelFormatStr[MAX_XIL_CSIDT_STR_LENGTH];
+};
+
+/**
+ * struct xcsi2rxss_event - Event log structure
+ * @mask: Event mask
+ * @name: Name of the event
+ * @counter: Count number of events
+ */
+struct xcsi2rxss_event {
+ u32 mask;
+ const char * const name;
+ unsigned int counter;
+};
+
+/*
+ * struct xcsi2rxss_core - Core configuration CSI2 Rx Subsystem device structure
+ * @dev: Platform structure
+ * @iomem: Base address of subsystem
+ * @irq: requested irq number
+ * @dphy_present: Flag for DPHY register interface presence
+ * @dphy_offset: DPHY registers offset
+ * @enable_active_lanes: If number of active lanes can be modified
+ * @max_num_lanes: Maximum number of lanes present
+ * @vfb: Video Format Bridge enabled or not
+ * @ppc: pixels per clock
+ * @vc: Virtual Channel
+ * @axis_tdata_width: AXI Stream data width
+ * @datatype: Data type filter
+ * @pxlformat: String with CSI pixel format from IP
+ * @num_lanes: Number of lanes requested from application
+ * @events: Structure to maintain event logs
+ * @vcx_events: Structure to maintain VCX event logs
+ * @en_vcx: If more than 4 VC are enabled.
+ * @cfg: Pointer to csi2rxss config structure
+ * @lite_aclk: AXI4-Lite interface clock
+ * @video_aclk: Video clock
+ * @dphy_clk_200M: 200MHz DPHY clock
+ */
+struct xcsi2rxss_core {
+ struct device *dev;
+ void __iomem *iomem;
+ int irq;
+ u32 dphy_offset;
+ bool dphy_present;
+ bool enable_active_lanes;
+ u32 max_num_lanes;
+ bool vfb;
+ u32 ppc;
+ u32 vc;
+ u32 axis_tdata_width;
+ u32 datatype;
+ const char *pxlformat;
+ u32 num_lanes;
+ struct xcsi2rxss_event *events;
+ struct xcsi2rxss_event *vcx_events;
+ bool en_vcx;
+ const struct xcsi2rxss_feature *cfg;
+ struct clk *lite_aclk;
+ struct clk *video_aclk;
+ struct clk *dphy_clk_200M;
+};
+
+/**
+ * struct xcsi2rxss_state - CSI2 Rx Subsystem device structure
+ * @core: Core structure for MIPI CSI2 Rx Subsystem
+ * @subdev: The v4l2 subdev structure
+ * @ctrl_handler: control handler
+ * @formats: Active V4L2 formats on each pad
+ * @default_format: default V4L2 media bus format
+ * @vip_format: format information corresponding to the active format
+ * @event: Holds the short packet event
+ * @lock: mutex for serializing operations
+ * @pads: media pads
+ * @npads: number of pads
+ * @streaming: Flag for storing streaming state
+ * @suspended: Flag for storing suspended state
+ *
+ * This structure contains the device driver related parameters
+ */
+struct xcsi2rxss_state {
+ struct xcsi2rxss_core core;
+ struct v4l2_subdev subdev;
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct v4l2_mbus_framefmt formats[2];
+ struct v4l2_mbus_framefmt default_format;
+ const struct xvip_video_format *vip_format;
+ struct v4l2_event event;
+ struct mutex lock;
+ struct media_pad pads[XILINX_CSI_MEDIA_PADS];
+ unsigned int npads;
+ bool streaming;
+ bool suspended;
+};
+
+static const struct xcsi2rxss_feature xlnx_csi2rxss_v4_0 = {
+ .flags = XCSI_CLK_PROP,
+};
+
+static const struct xcsi2rxss_feature xlnx_csi2rxss_v2_0 = {
+ .flags = 0,
+};
+
+static const struct of_device_id xcsi2rxss_of_id_table[] = {
+ { .compatible = "xlnx,mipi-csi2-rx-subsystem-2.0",
+ .data = &xlnx_csi2rxss_v2_0 },
+ { .compatible = "xlnx,mipi-csi2-rx-subsystem-3.0",
+ .data = &xlnx_csi2rxss_v2_0 },
+ { .compatible = "xlnx,mipi-csi2-rx-subsystem-4.0",
+ .data = &xlnx_csi2rxss_v4_0 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, xcsi2rxss_of_id_table);
+
+static inline struct xcsi2rxss_state *
+to_xcsi2rxssstate(struct v4l2_subdev *subdev)
+{
+ return container_of(subdev, struct xcsi2rxss_state, subdev);
+}
+
+/*
+ * Regsiter related operations
+ */
+static inline u32 xcsi2rxss_read(struct xcsi2rxss_core *xcsi2rxss,
+ u32 addr)
+{
+ return ioread32(xcsi2rxss->iomem + addr);
+}
+
+static inline void xcsi2rxss_write(struct xcsi2rxss_core *xcsi2rxss,
+ u32 addr,
+ u32 value)
+{
+ iowrite32(value, xcsi2rxss->iomem + addr);
+}
+
+static inline void xcsi2rxss_clr(struct xcsi2rxss_core *xcsi2rxss,
+ u32 addr,
+ u32 clr)
+{
+ xcsi2rxss_write(xcsi2rxss,
+ addr,
+ xcsi2rxss_read(xcsi2rxss, addr) & ~clr);
+}
+
+static inline void xcsi2rxss_set(struct xcsi2rxss_core *xcsi2rxss,
+ u32 addr,
+ u32 set)
+{
+ xcsi2rxss_write(xcsi2rxss,
+ addr,
+ xcsi2rxss_read(xcsi2rxss, addr) | set);
+}
+
+static const struct pixel_format pixel_formats[] = {
+ { MIPI_CSI_DT_YUV_420_8B, "YUV420_8bit" },
+ { MIPI_CSI_DT_YUV_420_10B, "YUV420_10bit" },
+ { MIPI_CSI_DT_YUV_420_8B_LEGACY, "Legacy_YUV420_8bit" },
+ { MIPI_CSI_DT_YUV_420_8B_CSPS, "YUV420_8bit_CSPS" },
+ { MIPI_CSI_DT_YUV_420_10B_CSPS, "YUV420_10bit_CSPS" },
+ { MIPI_CSI_DT_YUV_422_8B, "YUV422_8bit" },
+ { MIPI_CSI_DT_YUV_422_10B, "YUV422_10bit" },
+ { MIPI_CSI_DT_RGB_444, "RGB444" },
+ { MIPI_CSI_DT_RGB_555, "RGB555" },
+ { MIPI_CSI_DT_RGB_565, "RGB565" },
+ { MIPI_CSI_DT_RGB_666, "RGB666" },
+ { MIPI_CSI_DT_RGB_888, "RGB888" },
+ { MIPI_CSI_DT_RAW_6, "RAW6" },
+ { MIPI_CSI_DT_RAW_7, "RAW7" },
+ { MIPI_CSI_DT_RAW_8, "RAW8" },
+ { MIPI_CSI_DT_RAW_10, "RAW10" },
+ { MIPI_CSI_DT_RAW_12, "RAW12" },
+ { MIPI_CSI_DT_RAW_14, "RAW14"},
+ { MIPI_CSI_DT_RAW_16, "RAW16"},
+ { MIPI_CSI_DT_RAW_20, "RAW20"}
+};
+
+static struct xcsi2rxss_event xcsi2rxss_events[] = {
+ { XCSI_ISR_FR_MASK, "Frame Received", 0 },
+ { XCSI_ISR_VCX_MASK, "VCX Frame Errors", 0 },
+ { XCSI_ISR_ILC_MASK, "Invalid Lane Count Error", 0 },
+ { XCSI_ISR_SPFIFOF_MASK, "Short Packet FIFO OverFlow Error", 0 },
+ { XCSI_ISR_SPFIFONE_MASK, "Short Packet FIFO Not Empty", 0 },
+ { XCSI_ISR_SLBF_MASK, "Streamline Buffer Full Error", 0 },
+ { XCSI_ISR_STOP_MASK, "Lane Stop State", 0 },
+ { XCSI_ISR_SOTERR_MASK, "SOT Error", 0 },
+ { XCSI_ISR_SOTSYNCERR_MASK, "SOT Sync Error", 0 },
+ { XCSI_ISR_ECC2BERR_MASK, "2 Bit ECC Unrecoverable Error", 0 },
+ { XCSI_ISR_ECC1BERR_MASK, "1 Bit ECC Recoverable Error", 0 },
+ { XCSI_ISR_CRCERR_MASK, "CRC Error", 0 },
+ { XCSI_ISR_DATAIDERR_MASK, "Data Id Error", 0 },
+ { XCSI_ISR_VC3FSYNCERR_MASK, "Virtual Channel 3 Frame Sync Error", 0 },
+ { XCSI_ISR_VC3FLVLERR_MASK, "Virtual Channel 3 Frame Level Error", 0 },
+ { XCSI_ISR_VC2FSYNCERR_MASK, "Virtual Channel 2 Frame Sync Error", 0 },
+ { XCSI_ISR_VC2FLVLERR_MASK, "Virtual Channel 2 Frame Level Error", 0 },
+ { XCSI_ISR_VC1FSYNCERR_MASK, "Virtual Channel 1 Frame Sync Error", 0 },
+ { XCSI_ISR_VC1FLVLERR_MASK, "Virtual Channel 1 Frame Level Error", 0 },
+ { XCSI_ISR_VC0FSYNCERR_MASK, "Virtual Channel 0 Frame Sync Error", 0 },
+ { XCSI_ISR_VC0FLVLERR_MASK, "Virtual Channel 0 Frame Level Error", 0 }
+};
+
+#define XMIPICSISS_NUM_EVENTS ARRAY_SIZE(xcsi2rxss_events)
+
+#define XMIPICSISS_VCX_START (4)
+#define XMIPICSISS_MAX_VC (4)
+#define XMIPICSISS_MAX_VCX (16)
+
+/* There are 2 events frame sync and frame level error per VC */
+#define XMIPICSISS_VCX_NUM_EVENTS ((XMIPICSISS_MAX_VCX -\
+ XMIPICSISS_MAX_VC) * 2)
+
+/**
+ * xcsi2rxss_clr_and_set - Clear and set the register with a bitmask
+ * @xcsi2rxss: Xilinx MIPI CSI2 Rx Subsystem subdev core struct
+ * @addr: address of register
+ * @clr: bitmask to be cleared
+ * @set: bitmask to be set
+ *
+ * Clear a bit(s) of mask @clr in the register at address @addr, then set
+ * a bit(s) of mask @set in the register after.
+ */
+static void xcsi2rxss_clr_and_set(struct xcsi2rxss_core *xcsi2rxss,
+ u32 addr, u32 clr, u32 set)
+{
+ u32 reg;
+
+ reg = xcsi2rxss_read(xcsi2rxss, addr);
+ reg &= ~clr;
+ reg |= set;
+ xcsi2rxss_write(xcsi2rxss, addr, reg);
+}
+
+/**
+ * xcsi2rxss_pxlfmtstrtodt - Convert pixel format string got from dts
+ * to data type.
+ * @pxlfmtstr: String obtained while parsing device node
+ *
+ * This function takes a CSI pixel format string obtained while parsing
+ * device tree node and converts it to data type.
+ *
+ * Eg. "RAW8" string is converted to 0x2A.
+ * Refer to MIPI CSI2 specification for details.
+ *
+ * Return: Equivalent pixel format value from table
+ */
+static u32 xcsi2rxss_pxlfmtstrtodt(const char *pxlfmtstr)
+{
+ u32 Index;
+ u32 MaxEntries = ARRAY_SIZE(pixel_formats);
+
+ for (Index = 0; Index < MaxEntries; Index++) {
+ if (!strncmp(pixel_formats[Index].PixelFormatStr,
+ pxlfmtstr, MAX_XIL_CSIDT_STR_LENGTH))
+ return pixel_formats[Index].PixelFormat;
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * xcsi2rxss_pxlfmtdttostr - Convert pixel format data type to string.
+ * @datatype: MIPI CSI-2 Data Type
+ *
+ * This function takes a CSI pixel format data type and returns a
+ * pointer to the string name.
+ *
+ * Eg. 0x2A returns "RAW8" string.
+ * Refer to MIPI CSI2 specification for details.
+ *
+ * Return: Equivalent pixel format string from table
+ */
+static const char *xcsi2rxss_pxlfmtdttostr(u32 datatype)
+{
+ u32 Index;
+ u32 MaxEntries = ARRAY_SIZE(pixel_formats);
+
+ for (Index = 0; Index < MaxEntries; Index++) {
+ if (pixel_formats[Index].PixelFormat == datatype)
+ return pixel_formats[Index].PixelFormatStr;
+ }
+
+ return NULL;
+}
+
+/**
+ * xcsi2rxss_enable - Enable or disable the CSI Core
+ * @core: Core Xilinx CSI2 Rx Subsystem structure pointer
+ * @flag: true for enabling, false for disabling
+ *
+ * This function enables/disables the MIPI CSI2 Rx Subsystem core.
+ * After enabling the CSI2 Rx core, the DPHY is enabled in case the
+ * register interface for it is present.
+ */
+static void xcsi2rxss_enable(struct xcsi2rxss_core *core, bool flag)
+{
+ u32 DphyCtrlRegOffset = core->dphy_offset + XDPHY_CTRLREG_OFFSET;
+
+ if (flag) {
+ xcsi2rxss_write(core, XCSI_CCR_OFFSET, XCSI_CCR_COREENB_MASK);
+ if (core->dphy_present)
+ xcsi2rxss_write(core, DphyCtrlRegOffset,
+ XDPHY_CTRLREG_DPHYEN_MASK);
+ } else {
+ xcsi2rxss_write(core, XCSI_CCR_OFFSET, 0);
+ if (core->dphy_present)
+ xcsi2rxss_write(core, DphyCtrlRegOffset, 0);
+ }
+
+}
+
+/**
+ * xcsi2rxss_interrupts_enable - Enable or disable CSI interrupts
+ * @core: Core Xilinx CSI2 Rx Subsystem structure pointer
+ * @flag: true for enabling, false for disabling
+ *
+ * This function enables/disables the interrupts for the MIPI CSI2
+ * Rx Subsystem.
+ */
+static void xcsi2rxss_interrupts_enable(struct xcsi2rxss_core *core, bool flag)
+{
+ if (flag) {
+ xcsi2rxss_clr(core, XCSI_GIER_OFFSET, XCSI_GIER_GIE_MASK);
+ xcsi2rxss_write(core, XCSI_IER_OFFSET, XCSI_INTR_MASK);
+ xcsi2rxss_set(core, XCSI_GIER_OFFSET, XCSI_GIER_GIE_MASK);
+ } else {
+ xcsi2rxss_clr(core, XCSI_IER_OFFSET, XCSI_INTR_MASK);
+ xcsi2rxss_clr(core, XCSI_GIER_OFFSET, XCSI_GIER_GIE_MASK);
+ }
+}
+
+/**
+ * xcsi2rxss_reset - Does a soft reset of the MIPI CSI2 Rx Subsystem
+ * @core: Core Xilinx CSI2 Rx Subsystem structure pointer
+ *
+ * Return: 0 - on success OR -ETIME if reset times out
+ */
+static int xcsi2rxss_reset(struct xcsi2rxss_core *core)
+{
+ u32 Timeout = XCSI_TIMEOUT_VAL;
+
+ xcsi2rxss_set(core, XCSI_CCR_OFFSET, XCSI_CCR_SOFTRESET_MASK);
+
+ while (xcsi2rxss_read(core, XCSI_CSR_OFFSET) & XCSI_CSR_RIPCD_MASK) {
+ if (Timeout == 0) {
+ dev_err(core->dev, "Xilinx CSI2 Rx Subsystem Soft Reset Timeout!\n");
+ return -ETIME;
+ }
+
+ Timeout--;
+ udelay(1);
+ }
+
+ xcsi2rxss_clr(core, XCSI_CCR_OFFSET, XCSI_CCR_SOFTRESET_MASK);
+ return 0;
+}
+
+/**
+ * xcsi2rxss_irq_handler - Interrupt handler for CSI-2
+ * @irq: IRQ number
+ * @dev_id: Pointer to device state
+ *
+ * In the interrupt handler, a list of event counters are updated for
+ * corresponding interrupts. This is useful to get status / debug.
+ * If the short packet FIFO not empty or overflow interrupt is received
+ * capture the short packet and notify of event occurrence
+ *
+ * Return: IRQ_HANDLED after handling interrupts
+ */
+static irqreturn_t xcsi2rxss_irq_handler(int irq, void *dev_id)
+{
+ struct xcsi2rxss_state *state = (struct xcsi2rxss_state *)dev_id;
+ struct xcsi2rxss_core *core = &state->core;
+ u32 status;
+
+ status = xcsi2rxss_read(core, XCSI_ISR_OFFSET) & XCSI_INTR_MASK;
+ dev_dbg(core->dev, "interrupt status = 0x%08x\n", status);
+
+ if (!status)
+ return IRQ_NONE;
+
+ if (status & XCSI_ISR_SPFIFONE_MASK) {
+
+ memset(&state->event, 0, sizeof(state->event));
+
+ state->event.type = V4L2_EVENT_XLNXCSIRX_SPKT;
+
+ *((u32 *)(&state->event.u.data)) =
+ xcsi2rxss_read(core, XCSI_SPKTR_OFFSET);
+
+ v4l2_subdev_notify_event(&state->subdev, &state->event);
+ }
+
+ if (status & XCSI_ISR_SPFIFOF_MASK) {
+ dev_alert(core->dev, "Short packet FIFO overflowed\n");
+
+ memset(&state->event, 0, sizeof(state->event));
+
+ state->event.type = V4L2_EVENT_XLNXCSIRX_SPKT_OVF;
+
+ v4l2_subdev_notify_event(&state->subdev, &state->event);
+ }
+
+ if (status & XCSI_ISR_SLBF_MASK) {
+ dev_alert(core->dev, "Stream Line Buffer Full!\n");
+
+ memset(&state->event, 0, sizeof(state->event));
+
+ state->event.type = V4L2_EVENT_XLNXCSIRX_SLBF;
+
+ v4l2_subdev_notify_event(&state->subdev, &state->event);
+ }
+
+ if (status & XCSI_ISR_ALLINTR_MASK) {
+ unsigned int i;
+
+ for (i = 0; i < XMIPICSISS_NUM_EVENTS; i++) {
+ if (!(status & core->events[i].mask))
+ continue;
+ core->events[i].counter++;
+ dev_dbg(core->dev, "%s: %d\n", core->events[i].name,
+ core->events[i].counter);
+ }
+
+ if (status & XCSI_ISR_VCX_MASK && core->en_vcx) {
+ u32 vcxstatus;
+
+ vcxstatus = xcsi2rxss_read(core, XCSI_VCXR_OFFSET);
+ vcxstatus &= XCSI_VCXR_MASK;
+ for (i = 0; i < XMIPICSISS_VCX_NUM_EVENTS; i++) {
+ if (!(vcxstatus & core->vcx_events[i].mask))
+ continue;
+ core->vcx_events[i].counter++;
+ }
+ xcsi2rxss_write(core, XCSI_VCXR_OFFSET, vcxstatus);
+ }
+ }
+
+ xcsi2rxss_write(core, XCSI_ISR_OFFSET, status);
+
+ return IRQ_HANDLED;
+}
+
+static void xcsi2rxss_reset_event_counters(struct xcsi2rxss_state *state)
+{
+ int i;
+
+ for (i = 0; i < XMIPICSISS_NUM_EVENTS; i++)
+ state->core.events[i].counter = 0;
+
+ if (!state->core.en_vcx)
+ return;
+
+ for (i = 0; i < XMIPICSISS_VCX_NUM_EVENTS; i++)
+ state->core.vcx_events[i].counter = 0;
+}
+
+/**
+ * xcsi2rxss_log_counters - Print out the event counters.
+ * @state: Pointer to device state
+ *
+ */
+static void xcsi2rxss_log_counters(struct xcsi2rxss_state *state)
+{
+ int i;
+
+ for (i = 0; i < XMIPICSISS_NUM_EVENTS; i++) {
+ if (state->core.events[i].counter > 0)
+ v4l2_info(&state->subdev, "%s events: %d\n",
+ state->core.events[i].name,
+ state->core.events[i].counter);
+ }
+
+ if (!state->core.en_vcx)
+ return;
+
+ for (i = 0; i < XMIPICSISS_VCX_NUM_EVENTS; i++) {
+ if (state->core.vcx_events[i].counter > 0)
+ v4l2_info(&state->subdev,
+ "VC %d Frame %s error vcx events: %d\n",
+ (i / 2) + XMIPICSISS_VCX_START,
+ i & 1 ? "Sync" : "Level",
+ state->core.vcx_events[i].counter);
+ }
+}
+
+/**
+ * xcsi2rxss_log_status - Logs the status of the CSI-2 Receiver
+ * @sd: Pointer to V4L2 subdevice structure
+ *
+ * This function prints the current status of Xilinx MIPI CSI-2
+ *
+ * Return: 0 on success
+ */
+static int xcsi2rxss_log_status(struct v4l2_subdev *sd)
+{
+ struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd);
+ struct xcsi2rxss_core *core = &xcsi2rxss->core;
+ u32 reg, data, i, max_vc;
+
+ mutex_lock(&xcsi2rxss->lock);
+
+ xcsi2rxss_log_counters(xcsi2rxss);
+
+ v4l2_info(sd, "***** Core Status *****\n");
+ data = xcsi2rxss_read(core, XCSI_CSR_OFFSET);
+ v4l2_info(sd, "Short Packet FIFO Full = %s\n",
+ XCSI_GET_BITSET_STR(data, XCSI_CSR_SPFIFOFULL_MASK));
+ v4l2_info(sd, "Short Packet FIFO Not Empty = %s\n",
+ XCSI_GET_BITSET_STR(data, XCSI_CSR_SPFIFONE_MASK));
+ v4l2_info(sd, "Stream line buffer full = %s\n",
+ XCSI_GET_BITSET_STR(data, XCSI_CSR_SLBF_MASK));
+ v4l2_info(sd, "Soft reset/Core disable in progress = %s\n",
+ XCSI_GET_BITSET_STR(data, XCSI_CSR_RIPCD_MASK));
+
+ /* Clk & Lane Info */
+ v4l2_info(sd, "******** Clock Lane Info *********\n");
+ data = xcsi2rxss_read(core, XCSI_CLKINFR_OFFSET);
+ v4l2_info(sd, "Clock Lane in Stop State = %s\n",
+ XCSI_GET_BITSET_STR(data, XCSI_CLKINFR_STOP_MASK));
+
+ v4l2_info(sd, "******** Data Lane Info *********\n");
+ v4l2_info(sd, "Lane\tSoT Error\tSoT Sync Error\tStop State\n");
+ reg = XCSI_L0INFR_OFFSET;
+ for (i = 0; i < 4; i++) {
+ data = xcsi2rxss_read(core, reg);
+
+ v4l2_info(sd, "%d\t%s\t\t%s\t\t%s\n",
+ i,
+ XCSI_GET_BITSET_STR(data, XCSI_LXINFR_SOTERR_MASK),
+ XCSI_GET_BITSET_STR(data, XCSI_LXINFR_SOTSYNCERR_MASK),
+ XCSI_GET_BITSET_STR(data, XCSI_LXINFR_STOP_MASK));
+
+ reg += 4;
+ }
+
+ /* Virtual Channel Image Information */
+ v4l2_info(sd, "********** Virtual Channel Info ************\n");
+ v4l2_info(sd, "VC\tLine Count\tByte Count\tData Type\n");
+ if (core->en_vcx)
+ max_vc = XMIPICSISS_MAX_VCX;
+ else
+ max_vc = XMIPICSISS_MAX_VC;
+
+ reg = XCSI_VC0INF1R_OFFSET;
+ for (i = 0; i < max_vc; i++) {
+ u32 line_count, byte_count, data_type;
+ char *datatypestr;
+
+ /* Get line and byte count from VCXINFR1 Register */
+ data = xcsi2rxss_read(core, reg);
+ byte_count = (data & XCSI_VCXINF1R_BYTECOUNT_MASK) >>
+ XCSI_VCXINF1R_BYTECOUNT_SHIFT;
+ line_count = (data & XCSI_VCXINF1R_LINECOUNT_MASK) >>
+ XCSI_VCXINF1R_LINECOUNT_SHIFT;
+
+ /* Get data type from VCXINFR2 Register */
+ reg += 4;
+ data = xcsi2rxss_read(core, reg);
+ data_type = (data & XCSI_VCXINF2R_DATATYPE_MASK) >>
+ XCSI_VCXINF2R_DATATYPE_SHIFT;
+ datatypestr = (char *)xcsi2rxss_pxlfmtdttostr(data_type);
+
+ v4l2_info(sd, "%d\t%d\t\t%d\t\t%s\n",
+ i, line_count, byte_count, datatypestr);
+
+ /* Move to next pair of VC Info registers */
+ reg += 4;
+ }
+
+ mutex_unlock(&xcsi2rxss->lock);
+
+ return 0;
+}
+
+/*
+ * xcsi2rxss_subscribe_event - Subscribe to the custom short packet
+ * receive event.
+ * @sd: V4L2 Sub device
+ * @fh: V4L2 File Handle
+ * @sub: Subcribe event structure
+ *
+ * There are two types of events to be subscribed.
+ *
+ * First is to register for receiving a short packet.
+ * The short packets received are queued up in a FIFO.
+ * On reception of a short packet, an event will be generated
+ * with the short packet contents copied to its data area.
+ * Application subscribed to this event will poll for POLLPRI.
+ * On getting the event, the app dequeues the event to get the short packet
+ * data.
+ *
+ * Second is to register for Short packet FIFO overflow
+ * In case the rate of receiving short packets is high and
+ * the short packet FIFO overflows, this event will be triggered.
+ *
+ * Return: 0 on success, errors otherwise
+ */
+static int xcsi2rxss_subscribe_event(struct v4l2_subdev *sd,
+ struct v4l2_fh *fh,
+ struct v4l2_event_subscription *sub)
+{
+ int ret;
+ struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd);
+
+ mutex_lock(&xcsi2rxss->lock);
+
+ switch (sub->type) {
+ case V4L2_EVENT_XLNXCSIRX_SPKT:
+ case V4L2_EVENT_XLNXCSIRX_SPKT_OVF:
+ case V4L2_EVENT_XLNXCSIRX_SLBF:
+ ret = v4l2_event_subscribe(fh, sub, XCSI_MAX_SPKT, NULL);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&xcsi2rxss->lock);
+
+ return ret;
+}
+
+/**
+ * xcsi2rxss_unsubscribe_event - Unsubscribe from all events registered
+ * @sd: V4L2 Sub device
+ * @fh: V4L2 file handle
+ * @sub: pointer to Event unsubscription structure
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int xcsi2rxss_unsubscribe_event(struct v4l2_subdev *sd,
+ struct v4l2_fh *fh,
+ struct v4l2_event_subscription *sub)
+{
+ int ret = 0;
+ struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd);
+
+ mutex_lock(&xcsi2rxss->lock);
+ ret = v4l2_event_unsubscribe(fh, sub);
+ mutex_unlock(&xcsi2rxss->lock);
+
+ return ret;
+}
+
+/**
+ * xcsi2rxss_s_ctrl - This is used to set the Xilinx MIPI CSI-2 V4L2 controls
+ * @ctrl: V4L2 control to be set
+ *
+ * This function is used to set the V4L2 controls for the Xilinx MIPI
+ * CSI-2 Rx Subsystem. It is used to set the active lanes in the system.
+ * The event counters can be reset.
+ *
+ * Return: 0 on success, errors otherwise
+ */
+static int xcsi2rxss_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ int ret = 0;
+ u32 Timeout = XCSI_TIMEOUT_VAL;
+ u32 active_lanes = 1;
+
+ struct xcsi2rxss_state *xcsi2rxss =
+ container_of(ctrl->handler,
+ struct xcsi2rxss_state, ctrl_handler);
+ struct xcsi2rxss_core *core = &xcsi2rxss->core;
+
+ mutex_lock(&xcsi2rxss->lock);
+
+ switch (ctrl->id) {
+ case V4L2_CID_XILINX_MIPICSISS_ACT_LANES:
+ /*
+ * This will be called only when "Enable Active Lanes" parameter
+ * is set in design
+ */
+ xcsi2rxss_clr_and_set(core, XCSI_PCR_OFFSET,
+ XCSI_PCR_ACTLANES_MASK, ctrl->val - 1);
+
+ /*
+ * If the core is enabled, wait for active lanes to be
+ * set.
+ *
+ * If core is disabled or there is no clock from DPHY Tx
+ * then the read back won't reflect the updated value
+ * as the PPI clock will not be present.
+ */
+
+ if (core->dphy_present) {
+ u32 dphyclkstatregoffset = core->dphy_offset +
+ XDPHY_CLKSTATREG_OFFSET;
+
+ u32 dphyclkstat =
+ xcsi2rxss_read(core, dphyclkstatregoffset) &
+ XDPHY_CLKSTATREG_MODE_MASK;
+
+ u32 coreenable =
+ xcsi2rxss_read(core, XCSI_CCR_OFFSET) &
+ XCSI_CCR_COREENB_MASK;
+
+ char lpmstr[] = "Low Power";
+ char hsmstr[] = "High Speed";
+ char esmstr[] = "Escape";
+ char *modestr;
+
+ switch (dphyclkstat) {
+ case 0:
+ modestr = lpmstr;
+ break;
+ case 1:
+ modestr = hsmstr;
+ break;
+ case 2:
+ modestr = esmstr;
+ break;
+ default:
+ modestr = NULL;
+ break;
+ }
+
+ dev_dbg(core->dev, "DPHY Clock Lane in %s mode\n",
+ modestr);
+
+ if ((dphyclkstat == XDPHY_HI_SPEED_MODE) &&
+ coreenable) {
+
+ /* Wait for core to apply new active lanes */
+ while (Timeout--)
+ udelay(1);
+
+ active_lanes =
+ xcsi2rxss_read(core, XCSI_PCR_OFFSET);
+ active_lanes &= XCSI_PCR_ACTLANES_MASK;
+ active_lanes++;
+
+ if (active_lanes != ctrl->val) {
+ dev_err(core->dev, "Failed to set active lanes!\n");
+ ret = -EAGAIN;
+ }
+ }
+ } else {
+ dev_dbg(core->dev, "No read back as no DPHY present.\n");
+ }
+
+ dev_dbg(core->dev, "Set active lanes: requested = %d, active = %d\n",
+ ctrl->val, active_lanes);
+ break;
+ case V4L2_CID_XILINX_MIPICSISS_RESET_COUNTERS:
+ xcsi2rxss_reset_event_counters(xcsi2rxss);
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&xcsi2rxss->lock);
+
+ return ret;
+}
+
+/**
+ * xcsi2rxss_g_volatile_ctrl - get the Xilinx MIPI CSI-2 Rx controls
+ * @ctrl: Pointer to V4L2 control
+ *
+ * This is used to get the number of frames received by the Xilinx
+ * MIPI CSI-2 Rx.
+ *
+ * Return: 0 on success, errors otherwise
+ */
+static int xcsi2rxss_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ int ret = 0;
+ struct xcsi2rxss_state *xcsi2rxss =
+ container_of(ctrl->handler,
+ struct xcsi2rxss_state, ctrl_handler);
+
+ mutex_lock(&xcsi2rxss->lock);
+
+ switch (ctrl->id) {
+ case V4L2_CID_XILINX_MIPICSISS_FRAME_COUNTER:
+ ctrl->val = xcsi2rxss->core.events[0].counter;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&xcsi2rxss->lock);
+
+ return ret;
+}
+
+static int xcsi2rxss_start_stream(struct xcsi2rxss_state *xcsi2rxss)
+{
+ int ret;
+
+ xcsi2rxss_enable(&xcsi2rxss->core, true);
+
+ ret = xcsi2rxss_reset(&xcsi2rxss->core);
+ if (ret < 0)
+ return ret;
+
+ xcsi2rxss_interrupts_enable(&xcsi2rxss->core, true);
+
+ return 0;
+}
+
+static void xcsi2rxss_stop_stream(struct xcsi2rxss_state *xcsi2rxss)
+{
+ xcsi2rxss_interrupts_enable(&xcsi2rxss->core, false);
+ xcsi2rxss_enable(&xcsi2rxss->core, false);
+}
+
+/**
+ * xcsi2rxss_s_stream - It is used to start/stop the streaming.
+ * @sd: V4L2 Sub device
+ * @enable: Flag (True / False)
+ *
+ * This function controls the start or stop of streaming for the
+ * Xilinx MIPI CSI-2 Rx Subsystem provided the device isn't in
+ * suspended state.
+ *
+ * Return: 0 on success, errors otherwise
+ */
+static int xcsi2rxss_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ int ret = 0;
+ struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd);
+
+ mutex_lock(&xcsi2rxss->lock);
+
+ if (xcsi2rxss->suspended) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (enable) {
+ if (!xcsi2rxss->streaming) {
+ /* reset the event counters */
+ xcsi2rxss_reset_event_counters(xcsi2rxss);
+
+ ret = xcsi2rxss_start_stream(xcsi2rxss);
+ if (ret == 0)
+ xcsi2rxss->streaming = true;
+ }
+ } else {
+ if (xcsi2rxss->streaming) {
+ xcsi2rxss_stop_stream(xcsi2rxss);
+ xcsi2rxss->streaming = false;
+ }
+ }
+unlock:
+ mutex_unlock(&xcsi2rxss->lock);
+ return ret;
+}
+
+static struct v4l2_mbus_framefmt *
+__xcsi2rxss_get_pad_format(struct xcsi2rxss_state *xcsi2rxss,
+ struct v4l2_subdev_pad_config *cfg,
+ unsigned int pad, u32 which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_format(&xcsi2rxss->subdev, cfg, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &xcsi2rxss->formats[pad];
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * xcsi2rxss_get_format - Get the pad format
+ * @sd: Pointer to V4L2 Sub device structure
+ * @cfg: Pointer to sub device pad information structure
+ * @fmt: Pointer to pad level media bus format
+ *
+ * This function is used to get the pad format information.
+ *
+ * Return: 0 on success
+ */
+static int xcsi2rxss_get_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+
+ struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd);
+
+ mutex_lock(&xcsi2rxss->lock);
+ fmt->format = *__xcsi2rxss_get_pad_format(xcsi2rxss, cfg,
+ fmt->pad, fmt->which);
+ mutex_unlock(&xcsi2rxss->lock);
+
+ return 0;
+}
+
+/**
+ * xcsi2rxss_set_format - This is used to set the pad format
+ * @sd: Pointer to V4L2 Sub device structure
+ * @cfg: Pointer to sub device pad information structure
+ * @fmt: Pointer to pad level media bus format
+ *
+ * This function is used to set the pad format.
+ * Since the pad format is fixed in hardware, it can't be
+ * modified on run time. So when a format set is requested by
+ * application, all parameters except the format type is
+ * saved for the pad and the original pad format is sent
+ * back to the application.
+ *
+ * Return: 0 on success
+ */
+static int xcsi2rxss_set_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct v4l2_mbus_framefmt *__format;
+ struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd);
+ struct xcsi2rxss_core *core = &xcsi2rxss->core;
+ u32 code;
+
+ mutex_lock(&xcsi2rxss->lock);
+
+ /*
+ * Only the format->code parameter matters for CSI as the
+ * CSI format cannot be changed at runtime.
+ * Ensure that format to set is copied to over to CSI pad format
+ */
+ __format = __xcsi2rxss_get_pad_format(xcsi2rxss, cfg,
+ fmt->pad, fmt->which);
+
+ /* Save the pad format code */
+ code = __format->code;
+
+ /* If the bayer pattern to be set is SXXXX8 then only 1x8 type
+ * is supported and core's data type doesn't matter.
+ * In case the bayer pattern being set is SXXX10 then only
+ * 1x10 type are supported and core should be configured for RAW10.
+ * In case the bayer pattern being set is SXXX12 then only
+ * 1x12 type are supported and core should be configured for RAW12.
+ *
+ * Otherwise don't allow change.
+ */
+ if (((fmt->format.code == MEDIA_BUS_FMT_SBGGR8_1X8) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SGBRG8_1X8) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SGRBG8_1X8) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SRGGB8_1X8))
+ || ((core->datatype == MIPI_CSI_DT_RAW_10) &&
+ ((fmt->format.code == MEDIA_BUS_FMT_SBGGR10_1X10) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SGBRG10_1X10) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SGRBG10_1X10) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SRGGB10_1X10)))
+ || ((core->datatype == MIPI_CSI_DT_RAW_12) &&
+ ((fmt->format.code == MEDIA_BUS_FMT_SBGGR12_1X12) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SGBRG12_1X12) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SGRBG12_1X12) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SRGGB12_1X12))) ||
+ ((core->datatype == MIPI_CSI_DT_RAW_16) &&
+ ((fmt->format.code == MEDIA_BUS_FMT_SBGGR16_1X16) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SGBRG16_1X16) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SGRBG16_1X16) ||
+ (fmt->format.code == MEDIA_BUS_FMT_SRGGB16_1X16))))
+
+ /* Copy over the format to be set */
+ *__format = fmt->format;
+ else {
+ /* Restore the original pad format code */
+ fmt->format.code = code;
+ __format->code = code;
+ __format->width = fmt->format.width;
+ __format->height = fmt->format.height;
+ }
+
+ mutex_unlock(&xcsi2rxss->lock);
+
+ return 0;
+}
+
+/**
+ * xcsi2rxss_open - Called on v4l2_open()
+ * @sd: Pointer to V4L2 sub device structure
+ * @fh: Pointer to V4L2 File handle
+ *
+ * This function is called on v4l2_open(). It sets the default format
+ * for both pads.
+ *
+ * Return: 0 on success
+ */
+static int xcsi2rxss_open(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_mbus_framefmt *format;
+ struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd);
+
+ format = v4l2_subdev_get_try_format(sd, fh->pad, 0);
+ *format = xcsi2rxss->default_format;
+
+ format = v4l2_subdev_get_try_format(sd, fh->pad, 1);
+ *format = xcsi2rxss->default_format;
+
+ return 0;
+}
+
+static int xcsi2rxss_close(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+
+static const struct media_entity_operations xcsi2rxss_media_ops = {
+ .link_validate = v4l2_subdev_link_validate
+};
+
+static const struct v4l2_ctrl_ops xcsi2rxss_ctrl_ops = {
+ .g_volatile_ctrl = xcsi2rxss_g_volatile_ctrl,
+ .s_ctrl = xcsi2rxss_s_ctrl
+};
+
+static struct v4l2_ctrl_config xcsi2rxss_ctrls[] = {
+ {
+ .ops = &xcsi2rxss_ctrl_ops,
+ .id = V4L2_CID_XILINX_MIPICSISS_ACT_LANES,
+ .name = "MIPI CSI2 Rx Subsystem: Active Lanes",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 4,
+ .step = 1,
+ .def = 1,
+ }, {
+ .ops = &xcsi2rxss_ctrl_ops,
+ .id = V4L2_CID_XILINX_MIPICSISS_FRAME_COUNTER,
+ .name = "MIPI CSI2 Rx Subsystem: Frames Received Counter",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = 0xFFFFFFFF,
+ .step = 1,
+ .def = 0,
+ .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY,
+ }, {
+ .ops = &xcsi2rxss_ctrl_ops,
+ .id = V4L2_CID_XILINX_MIPICSISS_RESET_COUNTERS,
+ .name = "MIPI CSI2 Rx Subsystem: Reset Counters",
+ .type = V4L2_CTRL_TYPE_BUTTON,
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ .def = 0,
+ .flags = V4L2_CTRL_FLAG_WRITE_ONLY,
+ }
+};
+
+static const struct v4l2_subdev_core_ops xcsi2rxss_core_ops = {
+ .log_status = xcsi2rxss_log_status,
+ .subscribe_event = xcsi2rxss_subscribe_event,
+ .unsubscribe_event = xcsi2rxss_unsubscribe_event
+};
+
+static struct v4l2_subdev_video_ops xcsi2rxss_video_ops = {
+ .s_stream = xcsi2rxss_s_stream
+};
+
+static struct v4l2_subdev_pad_ops xcsi2rxss_pad_ops = {
+ .get_fmt = xcsi2rxss_get_format,
+ .set_fmt = xcsi2rxss_set_format,
+};
+
+static struct v4l2_subdev_ops xcsi2rxss_ops = {
+ .core = &xcsi2rxss_core_ops,
+ .video = &xcsi2rxss_video_ops,
+ .pad = &xcsi2rxss_pad_ops
+};
+
+static const struct v4l2_subdev_internal_ops xcsi2rxss_internal_ops = {
+ .open = xcsi2rxss_open,
+ .close = xcsi2rxss_close
+};
+
+/* -----------------------------------------------------------------------------
+ * Power Management
+ */
+
+/*
+ * xcsi2rxss_pm_suspend - Function called on Power Suspend
+ * @dev: Pointer to device structure
+ *
+ * On power suspend the CSI-2 Core is disabled if the device isn't
+ * in suspended state and is streaming.
+ *
+ * Return: 0 on success
+ */
+static int __maybe_unused xcsi2rxss_pm_suspend(struct device *dev)
+{
+ struct xcsi2rxss_state *xcsi2rxss = dev_get_drvdata(dev);
+
+ mutex_lock(&xcsi2rxss->lock);
+
+ if (!xcsi2rxss->suspended && xcsi2rxss->streaming)
+ xcsi2rxss_clr(&xcsi2rxss->core,
+ XCSI_CCR_OFFSET, XCSI_CCR_COREENB_MASK);
+
+ xcsi2rxss->suspended = true;
+
+ mutex_unlock(&xcsi2rxss->lock);
+
+ return 0;
+}
+
+/*
+ * xcsi2rxss_pm_resume - Function called on Power Resume
+ * @dev: Pointer to device structure
+ *
+ * On power resume the CSI-2 Core is enabled when it is in suspended state
+ * and prior to entering suspended state it was streaming.
+ *
+ * Return: 0 on success
+ */
+static int __maybe_unused xcsi2rxss_pm_resume(struct device *dev)
+{
+ struct xcsi2rxss_state *xcsi2rxss = dev_get_drvdata(dev);
+
+ mutex_lock(&xcsi2rxss->lock);
+
+ if ((xcsi2rxss->suspended) && (xcsi2rxss->streaming))
+ xcsi2rxss_set(&xcsi2rxss->core,
+ XCSI_CCR_OFFSET, XCSI_CCR_COREENB_MASK);
+
+ xcsi2rxss->suspended = false;
+
+ mutex_unlock(&xcsi2rxss->lock);
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Platform Device Driver
+ */
+
+static int xcsi2rxss_parse_of(struct xcsi2rxss_state *xcsi2rxss)
+{
+ struct device_node *node = xcsi2rxss->core.dev->of_node;
+ struct device_node *ports = NULL;
+ struct device_node *port = NULL;
+ unsigned int nports = 0;
+ struct xcsi2rxss_core *core = &xcsi2rxss->core;
+ int ret;
+ bool iic_present;
+
+ if (core->cfg->flags & XCSI_CLK_PROP) {
+ core->lite_aclk = devm_clk_get(core->dev, "lite_aclk");
+ if (IS_ERR(core->lite_aclk)) {
+ ret = PTR_ERR(core->lite_aclk);
+ dev_err(core->dev, "failed to get lite_aclk (%d)\n",
+ ret);
+ return ret;
+ }
+
+ core->video_aclk = devm_clk_get(core->dev, "video_aclk");
+ if (IS_ERR(core->video_aclk)) {
+ ret = PTR_ERR(core->video_aclk);
+ dev_err(core->dev, "failed to get video_aclk (%d)\n",
+ ret);
+ return ret;
+ }
+
+ core->dphy_clk_200M = devm_clk_get(core->dev, "dphy_clk_200M");
+ if (IS_ERR(core->dphy_clk_200M)) {
+ ret = PTR_ERR(core->dphy_clk_200M);
+ dev_err(core->dev, "failed to get dphy_clk_200M (%d)\n",
+ ret);
+ return ret;
+ }
+ } else {
+ dev_info(core->dev, "assuming all required clocks are enabled!\n");
+ }
+
+ core->dphy_present = of_property_read_bool(node, "xlnx,dphy-present");
+ dev_dbg(core->dev, "DPHY present property = %s\n",
+ core->dphy_present ? "Present" : "Absent");
+
+ iic_present = of_property_read_bool(node, "xlnx,iic-present");
+ dev_dbg(core->dev, "IIC present property = %s\n",
+ iic_present ? "Present" : "Absent");
+
+ if (core->dphy_present) {
+ if (iic_present)
+ core->dphy_offset = 0x20000;
+ else
+ core->dphy_offset = 0x10000;
+ }
+
+ ret = of_property_read_u32(node, "xlnx,max-lanes",
+ &core->max_num_lanes);
+ if (ret < 0) {
+ dev_err(core->dev, "missing xlnx,max-lanes property\n");
+ return ret;
+ }
+
+ if ((core->max_num_lanes > 4) || (core->max_num_lanes < 1)) {
+ dev_err(core->dev, "%d max lanes : invalid xlnx,max-lanes property\n",
+ core->max_num_lanes);
+ return -EINVAL;
+ }
+
+ core->en_vcx = of_property_read_bool(node, "xlnx,en-vcx");
+
+ ret = of_property_read_u32(node, "xlnx,vc", &core->vc);
+ if (ret < 0) {
+ dev_err(core->dev, "missing xlnx,vc property\n");
+ return ret;
+ }
+ if ((core->vc > XMIPICSISS_MAX_VC && !core->en_vcx) ||
+ (core->vc > XMIPICSISS_MAX_VCX && core->en_vcx)) {
+ dev_err(core->dev, "invalid virtual channel property value.\n");
+ return -EINVAL;
+ }
+
+ core->enable_active_lanes =
+ of_property_read_bool(node, "xlnx,en-active-lanes");
+ dev_dbg(core->dev, "Enable active lanes property = %s\n",
+ core->enable_active_lanes ? "Present" : "Absent");
+
+ ret = of_property_read_string(node, "xlnx,csi-pxl-format",
+ &core->pxlformat);
+ if (ret < 0) {
+ dev_err(core->dev, "missing xlnx,csi-pxl-format property\n");
+ return ret;
+ }
+
+ core->datatype = xcsi2rxss_pxlfmtstrtodt(core->pxlformat);
+ if ((core->datatype < MIPI_CSI_DT_YUV_420_8B) ||
+ (core->datatype > MIPI_CSI_DT_RAW_20)) {
+ dev_err(core->dev, "Invalid xlnx,csi-pxl-format string\n");
+ return -EINVAL;
+ }
+
+ core->vfb = of_property_read_bool(node, "xlnx,vfb");
+ dev_dbg(core->dev, "Video Format Bridge property = %s\n",
+ core->vfb ? "Present" : "Absent");
+
+ if (core->vfb) {
+ ret = of_property_read_u32(node, "xlnx,ppc", &core->ppc);
+ if ((ret < 0) || !((core->ppc == 1) ||
+ (core->ppc == 2) || (core->ppc == 4))) {
+ dev_err(core->dev, "Invalid xlnx,ppc property ret = %d ppc = %d\n",
+ ret, core->ppc);
+ return -EINVAL;
+ }
+ }
+
+ ports = of_get_child_by_name(node, "ports");
+ if (ports == NULL)
+ ports = node;
+
+ for_each_child_of_node(ports, port) {
+ int ret;
+ const struct xvip_video_format *format;
+ struct device_node *endpoint;
+ struct v4l2_fwnode_endpoint v4lendpoint;
+
+ if (!port->name || of_node_cmp(port->name, "port"))
+ continue;
+
+ /*
+ * Currently only a subset of VFB enabled formats present in
+ * xvip are supported in the driver.
+ *
+ * If the VFB is disabled, the pixels per clock don't matter.
+ * The data width is either 32 or 64 bit as selected in design.
+ *
+ * For e.g. If Data Type is RGB888, VFB is disabled and
+ * data width is 32 bits.
+ *
+ * Clk Cycle | Byte 0 | Byte 1 | Byte 2 | Byte 3
+ * -----------+----------+----------+----------+----------
+ * 1 | B0 | G0 | R0 | B1
+ * 2 | G1 | R1 | B2 | G2
+ * 3 | R2 | B3 | G3 | R3
+ */
+ format = xvip_of_get_format(port);
+ if (IS_ERR(format)) {
+ dev_err(core->dev, "invalid format in DT");
+ return PTR_ERR(format);
+ }
+
+ if (core->vfb &&
+ (format->vf_code != XVIP_VF_YUV_422) &&
+ (format->vf_code != XVIP_VF_RBG) &&
+ (format->vf_code != XVIP_VF_MONO_SENSOR)) {
+ dev_err(core->dev, "Invalid UG934 video format set.\n");
+ return -EINVAL;
+ }
+
+ /* Get and check the format description */
+ if (!xcsi2rxss->vip_format) {
+ xcsi2rxss->vip_format = format;
+ } else if (xcsi2rxss->vip_format != format) {
+ dev_err(core->dev, "in/out format mismatch in DT");
+ return -EINVAL;
+ }
+
+ endpoint = of_get_next_child(port, NULL);
+ if (!endpoint) {
+ dev_err(core->dev, "No port at\n");
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint),
+ &v4lendpoint);
+ if (ret) {
+ of_node_put(endpoint);
+ return ret;
+ }
+
+ of_node_put(endpoint);
+ dev_dbg(core->dev, "%s : port %d bus type = %d\n",
+ __func__, nports, v4lendpoint.bus_type);
+
+ if (v4lendpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
+ dev_dbg(core->dev, "%s : base.port = %d base.id = %d\n",
+ __func__,
+ v4lendpoint.base.port,
+ v4lendpoint.base.id);
+
+ dev_dbg(core->dev, "%s : mipi number lanes = %d\n",
+ __func__,
+ v4lendpoint.bus.mipi_csi2.num_data_lanes);
+ } else {
+ dev_dbg(core->dev, "%s : Not a CSI2 bus\n", __func__);
+ }
+
+ /* Count the number of ports. */
+ nports++;
+ }
+
+ if (nports != 2) {
+ dev_err(core->dev, "invalid number of ports %u\n", nports);
+ return -EINVAL;
+ }
+ xcsi2rxss->npads = nports;
+
+ /*Register interrupt handler */
+ core->irq = irq_of_parse_and_map(node, 0);
+
+ ret = devm_request_irq(core->dev, core->irq, xcsi2rxss_irq_handler,
+ IRQF_SHARED, "xilinx-csi2rxss", xcsi2rxss);
+ if (ret) {
+ dev_err(core->dev, "Err = %d Interrupt handler reg failed!\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int xcsi2rxss_probe(struct platform_device *pdev)
+{
+ struct v4l2_subdev *subdev;
+ struct xcsi2rxss_state *xcsi2rxss;
+ struct resource *res;
+ const struct of_device_id *match;
+ struct device_node *node = pdev->dev.of_node;
+ u32 i;
+ int ret;
+ int num_ctrls;
+
+ xcsi2rxss = devm_kzalloc(&pdev->dev, sizeof(*xcsi2rxss), GFP_KERNEL);
+ if (!xcsi2rxss)
+ return -ENOMEM;
+
+ mutex_init(&xcsi2rxss->lock);
+
+ xcsi2rxss->core.dev = &pdev->dev;
+
+ match = of_match_node(xcsi2rxss_of_id_table, node);
+ if (!match)
+ return -ENODEV;
+
+ xcsi2rxss->core.cfg = match->data;
+
+ ret = xcsi2rxss_parse_of(xcsi2rxss);
+ if (ret < 0)
+ return ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ xcsi2rxss->core.iomem = devm_ioremap_resource(xcsi2rxss->core.dev, res);
+ if (IS_ERR(xcsi2rxss->core.iomem))
+ return PTR_ERR(xcsi2rxss->core.iomem);
+
+ if (xcsi2rxss->core.cfg->flags & XCSI_CLK_PROP) {
+ unsigned long rate;
+
+ ret = clk_prepare_enable(xcsi2rxss->core.lite_aclk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable lite_aclk (%d)\n",
+ ret);
+ goto clk_err;
+ }
+
+ ret = clk_prepare_enable(xcsi2rxss->core.video_aclk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable video_aclk (%d)\n",
+ ret);
+ goto video_aclk_err;
+ }
+
+ ret = clk_prepare_enable(xcsi2rxss->core.dphy_clk_200M);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable dphy clk (%d)\n",
+ ret);
+ goto dphy_clk_err;
+ }
+
+ ret = clk_set_rate(xcsi2rxss->core.dphy_clk_200M,
+ XCSI_DPHY_CLK_REQ);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to set dphy clk rate (%d)\n",
+ ret);
+
+ goto all_clk_err;
+ }
+
+ rate = clk_get_rate(xcsi2rxss->core.dphy_clk_200M);
+ if (rate < XCSI_DPHY_CLK_MIN && rate > XCSI_DPHY_CLK_MAX) {
+ dev_err(&pdev->dev, "Err DPHY Clock = %lu\n",
+ rate);
+ ret = -EINVAL;
+ goto all_clk_err;
+ }
+ }
+
+ /*
+ * Reset and initialize the core.
+ */
+ xcsi2rxss_reset(&xcsi2rxss->core);
+
+ xcsi2rxss->core.events = (struct xcsi2rxss_event *)&xcsi2rxss_events;
+
+ if (xcsi2rxss->core.en_vcx) {
+ u32 alloc_size;
+
+ alloc_size = sizeof(struct xcsi2rxss_event) *
+ XMIPICSISS_VCX_NUM_EVENTS;
+ xcsi2rxss->core.vcx_events = devm_kzalloc(&pdev->dev,
+ alloc_size,
+ GFP_KERNEL);
+ if (!xcsi2rxss->core.vcx_events) {
+ mutex_destroy(&xcsi2rxss->lock);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < XMIPICSISS_VCX_NUM_EVENTS; i++)
+ xcsi2rxss->core.vcx_events[i].mask = 1 << i;
+ }
+
+ /* Initialize V4L2 subdevice and media entity */
+ xcsi2rxss->pads[0].flags = MEDIA_PAD_FL_SOURCE;
+ xcsi2rxss->pads[1].flags = MEDIA_PAD_FL_SINK;
+
+ /* Initialize the default format */
+ memset(&xcsi2rxss->default_format, 0,
+ sizeof(xcsi2rxss->default_format));
+ xcsi2rxss->default_format.code = xcsi2rxss->vip_format->code;
+ xcsi2rxss->default_format.field = V4L2_FIELD_NONE;
+ xcsi2rxss->default_format.colorspace = V4L2_COLORSPACE_SRGB;
+ xcsi2rxss->default_format.width = XCSI_DEFAULT_WIDTH;
+ xcsi2rxss->default_format.height = XCSI_DEFAULT_HEIGHT;
+
+ xcsi2rxss->formats[0] = xcsi2rxss->default_format;
+ xcsi2rxss->formats[1] = xcsi2rxss->default_format;
+
+ /* Initialize V4L2 subdevice and media entity */
+ subdev = &xcsi2rxss->subdev;
+ v4l2_subdev_init(subdev, &xcsi2rxss_ops);
+
+ subdev->dev = &pdev->dev;
+ subdev->internal_ops = &xcsi2rxss_internal_ops;
+ strlcpy(subdev->name, dev_name(&pdev->dev), sizeof(subdev->name));
+
+ subdev->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ subdev->entity.ops = &xcsi2rxss_media_ops;
+
+ v4l2_set_subdevdata(subdev, xcsi2rxss);
+
+ ret = media_entity_pads_init(&subdev->entity, 2, xcsi2rxss->pads);
+ if (ret < 0)
+ goto error;
+
+ /*
+ * In case the Enable Active Lanes config parameter is not set,
+ * dynamic lane reconfiguration is not allowed.
+ * So V4L2_CID_XILINX_MIPICSISS_ACT_LANES ctrl will not be registered.
+ * Accordingly allocate the number of controls
+ */
+ num_ctrls = ARRAY_SIZE(xcsi2rxss_ctrls);
+
+ if (!xcsi2rxss->core.enable_active_lanes)
+ num_ctrls--;
+
+ dev_dbg(xcsi2rxss->core.dev, "# of ctrls = %d\n", num_ctrls);
+
+ v4l2_ctrl_handler_init(&xcsi2rxss->ctrl_handler, num_ctrls);
+
+ for (i = 0; i < ARRAY_SIZE(xcsi2rxss_ctrls); i++) {
+ struct v4l2_ctrl *ctrl;
+
+ if (xcsi2rxss_ctrls[i].id ==
+ V4L2_CID_XILINX_MIPICSISS_ACT_LANES) {
+
+ if (xcsi2rxss->core.enable_active_lanes) {
+ xcsi2rxss_ctrls[i].max =
+ xcsi2rxss->core.max_num_lanes;
+ } else {
+ /* Don't register control */
+ dev_dbg(xcsi2rxss->core.dev,
+ "Skip active lane control\n");
+ continue;
+ }
+ }
+
+ dev_dbg(xcsi2rxss->core.dev, "%d ctrl = 0x%x\n",
+ i, xcsi2rxss_ctrls[i].id);
+ ctrl = v4l2_ctrl_new_custom(&xcsi2rxss->ctrl_handler,
+ &xcsi2rxss_ctrls[i], NULL);
+ if (!ctrl) {
+ dev_err(xcsi2rxss->core.dev, "Failed for %s ctrl\n",
+ xcsi2rxss_ctrls[i].name);
+ goto error;
+ }
+ }
+
+ dev_dbg(xcsi2rxss->core.dev, "# v4l2 ctrls registered = %d\n", i - 1);
+
+ if (xcsi2rxss->ctrl_handler.error) {
+ dev_err(&pdev->dev, "failed to add controls\n");
+ ret = xcsi2rxss->ctrl_handler.error;
+ goto error;
+ }
+
+ subdev->ctrl_handler = &xcsi2rxss->ctrl_handler;
+
+ ret = v4l2_ctrl_handler_setup(&xcsi2rxss->ctrl_handler);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to set controls\n");
+ goto error;
+ }
+
+ platform_set_drvdata(pdev, xcsi2rxss);
+
+ dev_info(xcsi2rxss->core.dev, "Xilinx CSI2 Rx Subsystem device found!\n");
+
+ ret = v4l2_async_register_subdev(subdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register subdev\n");
+ goto error;
+ }
+
+ /* default states for streaming and suspend */
+ xcsi2rxss->streaming = false;
+ xcsi2rxss->suspended = false;
+ return 0;
+
+error:
+ v4l2_ctrl_handler_free(&xcsi2rxss->ctrl_handler);
+ media_entity_cleanup(&subdev->entity);
+ mutex_destroy(&xcsi2rxss->lock);
+
+all_clk_err:
+ clk_disable_unprepare(xcsi2rxss->core.dphy_clk_200M);
+dphy_clk_err:
+ clk_disable_unprepare(xcsi2rxss->core.video_aclk);
+video_aclk_err:
+ clk_disable_unprepare(xcsi2rxss->core.lite_aclk);
+clk_err:
+ return ret;
+}
+
+static int xcsi2rxss_remove(struct platform_device *pdev)
+{
+ struct xcsi2rxss_state *xcsi2rxss = platform_get_drvdata(pdev);
+ struct v4l2_subdev *subdev = &xcsi2rxss->subdev;
+
+ v4l2_async_unregister_subdev(subdev);
+ v4l2_ctrl_handler_free(&xcsi2rxss->ctrl_handler);
+ media_entity_cleanup(&subdev->entity);
+ mutex_destroy(&xcsi2rxss->lock);
+ clk_disable_unprepare(xcsi2rxss->core.dphy_clk_200M);
+ clk_disable_unprepare(xcsi2rxss->core.video_aclk);
+ clk_disable_unprepare(xcsi2rxss->core.lite_aclk);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(xcsi2rxss_pm_ops,
+ xcsi2rxss_pm_suspend, xcsi2rxss_pm_resume);
+
+static struct platform_driver xcsi2rxss_driver = {
+ .driver = {
+ .name = "xilinx-csi2rxss",
+ .pm = &xcsi2rxss_pm_ops,
+ .of_match_table = xcsi2rxss_of_id_table,
+ },
+ .probe = xcsi2rxss_probe,
+ .remove = xcsi2rxss_remove,
+};
+
+module_platform_driver(xcsi2rxss_driver);
+
+MODULE_AUTHOR("Vishal Sagar <vsagar@xilinx.com>");
+MODULE_DESCRIPTION("Xilinx MIPI CSI2 Rx Subsystem Driver");
+MODULE_LICENSE("GPL v2");