From nobody Thu Apr 2 22:58:53 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A8A50ECAAA1 for ; Mon, 19 Sep 2022 18:16:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229779AbiISSP7 (ORCPT ); Mon, 19 Sep 2022 14:15:59 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46276 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229519AbiISSPw (ORCPT ); Mon, 19 Sep 2022 14:15:52 -0400 Received: from mx.gpxsee.org (mx.gpxsee.org [37.205.14.76]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 6D71446203; Mon, 19 Sep 2022 11:15:50 -0700 (PDT) Received: from mgb4.digiteq.red (unknown [62.77.71.229]) by mx.gpxsee.org (Postfix) with ESMTPSA id 696DF4236E; Mon, 19 Sep 2022 18:56:21 +0200 (CEST) From: tumic@gpxsee.org To: Mauro Carvalho Chehab , Vinod Koul , Michal Simek Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org, linux-i2c@vger.kernel.org, =?UTF-8?q?Martin=20T=C5=AFma?= Subject: [PATCH v2 1/3] Added platform module alias for the xiic I2C driver Date: Mon, 19 Sep 2022 20:55:54 +0200 Message-Id: <20220919185556.5215-2-tumic@gpxsee.org> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220919185556.5215-1-tumic@gpxsee.org> References: <20220919185556.5215-1-tumic@gpxsee.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Martin T=C5=AFma The missing "platform" alias is required for the mgb4 v4l2 driver to load the i2c controller driver when probing the HW. Signed-off-by: Martin T=C5=AFma --- drivers/i2c/busses/i2c-xiic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c index b3fe6b2aa3ca..277a02455cdd 100644 --- a/drivers/i2c/busses/i2c-xiic.c +++ b/drivers/i2c/busses/i2c-xiic.c @@ -920,6 +920,7 @@ static struct platform_driver xiic_i2c_driver =3D { =20 module_platform_driver(xiic_i2c_driver); =20 +MODULE_ALIAS("platform:" DRIVER_NAME); MODULE_AUTHOR("info@mocean-labs.com"); MODULE_DESCRIPTION("Xilinx I2C bus driver"); MODULE_LICENSE("GPL v2"); --=20 2.37.2 From nobody Thu Apr 2 22:58:53 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A442BECAAA1 for ; Mon, 19 Sep 2022 18:16:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229750AbiISSQF (ORCPT ); Mon, 19 Sep 2022 14:16:05 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46298 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229692AbiISSPy (ORCPT ); Mon, 19 Sep 2022 14:15:54 -0400 Received: from mx.gpxsee.org (mx.gpxsee.org [37.205.14.76]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 6D29546201; Mon, 19 Sep 2022 11:15:50 -0700 (PDT) Received: from mgb4.digiteq.red (unknown [62.77.71.229]) by mx.gpxsee.org (Postfix) with ESMTPSA id 55D134474B; Mon, 19 Sep 2022 18:56:22 +0200 (CEST) From: tumic@gpxsee.org To: Mauro Carvalho Chehab , Vinod Koul , Michal Simek Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org, linux-i2c@vger.kernel.org, =?UTF-8?q?Martin=20T=C5=AFma?= Subject: [PATCH v2 2/3] Added Xilinx XDMA IP core driver Date: Mon, 19 Sep 2022 20:55:55 +0200 Message-Id: <20220919185556.5215-3-tumic@gpxsee.org> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220919185556.5215-1-tumic@gpxsee.org> References: <20220919185556.5215-1-tumic@gpxsee.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Martin T=C5=AFma Added support for the Xilinx XDMA PCIe DMA IP core. The XDMA IP core is use= d in many FPGA PCIe card designs for DMA transfers between the PCIe card and the= host system. This driver can be incorporated into any PCIe card (that contains the XDMA IP core) driver to initialize the XDMA HW and process DMA transfer= s. The driver is originally based on the code provided by Xilinx at https://github.com/Xilinx/dma_ip_drivers Signed-off-by: Martin T=C5=AFma Reported-by: kernel test robot --- drivers/dma/Kconfig | 7 + drivers/dma/xilinx/Makefile | 1 + drivers/dma/xilinx/xilinx_xdma.c | 2042 ++++++++++++++++++++++++++++++ include/linux/dma/xilinx_xdma.h | 44 + 4 files changed, 2094 insertions(+) create mode 100644 drivers/dma/xilinx/xilinx_xdma.c create mode 100644 include/linux/dma/xilinx_xdma.h diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index a06d2a7627aa..932086cd5962 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -804,4 +804,11 @@ config DMATEST config DMA_ENGINE_RAID bool =20 +config XILINX_XDMA + tristate "Xilinx XDMA Engine" + depends on PCI + select DMA_ENGINE + help + Enable support for Xilinx XDMA IP controller. + endif diff --git a/drivers/dma/xilinx/Makefile b/drivers/dma/xilinx/Makefile index 767bb45f641f..55e97686f8ea 100644 --- a/drivers/dma/xilinx/Makefile +++ b/drivers/dma/xilinx/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_XILINX_DMA) +=3D xilinx_dma.o obj-$(CONFIG_XILINX_ZYNQMP_DMA) +=3D zynqmp_dma.o obj-$(CONFIG_XILINX_ZYNQMP_DPDMA) +=3D xilinx_dpdma.o +obj-$(CONFIG_XILINX_XDMA) +=3D xilinx_xdma.o diff --git a/drivers/dma/xilinx/xilinx_xdma.c b/drivers/dma/xilinx/xilinx_x= dma.c new file mode 100644 index 000000000000..9db637c25045 --- /dev/null +++ b/drivers/dma/xilinx/xilinx_xdma.c @@ -0,0 +1,2042 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is part of the Xilinx DMA IP Core driver for Linux + * + * Copyright (c) 2016-2021, Xilinx, Inc. + * Copyright (c) 2022, Digiteq Automotive s.r.o. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static unsigned int enable_credit_mp =3D 1; +module_param(enable_credit_mp, uint, 0644); +MODULE_PARM_DESC(enable_credit_mp, + "Set 0 to disable credit feature, default is 1 (enabled)"); + +#define XDMA_BAR_SIZE 0x8000UL + +#define XDMA_CHANNEL_NUM_MAX 4 +#define XDMA_ENG_IRQ_NUM 1 +#define XDMA_MAX_ADJ_BLOCK_SIZE 0x40 +#define XDMA_PAGE_SIZE 0x1000 +#define RX_STATUS_EOP 1 + +#define XDMA_OFS_INT_CTRL 0x2000UL +#define XDMA_OFS_CONFIG 0x3000UL + +#define XDMA_TRANSFER_MAX_DESC 2048 + +#define XDMA_DESC_BLEN_BITS 28 +#define XDMA_DESC_BLEN_MAX ((1 << (XDMA_DESC_BLEN_BITS)) - 1) + +/* bits of the SG DMA control register */ +#define XDMA_CTRL_RUN_STOP (1UL << 0) +#define XDMA_CTRL_IE_DESC_STOPPED (1UL << 1) +#define XDMA_CTRL_IE_DESC_COMPLETED (1UL << 2) +#define XDMA_CTRL_IE_DESC_ALIGN_MISMATCH (1UL << 3) +#define XDMA_CTRL_IE_MAGIC_STOPPED (1UL << 4) +#define XDMA_CTRL_IE_IDLE_STOPPED (1UL << 6) +#define XDMA_CTRL_IE_READ_ERROR (0x1FUL << 9) +#define XDMA_CTRL_IE_DESC_ERROR (0x1FUL << 19) +#define XDMA_CTRL_NON_INCR_ADDR (1UL << 25) +#define XDMA_CTRL_POLL_MODE_WB (1UL << 26) +#define XDMA_CTRL_STM_MODE_WB (1UL << 27) + +/* bits of the SG DMA status register */ +#define XDMA_STAT_BUSY (1UL << 0) +#define XDMA_STAT_DESC_STOPPED (1UL << 1) +#define XDMA_STAT_DESC_COMPLETED (1UL << 2) +#define XDMA_STAT_ALIGN_MISMATCH (1UL << 3) +#define XDMA_STAT_MAGIC_STOPPED (1UL << 4) +#define XDMA_STAT_INVALID_LEN (1UL << 5) +#define XDMA_STAT_IDLE_STOPPED (1UL << 6) + +#define XDMA_STAT_COMMON_ERR_MASK \ + (XDMA_STAT_ALIGN_MISMATCH | XDMA_STAT_MAGIC_STOPPED | \ + XDMA_STAT_INVALID_LEN) + +/* desc_error, C2H & H2C */ +#define XDMA_STAT_DESC_UNSUPP_REQ (1UL << 19) +#define XDMA_STAT_DESC_COMPL_ABORT (1UL << 20) +#define XDMA_STAT_DESC_PARITY_ERR (1UL << 21) +#define XDMA_STAT_DESC_HEADER_EP (1UL << 22) +#define XDMA_STAT_DESC_UNEXP_COMPL (1UL << 23) + +#define XDMA_STAT_DESC_ERR_MASK \ + (XDMA_STAT_DESC_UNSUPP_REQ | XDMA_STAT_DESC_COMPL_ABORT | \ + XDMA_STAT_DESC_PARITY_ERR | XDMA_STAT_DESC_HEADER_EP | \ + XDMA_STAT_DESC_UNEXP_COMPL) + +/* read error: H2C */ +#define XDMA_STAT_H2C_R_UNSUPP_REQ (1UL << 9) +#define XDMA_STAT_H2C_R_COMPL_ABORT (1UL << 10) +#define XDMA_STAT_H2C_R_PARITY_ERR (1UL << 11) +#define XDMA_STAT_H2C_R_HEADER_EP (1UL << 12) +#define XDMA_STAT_H2C_R_UNEXP_COMPL (1UL << 13) + +#define XDMA_STAT_H2C_R_ERR_MASK \ + (XDMA_STAT_H2C_R_UNSUPP_REQ | XDMA_STAT_H2C_R_COMPL_ABORT | \ + XDMA_STAT_H2C_R_PARITY_ERR | XDMA_STAT_H2C_R_HEADER_EP | \ + XDMA_STAT_H2C_R_UNEXP_COMPL) + +/* write error, H2C only */ +#define XDMA_STAT_H2C_W_DECODE_ERR (1UL << 14) +#define XDMA_STAT_H2C_W_SLAVE_ERR (1UL << 15) + +#define XDMA_STAT_H2C_W_ERR_MASK \ + (XDMA_STAT_H2C_W_DECODE_ERR | XDMA_STAT_H2C_W_SLAVE_ERR) + +/* read error: C2H */ +#define XDMA_STAT_C2H_R_DECODE_ERR (1UL << 9) +#define XDMA_STAT_C2H_R_SLAVE_ERR (1UL << 10) + +#define XDMA_STAT_C2H_R_ERR_MASK \ + (XDMA_STAT_C2H_R_DECODE_ERR | XDMA_STAT_C2H_R_SLAVE_ERR) + +/* all combined */ +#define XDMA_STAT_H2C_ERR_MASK \ + (XDMA_STAT_COMMON_ERR_MASK | XDMA_STAT_DESC_ERR_MASK | \ + XDMA_STAT_H2C_R_ERR_MASK | XDMA_STAT_H2C_W_ERR_MASK) + +#define XDMA_STAT_C2H_ERR_MASK \ + (XDMA_STAT_COMMON_ERR_MASK | XDMA_STAT_DESC_ERR_MASK | \ + XDMA_STAT_C2H_R_ERR_MASK) + +/* bits of the SGDMA descriptor control field */ +#define XDMA_DESC_STOPPED (1UL << 0) +#define XDMA_DESC_COMPLETED (1UL << 1) +#define XDMA_DESC_EOP (1UL << 4) + +/* upper 16-bits of engine identifier register */ +#define XDMA_ID_H2C 0x1fc0U +#define XDMA_ID_C2H 0x1fc1U + +#define LS_BYTE_MASK 0x000000FFUL + +#define BLOCK_ID_MASK 0xFFF00000 +#define BLOCK_ID_HEAD 0x1FC00000 + +#define IRQ_BLOCK_ID 0x1fc20000UL +#define CONFIG_BLOCK_ID 0x1fc30000UL + +#define WB_COUNT_MASK 0x00ffffffUL +#define WB_ERR_MASK (1UL << 31) + +#define MAX_USER_IRQ 16 + +#define DESC_MAGIC 0xAD4B0000UL + +#define C2H_WB 0x52B4UL + +#define H2C_CHANNEL_OFFSET 0x1000 +#define SGDMA_OFFSET_FROM_CHANNEL 0x4000 +#define CHANNEL_SPACING 0x100 +#define TARGET_SPACING 0x1000 + +/* obtain the 32 most significant (high) bits of a 32-bit or 64-bit addres= s */ +#define PCI_DMA_H(addr) ((addr >> 16) >> 16) +/* obtain the 32 least significant (low) bits of a 32-bit or 64-bit addres= s */ +#define PCI_DMA_L(addr) (addr & 0xffffffffUL) + + +enum transfer_state { + TRANSFER_STATE_NEW =3D 0, + TRANSFER_STATE_SUBMITTED, + TRANSFER_STATE_COMPLETED, + TRANSFER_STATE_FAILED, + TRANSFER_STATE_ABORTED +}; + +enum shutdown_state { + ENGINE_SHUTDOWN_NONE =3D 0, /* No shutdown in progress */ + ENGINE_SHUTDOWN_REQUEST =3D 1, /* engine requested to shutdown */ + ENGINE_SHUTDOWN_IDLE =3D 2 /* engine has shutdown and is idle */ +}; + +struct config_regs { + u32 identifier; + u32 reserved_1[4]; + u32 msi_enable; +}; + +struct engine_regs { + u32 identifier; + u32 control; + u32 control_w1s; + u32 control_w1c; + u32 reserved_1[12]; /* padding */ + + u32 status; + u32 status_rc; + u32 completed_desc_count; + u32 alignments; + u32 reserved_2[14]; /* padding */ + + u32 poll_mode_wb_lo; + u32 poll_mode_wb_hi; + u32 interrupt_enable_mask; + u32 interrupt_enable_mask_w1s; + u32 interrupt_enable_mask_w1c; + u32 reserved_3[9]; /* padding */ + + u32 perf_ctrl; + u32 perf_cyc_lo; + u32 perf_cyc_hi; + u32 perf_dat_lo; + u32 perf_dat_hi; + u32 perf_pnd_lo; + u32 perf_pnd_hi; +} __packed; + +struct engine_sgdma_regs { + u32 identifier; + u32 reserved_1[31]; /* padding */ + + /* bus address to first descriptor in Root Complex Memory */ + u32 first_desc_lo; + u32 first_desc_hi; + /* number of adjacent descriptors at first_desc */ + u32 first_desc_adjacent; + u32 credits; +} __packed; + +struct interrupt_regs { + u32 identifier; + u32 user_int_enable; + u32 user_int_enable_w1s; + u32 user_int_enable_w1c; + u32 channel_int_enable; + u32 channel_int_enable_w1s; + u32 channel_int_enable_w1c; + u32 reserved_1[9]; /* padding */ + + u32 user_int_request; + u32 channel_int_request; + u32 user_int_pending; + u32 channel_int_pending; + u32 reserved_2[12]; /* padding */ + + u32 user_msi_vector[8]; + u32 channel_msi_vector[8]; +} __packed; + +struct sgdma_common_regs { + u32 padding[8]; + u32 credit_mode_enable; + u32 credit_mode_enable_w1s; + u32 credit_mode_enable_w1c; +} __packed; + + +/* + * Descriptor for a single contiguous memory block transfer. + * + * Multiple descriptors are linked by means of the next pointer. An additi= onal + * extra adjacent number gives the amount of extra contiguous descriptors. + * + * The descriptors are in root complex memory, and the bytes in the 32-bit + * words must be in little-endian byte ordering. + */ +struct xdma_desc { + u32 control; + u32 bytes; /* transfer length in bytes */ + u32 src_addr_lo; /* source address (low 32-bit) */ + u32 src_addr_hi; /* source address (high 32-bit) */ + u32 dst_addr_lo; /* destination address (low 32-bit) */ + u32 dst_addr_hi; /* destination address (high 32-bit) */ + /* + * next descriptor in the single-linked list of descriptors; + * this is the PCIe (bus) address of the next descriptor in the + * root complex memory + */ + u32 next_lo; /* next desc address (low 32-bit) */ + u32 next_hi; /* next desc address (high 32-bit) */ +} __packed; + +/* 32 bytes (four 32-bit words) or 64 bytes (eight 32-bit words) */ +struct xdma_result { + u32 status; + u32 length; + u32 reserved_1[6]; /* padding */ +} __packed; + +struct sw_desc { + dma_addr_t addr; + unsigned int len; +}; + +/* Describes a (SG DMA) single transfer for the engine */ +#define XFER_FLAG_NEED_UNMAP 0x1 +#define XFER_FLAG_ST_C2H_EOP_RCVED 0x2 /* ST c2h only */ +struct xdma_transfer { + struct list_head entry; /* queue of non-completed transfers */ + struct xdma_desc *desc_virt; /* virt addr of the 1st descriptor */ + struct xdma_result *res_virt; /* virt addr of result, c2h streaming */ + dma_addr_t res_bus; /* bus addr for result descriptors */ + dma_addr_t desc_bus; /* bus addr of the first descriptor */ + int desc_adjacent; /* adjacent descriptors at desc_bus */ + int desc_num; /* number of descriptors in transfer */ + int desc_index; /* index for 1st desc. in transfer */ + int desc_cmpl; /* completed descriptors */ + int desc_cmpl_th; /* completed descriptor threshold */ + enum dma_data_direction dir; + struct swait_queue_head wq; /* wait queue for transfer completion */ + + enum transfer_state state; /* state of the transfer */ + unsigned int flags; + int cyclic; /* flag if transfer is cyclic */ + int last_in_request; /* flag if last within request */ + unsigned int len; + struct sg_table *sgt; +}; + +struct xdma_request_cb { + struct sg_table *sgt; + unsigned int total_len; + u64 ep_addr; + + struct xdma_transfer tfer; + + unsigned int sw_desc_idx; + unsigned int sw_desc_cnt; + struct sw_desc sdesc[0]; +}; + +struct xdma_engine { + struct xdma_dev *xdev; /* parent device */ + char name[16]; /* name of this engine */ + + /* HW register address offsets */ + struct engine_regs *regs; /* Control reg BAR offset */ + struct engine_sgdma_regs *sgdma_regs; /* SGDAM reg BAR offset */ + + /* Engine state, configuration and flags */ + enum shutdown_state shutdown; /* engine shutdown mode */ + enum dma_data_direction dir; + u8 addr_align; /* source/dest alignment in bytes */ + u8 len_granularity; /* transfer length multiple */ + u8 addr_bits; /* HW datapath address width */ + u8 channel:2; /* engine indices */ + u8 streaming:1; + u8 device_open:1; /* flag if engine node open, ST mode only */ + u8 running:1; /* flag if the driver started engine */ + u8 non_incr_addr:1; /* flag if non-incremental addressing used */ + u8 eop_flush:1; /* st c2h only, flush up the data with eop */ + u8 filler:1; + + int max_extra_adj; /* descriptor prefetch capability */ + int desc_dequeued; /* num descriptors of completed transfers */ + u32 status; /* last known status of device */ + u32 interrupt_enable_mask_value; /* per-engine interrupt mask value */ + + /* Transfer list management */ + struct list_head transfer_list; /* queue of transfers */ + + /* Members applicable to AXI-ST C2H (cyclic) transfers */ + struct xdma_result *cyclic_result; + dma_addr_t cyclic_result_bus; /* bus addr for transfer */ + + /* Members associated with interrupt mode support */ + struct swait_queue_head shutdown_wq; + spinlock_t lock; /* protects concurrent access */ + int prev_cpu; /* remember CPU# of (last) locker */ + int irq_line; /* IRQ vector for this engine */ + u32 irq_bitmask; /* IRQ bit mask for this engine */ + struct work_struct work; /* Work queue for interrupt handling */ + + struct mutex desc_lock; /* protects concurrent access */ + dma_addr_t desc_bus; + struct xdma_desc *desc; + int desc_idx; /* current descriptor index */ + int desc_used; /* total descriptors used */ +}; + +struct xdma_dev { + struct pci_dev *pdev; + void __iomem *config_bar; + unsigned int mask_irq_user; + int engines_num; + struct xdma_engine engine_h2c[XDMA_CHANNEL_NUM_MAX]; + struct xdma_engine engine_c2h[XDMA_CHANNEL_NUM_MAX]; +}; + + +static void channel_interrupts_enable(struct xdma_dev *xdev, u32 mask) +{ + struct interrupt_regs *reg =3D + (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL); + + iowrite32(mask, ®->channel_int_enable_w1s); +} + +static void channel_interrupts_disable(struct xdma_dev *xdev, u32 mask) +{ + struct interrupt_regs *reg =3D + (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL); + + iowrite32(mask, ®->channel_int_enable_w1c); +} + +static void user_interrupts_enable(struct xdma_dev *xdev, u32 mask) +{ + struct interrupt_regs *reg =3D + (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL); + + iowrite32(mask, ®->user_int_enable_w1s); +} + +static void user_interrupts_disable(struct xdma_dev *xdev, u32 mask) +{ + struct interrupt_regs *reg =3D + (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL); + + iowrite32(mask, ®->user_int_enable_w1c); +} + +static void read_interrupts(struct xdma_dev *xdev) +{ + struct interrupt_regs *reg =3D + (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL); + u32 lo, hi; + + hi =3D ioread32(®->user_int_request); + lo =3D ioread32(®->channel_int_request); +} + +static void engine_reg_dump(struct xdma_engine *engine) +{ + u32 w; + + w =3D ioread32(&engine->regs->identifier); + if ((w & BLOCK_ID_MASK) !=3D BLOCK_ID_HEAD) { + pr_warn("XDMA: %s: 0x%08x: invalid engine id\n", + engine->name, w); + return; + } + + pr_info("XDMA: %s: ENGINE REGISTER DUMP\n", engine->name); + pr_info("%s: ioread32(0x%p) =3D 0x%08x (id).\n", + engine->name, &engine->regs->identifier, w); + w =3D ioread32(&engine->regs->status); + pr_info("%s: ioread32(0x%p) =3D 0x%08x (status).\n", + engine->name, &engine->regs->status, w); + w =3D ioread32(&engine->regs->control); + pr_info("%s: ioread32(0x%p) =3D 0x%08x (control)\n", + engine->name, &engine->regs->control, w); + w =3D ioread32(&engine->sgdma_regs->first_desc_lo); + pr_info("%s: ioread32(0x%p) =3D 0x%08x (first_desc_lo)\n", + engine->name, &engine->sgdma_regs->first_desc_lo, w); + w =3D ioread32(&engine->sgdma_regs->first_desc_hi); + pr_info("%s: ioread32(0x%p) =3D 0x%08x (first_desc_hi)\n", + engine->name, &engine->sgdma_regs->first_desc_hi, w); + w =3D ioread32(&engine->sgdma_regs->first_desc_adjacent); + pr_info("%s: ioread32(0x%p) =3D 0x%08x (first_desc_adjacent).\n", + engine->name, &engine->sgdma_regs->first_desc_adjacent, w); + w =3D ioread32(&engine->regs->completed_desc_count); + pr_info("%s: ioread32(0x%p) =3D 0x%08x (completed_desc_count).\n", + engine->name, &engine->regs->completed_desc_count, w); + w =3D ioread32(&engine->regs->interrupt_enable_mask); + pr_info("%s: ioread32(0x%p) =3D 0x%08x (interrupt_enable_mask)\n", + engine->name, &engine->regs->interrupt_enable_mask, w); +} + +static void engine_status_dump(struct xdma_engine *engine) +{ + u32 v =3D engine->status; + char buffer[256]; + char *buf =3D buffer; + int len =3D 0; + + len =3D sprintf(buf, "XDMA: %s: status: 0x%08x: ", engine->name, v); + + if ((v & XDMA_STAT_BUSY)) + len +=3D sprintf(buf + len, "BUSY,"); + if ((v & XDMA_STAT_DESC_STOPPED)) + len +=3D sprintf(buf + len, "DESC_STOPPED,"); + if ((v & XDMA_STAT_DESC_COMPLETED)) + len +=3D sprintf(buf + len, "DESC_COMPL,"); + + /* common H2C & C2H */ + if ((v & XDMA_STAT_COMMON_ERR_MASK)) { + if ((v & XDMA_STAT_ALIGN_MISMATCH)) + len +=3D sprintf(buf + len, "ALIGN_MISMATCH "); + if ((v & XDMA_STAT_MAGIC_STOPPED)) + len +=3D sprintf(buf + len, "MAGIC_STOPPED "); + if ((v & XDMA_STAT_INVALID_LEN)) + len +=3D sprintf(buf + len, "INVLIAD_LEN "); + if ((v & XDMA_STAT_IDLE_STOPPED)) + len +=3D sprintf(buf + len, "IDLE_STOPPED "); + buf[len - 1] =3D ','; + } + + if (engine->dir =3D=3D DMA_TO_DEVICE) { + /* H2C only */ + if ((v & XDMA_STAT_H2C_R_ERR_MASK)) { + len +=3D sprintf(buf + len, "R:"); + if ((v & XDMA_STAT_H2C_R_UNSUPP_REQ)) + len +=3D sprintf(buf + len, "UNSUPP_REQ "); + if ((v & XDMA_STAT_H2C_R_COMPL_ABORT)) + len +=3D sprintf(buf + len, "COMPL_ABORT "); + if ((v & XDMA_STAT_H2C_R_PARITY_ERR)) + len +=3D sprintf(buf + len, "PARITY "); + if ((v & XDMA_STAT_H2C_R_HEADER_EP)) + len +=3D sprintf(buf + len, "HEADER_EP "); + if ((v & XDMA_STAT_H2C_R_UNEXP_COMPL)) + len +=3D sprintf(buf + len, "UNEXP_COMPL "); + buf[len - 1] =3D ','; + } + + if ((v & XDMA_STAT_H2C_W_ERR_MASK)) { + len +=3D sprintf(buf + len, "W:"); + if ((v & XDMA_STAT_H2C_W_DECODE_ERR)) + len +=3D sprintf(buf + len, "DECODE_ERR "); + if ((v & XDMA_STAT_H2C_W_SLAVE_ERR)) + len +=3D sprintf(buf + len, "SLAVE_ERR "); + buf[len - 1] =3D ','; + } + + } else { + /* C2H only */ + if ((v & XDMA_STAT_C2H_R_ERR_MASK)) { + len +=3D sprintf(buf + len, "R:"); + if ((v & XDMA_STAT_C2H_R_DECODE_ERR)) + len +=3D sprintf(buf + len, "DECODE_ERR "); + if ((v & XDMA_STAT_C2H_R_SLAVE_ERR)) + len +=3D sprintf(buf + len, "SLAVE_ERR "); + buf[len - 1] =3D ','; + } + } + + /* common H2C & C2H */ + if ((v & XDMA_STAT_DESC_ERR_MASK)) { + len +=3D sprintf(buf + len, "DESC_ERR:"); + if ((v & XDMA_STAT_DESC_UNSUPP_REQ)) + len +=3D sprintf(buf + len, "UNSUPP_REQ "); + if ((v & XDMA_STAT_DESC_COMPL_ABORT)) + len +=3D sprintf(buf + len, "COMPL_ABORT "); + if ((v & XDMA_STAT_DESC_PARITY_ERR)) + len +=3D sprintf(buf + len, "PARITY "); + if ((v & XDMA_STAT_DESC_HEADER_EP)) + len +=3D sprintf(buf + len, "HEADER_EP "); + if ((v & XDMA_STAT_DESC_UNEXP_COMPL)) + len +=3D sprintf(buf + len, "UNEXP_COMPL "); + buf[len - 1] =3D ','; + } + + buf[len - 1] =3D '\0'; + pr_info("%s\n", buffer); +} + +static void engine_status_read(struct xdma_engine *engine, bool clear, boo= l dump) +{ + if (dump) + engine_reg_dump(engine); + + if (clear) + engine->status =3D ioread32(&engine->regs->status_rc); + else + engine->status =3D ioread32(&engine->regs->status); + + if (dump) + engine_status_dump(engine); +} + +static void engine_stop(struct xdma_engine *engine) +{ + u32 w; + + if (enable_credit_mp && engine->streaming && + engine->dir =3D=3D DMA_FROM_DEVICE) + iowrite32(0, &engine->sgdma_regs->credits); + + w =3D 0; + w |=3D (u32)XDMA_CTRL_IE_DESC_ALIGN_MISMATCH; + w |=3D (u32)XDMA_CTRL_IE_MAGIC_STOPPED; + w |=3D (u32)XDMA_CTRL_IE_READ_ERROR; + w |=3D (u32)XDMA_CTRL_IE_DESC_ERROR; + + w |=3D (u32)XDMA_CTRL_IE_DESC_STOPPED; + w |=3D (u32)XDMA_CTRL_IE_DESC_COMPLETED; + + iowrite32(w, &engine->regs->control); + + engine->running =3D 0; +} + +static int engine_start_mode_config(struct xdma_engine *engine) +{ + u32 w; + + /* write control register of SG DMA engine */ + w =3D (u32)XDMA_CTRL_RUN_STOP; + w |=3D (u32)XDMA_CTRL_IE_READ_ERROR; + w |=3D (u32)XDMA_CTRL_IE_DESC_ERROR; + w |=3D (u32)XDMA_CTRL_IE_DESC_ALIGN_MISMATCH; + w |=3D (u32)XDMA_CTRL_IE_MAGIC_STOPPED; + + w |=3D (u32)XDMA_CTRL_IE_DESC_STOPPED; + w |=3D (u32)XDMA_CTRL_IE_DESC_COMPLETED; + + /* set non-incremental addressing mode */ + if (engine->non_incr_addr) + w |=3D (u32)XDMA_CTRL_NON_INCR_ADDR; + + /* start the engine */ + iowrite32(w, &engine->regs->control); + /* dummy read of status register to flush all previous writes */ + w =3D ioread32(&engine->regs->status); + + return 0; +} + +/* + * Get the number for adjacent descriptors to set in a descriptor, based o= n the + * remaining number of descriptors and the lower bits of the address of the + * next descriptor. + * Since the number of descriptors in a page (XDMA_PAGE_SIZE) is 128 and t= he + * maximum size of a block of adjacent descriptors is 64 (63 max adjacent + * descriptors for any descriptor), align the blocks of adjacent descripto= rs + * to the block size. + */ +static u32 xdma_get_next_adj(unsigned int remaining, u32 next_lo) +{ + unsigned int next_index; + + if (remaining <=3D 1) + return 0; + + /* shift right 5 times corresponds to a division by + * sizeof(xdma_desc) =3D 32 + */ + next_index =3D ((next_lo & (XDMA_PAGE_SIZE - 1)) >> 5) % + XDMA_MAX_ADJ_BLOCK_SIZE; + return min(XDMA_MAX_ADJ_BLOCK_SIZE - next_index - 1, remaining - 1); +} + +/* + * start an idle engine with its first transfer on queue + * + * The engine will run and process all transfers that are queued using + * transfer_queue() and thus have their descriptor lists chained. + * + * During the run, new transfers will be processed if transfer_queue() has + * chained the descriptors before the hardware fetches the last descriptor. + * A transfer that was chained too late will invoke a new run of the engine + * initiated from the engine_service() routine. + * + * The engine must be idle and at least one transfer must be queued. + */ +static int engine_start(struct xdma_engine *engine) +{ + struct xdma_transfer *transfer; + u32 w, next_adj; + int rv; + + /* engine transfer queue must not be empty */ + if (list_empty(&engine->transfer_list)) { + pr_warn("XDMA: %s: transfer queue must not be empty\n", + engine->name); + return -EIO; + } + /* inspect first transfer queued on the engine */ + transfer =3D list_entry(engine->transfer_list.next, struct xdma_transfer, + entry); + if (!transfer) { + pr_warn("XDMA: %s: queued transfer must not be empty\n", + engine->name); + return -EIO; + } + + /* engine is no longer shutdown */ + engine->shutdown =3D ENGINE_SHUTDOWN_NONE; + + /* Add credits for Streaming mode C2H */ + if (enable_credit_mp && engine->streaming && + engine->dir =3D=3D DMA_FROM_DEVICE) + iowrite32(engine->desc_used, &engine->sgdma_regs->credits); + + /* initialize number of descriptors of dequeued transfers */ + engine->desc_dequeued =3D 0; + + /* write lower 32-bit of bus address of transfer first descriptor */ + w =3D cpu_to_le32(PCI_DMA_L(transfer->desc_bus)); + iowrite32(w, &engine->sgdma_regs->first_desc_lo); + /* write upper 32-bit of bus address of transfer first descriptor */ + w =3D cpu_to_le32(PCI_DMA_H(transfer->desc_bus)); + iowrite32(w, &engine->sgdma_regs->first_desc_hi); + + next_adj =3D xdma_get_next_adj(transfer->desc_adjacent, + cpu_to_le32(PCI_DMA_L(transfer->desc_bus))); + iowrite32(next_adj, &engine->sgdma_regs->first_desc_adjacent); + + rv =3D engine_start_mode_config(engine); + if (rv < 0) + return rv; + engine_status_read(engine, 0, 0); + + engine->running =3D 1; + + return 0; +} + +static void engine_service_shutdown(struct xdma_engine *engine) +{ + engine_stop(engine); + /* awake task on engine's shutdown wait queue */ + swake_up_one(&engine->shutdown_wq); +} + +static struct xdma_transfer *engine_transfer_completion( + struct xdma_engine *engine, + struct xdma_transfer *transfer) +{ + if (unlikely(!transfer)) { + pr_warn("XDMA: %s empty xfer\n", engine->name); + return NULL; + } + + /* synchronous I/O? */ + /* awake task on transfer's wait queue */ + swake_up_one(&transfer->wq); + + return transfer; +} + +static struct xdma_transfer *engine_service_transfer_list( + struct xdma_engine *engine, + struct xdma_transfer *transfer, + u32 *pdesc_completed) +{ + if (unlikely(!transfer)) { + pr_warn("XDMA: %s empty xfer\n", engine->name); + return NULL; + } + + /* + * iterate over all the transfers completed by the engine, + * except for the last + */ + while (transfer && (!transfer->cyclic) && + (*pdesc_completed > transfer->desc_num)) { + /* remove this transfer from pdesc_completed */ + *pdesc_completed -=3D transfer->desc_num; + + /* remove completed transfer from list */ + list_del(engine->transfer_list.next); + /* add to dequeued number of descriptors during this run */ + engine->desc_dequeued +=3D transfer->desc_num; + /* mark transfer as successfully completed */ + transfer->state =3D TRANSFER_STATE_COMPLETED; + + /* + * Complete transfer - sets transfer to NULL if an async + * transfer has completed + */ + transfer =3D engine_transfer_completion(engine, transfer); + + /* if exists, get the next transfer on the list */ + if (!list_empty(&engine->transfer_list)) { + transfer =3D list_entry(engine->transfer_list.next, + struct xdma_transfer, entry); + } else { + /* no further transfers? */ + transfer =3D NULL; + } + } + + return transfer; +} + +static void engine_err_handle(struct xdma_engine *engine, + struct xdma_transfer *transfer) +{ + u32 value; + + /* + * The BUSY bit is expected to be clear now but older HW has a race + * condition which could cause it to be still set. If it's set, re-read + * and check again. If it's still set, log the issue. + */ + if (engine->status & XDMA_STAT_BUSY) { + value =3D ioread32(&engine->regs->status); + if ((value & XDMA_STAT_BUSY)) + pr_warn("XDMA: %s has errors but is still BUSY\n", + engine->name); + } + + /* mark transfer as failed */ + transfer->state =3D TRANSFER_STATE_FAILED; + engine_stop(engine); +} + +static struct xdma_transfer * +engine_service_final_transfer(struct xdma_engine *engine, + struct xdma_transfer *transfer, + u32 *pdesc_completed) +{ + /* inspect the current transfer */ + if (unlikely(!transfer)) { + pr_warn("XDMA: %s: empty xfer\n", engine->name); + return NULL; + } + + if (((engine->dir =3D=3D DMA_FROM_DEVICE) && + (engine->status & XDMA_STAT_C2H_ERR_MASK)) || + ((engine->dir =3D=3D DMA_TO_DEVICE) && + (engine->status & XDMA_STAT_H2C_ERR_MASK))) { + pr_warn("XDMA: %s: status error 0x%x.\n", engine->name, + engine->status); + engine_status_dump(engine); + engine_err_handle(engine, transfer); + goto transfer_del; + } + + if (engine->status & XDMA_STAT_BUSY) + pr_info("XDMA: %s: engine unexpectedly busy, ignoring\n", + engine->name); + + /* the engine stopped on current transfer? */ + if (*pdesc_completed < transfer->desc_num) { + if (engine->eop_flush) { + /* check if eop received */ + struct xdma_result *result =3D transfer->res_virt; + int i; + int max =3D *pdesc_completed; + + for (i =3D 0; i < max; i++) { + if ((result[i].status & RX_STATUS_EOP) !=3D 0) { + transfer->flags |=3D + XFER_FLAG_ST_C2H_EOP_RCVED; + break; + } + } + + transfer->desc_cmpl +=3D *pdesc_completed; + if (!(transfer->flags & XFER_FLAG_ST_C2H_EOP_RCVED)) + return NULL; + + /* mark transfer as successfully completed */ + engine_service_shutdown(engine); + transfer->state =3D TRANSFER_STATE_COMPLETED; + engine->desc_dequeued +=3D transfer->desc_cmpl; + } else { + transfer->state =3D TRANSFER_STATE_FAILED; + pr_warn("XDMA: %s: xfer stopped half-way\n", + engine->name); + + /* add dequeued number of descriptors during this run */ + engine->desc_dequeued +=3D transfer->desc_num; + transfer->desc_cmpl =3D *pdesc_completed; + } + } else { + if (!transfer->cyclic) { + /* + * if the engine stopped on this transfer, + * it should be the last + */ + WARN_ON(*pdesc_completed > transfer->desc_num); + } + /* mark transfer as successfully completed */ + transfer->state =3D TRANSFER_STATE_COMPLETED; + transfer->desc_cmpl =3D transfer->desc_num; + /* add dequeued number of descriptors during this run */ + engine->desc_dequeued +=3D transfer->desc_num; + } + +transfer_del: + /* remove completed transfer from list */ + list_del(engine->transfer_list.next); + + /* + * Complete transfer - sets transfer to NULL if an asynchronous + * transfer has completed + */ + transfer =3D engine_transfer_completion(engine, transfer); + + return transfer; +} + +static int engine_service_resume(struct xdma_engine *engine) +{ + int rv; + + if (!engine->running) { + /* in the case of shutdown, let it finish what's in the Q */ + if (!list_empty(&engine->transfer_list)) { + /* (re)start engine */ + rv =3D engine_start(engine); + if (rv) + return rv; + /* engine was requested to be shutdown? */ + } else if (engine->shutdown & ENGINE_SHUTDOWN_REQUEST) { + engine->shutdown |=3D ENGINE_SHUTDOWN_IDLE; + /* awake task on engine's shutdown wait queue */ + swake_up_one(&engine->shutdown_wq); + } + } else if (list_empty(&engine->transfer_list)) { + engine_service_shutdown(engine); + } + + return 0; +} + +static int engine_service(struct xdma_engine *engine, int desc_writeback) +{ + struct xdma_transfer *transfer =3D NULL; + u32 desc_count =3D desc_writeback & WB_COUNT_MASK; + u32 err_flag =3D desc_writeback & WB_ERR_MASK; + int rv; + + if (!engine->running) { + engine_status_read(engine, 1, 0); + return 0; + } + + /* + * If called by the ISR detected an error, read and clear + * engine status. + */ + if ((desc_count =3D=3D 0) || (err_flag !=3D 0)) + engine_status_read(engine, 1, 0); + + /* + * engine was running but is no longer busy, or writeback occurred, + * shut down + */ + if ((engine->running && !(engine->status & XDMA_STAT_BUSY)) || + (!engine->eop_flush && desc_count !=3D 0)) + engine_service_shutdown(engine); + + /* + * If called from the ISR, or if an error occurred, the descriptor + * count will be zero. In this scenario, read the descriptor count + * from HW. + */ + if (!desc_count) + desc_count =3D ioread32(&engine->regs->completed_desc_count); + if (!desc_count) + goto done; + + /* transfers on queue? */ + if (!list_empty(&engine->transfer_list)) { + /* pick first transfer on queue (was submitted to the engine) */ + transfer =3D list_entry(engine->transfer_list.next, + struct xdma_transfer, entry); + } + + /* account for already dequeued transfers during this engine run */ + desc_count -=3D engine->desc_dequeued; + + /* Process all but the last transfer */ + transfer =3D engine_service_transfer_list(engine, transfer, &desc_count); + + /* + * Process final transfer - includes checks of number of descriptors to + * detect faulty completion + */ + transfer =3D engine_service_final_transfer(engine, transfer, &desc_count); + + /* Restart the engine following the servicing */ + if (!engine->eop_flush) { + rv =3D engine_service_resume(engine); + if (rv) + return rv; + } + +done: + return err_flag ? -1 : 0; +} + +static void engine_service_work(struct work_struct *work) +{ + struct xdma_engine *engine; + unsigned long flags; + int rv; + + engine =3D container_of(work, struct xdma_engine, work); + + spin_lock_irqsave(&engine->lock, flags); + + rv =3D engine_service(engine, 0); + if (rv < 0) + goto unlock; + + /* re-enable interrupts for this engine */ + iowrite32(engine->interrupt_enable_mask_value, + &engine->regs->interrupt_enable_mask_w1s); + +unlock: + spin_unlock_irqrestore(&engine->lock, flags); +} + +static irqreturn_t xdma_isr(int irq, void *dev_id) +{ + struct xdma_dev *xdev; + struct xdma_engine *engine; + struct interrupt_regs *irq_regs; + + engine =3D (struct xdma_engine *)dev_id; + xdev =3D engine->xdev; + + irq_regs =3D (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CT= RL); + + /* Disable the interrupt for this engine */ + iowrite32(engine->interrupt_enable_mask_value, + &engine->regs->interrupt_enable_mask_w1c); + /* Dummy read to flush the above write */ + ioread32(&irq_regs->channel_int_pending); + schedule_work(&engine->work); + + return IRQ_HANDLED; +} + +static int is_config_bar(void *bar) +{ + u32 irq_id =3D 0; + u32 cfg_id =3D 0; + u32 mask =3D 0xffff0000; /* Compare only XDMA ID's not Version number */ + struct interrupt_regs *irq_regs =3D + (struct interrupt_regs *)(bar + XDMA_OFS_INT_CTRL); + struct config_regs *cfg_regs =3D + (struct config_regs *)(bar + XDMA_OFS_CONFIG); + + irq_id =3D ioread32(&irq_regs->identifier); + cfg_id =3D ioread32(&cfg_regs->identifier); + + if (((irq_id & mask) =3D=3D IRQ_BLOCK_ID) + && ((cfg_id & mask) =3D=3D CONFIG_BLOCK_ID)) + return 1; + + return 0; +} + +static void unmap_config_bar(struct xdma_dev *xdev, int config_bar_id) +{ + pci_iounmap(xdev->pdev, xdev->config_bar); + pci_release_selected_regions(xdev->pdev, 1U<pdev, config_bar_id); + if (bar_len < XDMA_BAR_SIZE) { + pr_err("XDMA: %d: Not a config BAR\n", config_bar_id); + return -EINVAL; + } + rv =3D pci_request_selected_regions(xdev->pdev, 1U<config_bar =3D pci_iomap(xdev->pdev, config_bar_id, bar_len); + if (!xdev->config_bar) { + pr_err("XDMA: Failed to map config BAR memory\n"); + rv =3D -ENOMEM; + goto err_map; + } + if (!is_config_bar(xdev->config_bar)) { + pr_err("XDMA: %d: Not a config BAR\n", config_bar_id); + rv =3D -EINVAL; + goto err_bar; + } + + pr_debug("XDMA: Config BAR %d mapped at %p\n", + config_bar_id, xdev->config_bar); + + return 0; + +err_bar: + pci_iounmap(xdev->pdev, xdev->config_bar); +err_map: + pci_release_selected_regions(xdev->pdev, 1U<config_bar + XDMA_OFS_INT_CTRL); + int i =3D num_channel; + int max =3D i + num_irq; + int j; + + for (j =3D 0; i < max; j++) { + u32 val =3D 0; + int k, shift =3D 0; + + if (clear) + i +=3D 4; + else + for (k =3D 0; k < 4 && i < max; i++, k++, shift +=3D 8) + val |=3D (i & 0x1f) << shift; + + iowrite32(val, &int_regs->user_msi_vector[j]); + } +} + +static void prog_irq_channel(struct xdma_dev *xdev, int num_channel, bool = clear) +{ + struct interrupt_regs *int_regs =3D + (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL); + int i, j; + + for (i =3D 0, j =3D 0; i < num_channel; j++) { + u32 val =3D 0; + int k, shift =3D 0; + + if (clear) + i +=3D 4; + else + for (k =3D 0; k < 4 && i < num_channel; i++, k++, shift +=3D 8) + val |=3D (i & 0x1f) << shift; + + iowrite32(val, &int_regs->channel_msi_vector[j]); + } +} + +static void irq_channel_teardown(struct xdma_dev *xdev, int h2c_channel_ma= x, + int c2h_channel_max) +{ + struct xdma_engine *engine; + int i =3D 0, j =3D 0; + + engine =3D xdev->engine_h2c; + for (i =3D 0; i < h2c_channel_max; i++, j++, engine++) { + if (!engine->irq_line) + break; + free_irq(engine->irq_line, engine); + } + + engine =3D xdev->engine_c2h; + for (i =3D 0; i < c2h_channel_max; i++, j++, engine++) { + if (!engine->irq_line) + break; + free_irq(engine->irq_line, engine); + } +} + +static int irq_channel_setup(struct xdma_dev *xdev, int h2c_channel_max, + int c2h_channel_max) +{ + int i, j, rv; + u32 vector; + struct xdma_engine *engine; + + j =3D h2c_channel_max; + engine =3D xdev->engine_h2c; + for (i =3D 0; i < h2c_channel_max; i++, engine++) { + vector =3D pci_irq_vector(xdev->pdev, i); + rv =3D request_irq(vector, xdma_isr, 0, engine->name, engine); + if (rv) { + pr_err("XDMA: %s: error requesting irq#%d\n", + engine->name, vector); + return rv; + } + pr_info("XDMA: %s: irq#%d\n", engine->name, vector); + engine->irq_line =3D vector; + } + + engine =3D xdev->engine_c2h; + for (i =3D 0; i < c2h_channel_max; i++, j++, engine++) { + vector =3D pci_irq_vector(xdev->pdev, j); + rv =3D request_irq(vector, xdma_isr, 0, engine->name, engine); + if (rv) { + pr_err("XDMA: %s: error requesting irq#%d\n", + engine->name, vector); + return rv; + } + pr_info("XDMA: %s: irq#%d\n", engine->name, vector); + engine->irq_line =3D vector; + } + + return 0; +} + +static void irq_teardown(struct xdma_dev *xdev, int h2c_channel_max, + int c2h_channel_max, int user_irq_max) +{ + int num_channel =3D h2c_channel_max + c2h_channel_max; + + prog_irq_user(xdev, num_channel, user_irq_max, 1); + prog_irq_channel(xdev, num_channel, 1); + + irq_channel_teardown(xdev, h2c_channel_max, c2h_channel_max); +} + +static int irq_setup(struct xdma_dev *xdev, int h2c_channel_max, + int c2h_channel_max, int user_irq_max) +{ + int rv; + int num_channel =3D h2c_channel_max + c2h_channel_max; + + rv =3D irq_channel_setup(xdev, h2c_channel_max, c2h_channel_max); + if (rv) + return rv; + + prog_irq_channel(xdev, num_channel, 0); + prog_irq_user(xdev, num_channel, user_irq_max, 0); + + return 0; +} + +/* Chains the descriptors as a singly-linked list + * + * Each descriptor's next pointer specifies the bus address of the next + * descriptor. + * Terminates the last descriptor to form a singly-linked list. + */ +static void transfer_desc_init(struct xdma_transfer *transfer, int count) +{ + struct xdma_desc *desc_virt =3D transfer->desc_virt; + dma_addr_t desc_bus =3D transfer->desc_bus; + int i; + + BUG_ON(count > XDMA_TRANSFER_MAX_DESC); + + /* create singly-linked list for SG DMA controller */ + for (i =3D 0; i < count - 1; i++) { + /* increment bus address to next in array */ + desc_bus +=3D sizeof(struct xdma_desc); + + /* singly-linked list uses bus addresses */ + desc_virt[i].next_lo =3D cpu_to_le32(PCI_DMA_L(desc_bus)); + desc_virt[i].next_hi =3D cpu_to_le32(PCI_DMA_H(desc_bus)); + desc_virt[i].bytes =3D cpu_to_le32(0); + + desc_virt[i].control =3D cpu_to_le32(DESC_MAGIC); + } + + /* zero the last descriptor next pointer */ + desc_virt[i].next_lo =3D cpu_to_le32(0); + desc_virt[i].next_hi =3D cpu_to_le32(0); + desc_virt[i].bytes =3D cpu_to_le32(0); + desc_virt[i].control =3D cpu_to_le32(DESC_MAGIC); +} + +/* Set how many descriptors are adjacent to this one */ +static void xdma_desc_adjacent(struct xdma_desc *desc, u32 next_adjacent) +{ + /* remember reserved and control bits */ + u32 control =3D le32_to_cpu(desc->control) & 0x0000f0ffUL; + /* merge adjacent and control field */ + control |=3D 0xAD4B0000UL | (next_adjacent << 8); + /* write control and next_adjacent */ + desc->control =3D cpu_to_le32(control); +} + +/* Set complete control field of a descriptor */ +static void xdma_desc_control_set(struct xdma_desc *first, u32 control_fie= ld) +{ + /* remember magic and adjacent number */ + u32 control =3D le32_to_cpu(first->control) & ~(LS_BYTE_MASK); + + /* merge adjacent and control field */ + control |=3D control_field; + /* write control and next_adjacent */ + first->control =3D cpu_to_le32(control); +} + +static inline void xdma_desc_done(struct xdma_desc *desc_virt, int count) +{ + memset(desc_virt, 0, count * sizeof(struct xdma_desc)); +} + +/* Fill a descriptor with the transfer details */ +static void xdma_desc_set(struct xdma_desc *desc, dma_addr_t rc_bus_addr, + u64 ep_addr, int len, int dir) +{ + /* transfer length */ + desc->bytes =3D cpu_to_le32(len); + if (dir =3D=3D DMA_TO_DEVICE) { + /* read from root complex memory (source address) */ + desc->src_addr_lo =3D cpu_to_le32(PCI_DMA_L(rc_bus_addr)); + desc->src_addr_hi =3D cpu_to_le32(PCI_DMA_H(rc_bus_addr)); + /* write to end point address (destination address) */ + desc->dst_addr_lo =3D cpu_to_le32(PCI_DMA_L(ep_addr)); + desc->dst_addr_hi =3D cpu_to_le32(PCI_DMA_H(ep_addr)); + } else { + /* read from end point address (source address) */ + desc->src_addr_lo =3D cpu_to_le32(PCI_DMA_L(ep_addr)); + desc->src_addr_hi =3D cpu_to_le32(PCI_DMA_H(ep_addr)); + /* write to root complex memory (destination address) */ + desc->dst_addr_lo =3D cpu_to_le32(PCI_DMA_L(rc_bus_addr)); + desc->dst_addr_hi =3D cpu_to_le32(PCI_DMA_H(rc_bus_addr)); + } +} + +static void transfer_abort(struct xdma_engine *engine, + struct xdma_transfer *transfer) +{ + struct xdma_transfer *head; + + head =3D list_entry(engine->transfer_list.next, struct xdma_transfer, + entry); + if (head =3D=3D transfer) + list_del(engine->transfer_list.next); + else + pr_warn("XDMA: %s: transfer for abort NOT found\n", + engine->name); + + if (transfer->state =3D=3D TRANSFER_STATE_SUBMITTED) + transfer->state =3D TRANSFER_STATE_ABORTED; +} + +static int transfer_queue(struct xdma_engine *engine, + struct xdma_transfer *transfer) +{ + int rv =3D 0; + unsigned long flags; + + /* lock the engine state */ + spin_lock_irqsave(&engine->lock, flags); + + engine->prev_cpu =3D get_cpu(); + put_cpu(); + + /* engine is being shutdown; do not accept new transfers */ + if (engine->shutdown & ENGINE_SHUTDOWN_REQUEST) { + pr_info("XDMA: %s: engine offline, transfer not queued\n", + engine->name); + rv =3D -EBUSY; + goto shutdown; + } + + /* mark the transfer as submitted */ + transfer->state =3D TRANSFER_STATE_SUBMITTED; + /* add transfer to the tail of the engine transfer queue */ + list_add_tail(&transfer->entry, &engine->transfer_list); + + if (!engine->running) + rv =3D engine_start(engine); + +shutdown: + spin_unlock_irqrestore(&engine->lock, flags); + + return rv; +} + +static void engine_alignments(struct xdma_engine *engine) +{ + u32 w =3D ioread32(&engine->regs->alignments); + + if (w) { + engine->addr_align =3D (w & 0x00ff0000U) >> 16; + engine->len_granularity =3D (w & 0x0000ff00U) >> 8; + engine->addr_bits =3D (w & 0x000000ffU); + } else { + /* Some default values if alignments are unspecified */ + engine->addr_align =3D 1; + engine->len_granularity =3D 1; + engine->addr_bits =3D 64; + } +} + +static void engine_free_resource(struct xdma_engine *engine) +{ + struct xdma_dev *xdev =3D engine->xdev; + + if (engine->desc) { + dma_free_coherent(&xdev->pdev->dev, + XDMA_TRANSFER_MAX_DESC * + sizeof(struct xdma_desc), + engine->desc, engine->desc_bus); + engine->desc =3D NULL; + } + + if (engine->cyclic_result) { + dma_free_coherent( + &xdev->pdev->dev, + XDMA_TRANSFER_MAX_DESC * sizeof(struct xdma_result), + engine->cyclic_result, engine->cyclic_result_bus); + engine->cyclic_result =3D NULL; + } +} + +static void engine_destroy(struct xdma_dev *xdev, struct xdma_engine *engi= ne) +{ + /* Disable interrupts to stop processing new events during shutdown */ + iowrite32(0x0, &engine->regs->interrupt_enable_mask); + + if (enable_credit_mp && engine->streaming && + engine->dir =3D=3D DMA_FROM_DEVICE) { + u32 reg_value =3D (0x1 << engine->channel) << 16; + struct sgdma_common_regs *reg =3D + (struct sgdma_common_regs *) + (xdev->config_bar + (0x6 * TARGET_SPACING)); + iowrite32(reg_value, ®->credit_mode_enable_w1c); + } + + /* Release memory use for descriptor writebacks */ + engine_free_resource(engine); + + memset(engine, 0, sizeof(struct xdma_engine)); + /* Decrement the number of engines available */ + xdev->engines_num--; +} + +static void engine_init_regs(struct xdma_engine *engine) +{ + u32 reg_value; + + iowrite32(XDMA_CTRL_NON_INCR_ADDR, &engine->regs->control_w1c); + + engine_alignments(engine); + + /* Configure error interrupts by default */ + reg_value =3D XDMA_CTRL_IE_DESC_ALIGN_MISMATCH; + reg_value |=3D XDMA_CTRL_IE_MAGIC_STOPPED; + reg_value |=3D XDMA_CTRL_IE_MAGIC_STOPPED; + reg_value |=3D XDMA_CTRL_IE_READ_ERROR; + reg_value |=3D XDMA_CTRL_IE_DESC_ERROR; + + /* enable the relevant completion interrupts */ + reg_value |=3D XDMA_CTRL_IE_DESC_STOPPED; + reg_value |=3D XDMA_CTRL_IE_DESC_COMPLETED; + + /* Apply engine configurations */ + iowrite32(reg_value, &engine->regs->interrupt_enable_mask); + + engine->interrupt_enable_mask_value =3D reg_value; + + /* only enable credit mode for AXI-ST C2H */ + if (enable_credit_mp && engine->streaming && + engine->dir =3D=3D DMA_FROM_DEVICE) { + struct xdma_dev *xdev =3D engine->xdev; + u32 reg_value =3D (0x1 << engine->channel) << 16; + struct sgdma_common_regs *reg =3D + (struct sgdma_common_regs *) + (xdev->config_bar + (0x6 * TARGET_SPACING)); + + iowrite32(reg_value, ®->credit_mode_enable_w1s); + } +} + +static int engine_alloc_resource(struct xdma_engine *engine) +{ + struct xdma_dev *xdev =3D engine->xdev; + + engine->desc =3D dma_alloc_coherent(&xdev->pdev->dev, + XDMA_TRANSFER_MAX_DESC * + sizeof(struct xdma_desc), + &engine->desc_bus, GFP_KERNEL); + if (!engine->desc) + goto err_out; + + if (engine->streaming && engine->dir =3D=3D DMA_FROM_DEVICE) { + engine->cyclic_result =3D dma_alloc_coherent( + &xdev->pdev->dev, + XDMA_TRANSFER_MAX_DESC * sizeof(struct xdma_result), + &engine->cyclic_result_bus, GFP_KERNEL); + + if (!engine->cyclic_result) + goto err_out; + } + + return 0; + +err_out: + engine_free_resource(engine); + return -ENOMEM; +} + +static int engine_init(struct xdma_engine *engine, struct xdma_dev *xdev, + int offset, enum dma_data_direction dir, int channel) +{ + int rv; + u32 val; + + engine->channel =3D channel; + engine->xdev =3D xdev; + + /* engine interrupt request bit */ + engine->irq_bitmask =3D (1 << XDMA_ENG_IRQ_NUM) - 1; + engine->irq_bitmask <<=3D (xdev->engines_num * XDMA_ENG_IRQ_NUM); + + /* register address */ + engine->regs =3D xdev->config_bar + offset; + engine->sgdma_regs =3D xdev->config_bar + offset + + SGDMA_OFFSET_FROM_CHANNEL; + val =3D ioread32(&engine->regs->identifier); + if (val & 0x8000U) + engine->streaming =3D 1; + + /* remember SG DMA direction */ + engine->dir =3D dir; + sprintf(engine->name, "xdma-%s%d%s", (dir =3D=3D DMA_TO_DEVICE) ? "H2C" := "C2H", + channel, engine->streaming ? "ST" : "MM"); + + /* initialize the deferred work for transfer completion */ + INIT_WORK(&engine->work, engine_service_work); + + xdev->engines_num++; + + rv =3D engine_alloc_resource(engine); + if (rv) + return rv; + engine_init_regs(engine); + + return 0; +} + +static void transfer_destroy(struct xdma_dev *xdev, struct xdma_transfer *= xfer) +{ + xdma_desc_done(xfer->desc_virt, xfer->desc_num); + + if (xfer->last_in_request && (xfer->flags & XFER_FLAG_NEED_UNMAP)) { + struct sg_table *sgt =3D xfer->sgt; + + if (sgt->nents) { + dma_unmap_sg(&xdev->pdev->dev, sgt->sgl, sgt->nents, + xfer->dir); + sgt->nents =3D 0; + } + } +} + +static void transfer_build(struct xdma_engine *engine, + struct xdma_request_cb *req, struct xdma_transfer *xfer, + unsigned int desc_max) +{ + struct sw_desc *sdesc =3D &(req->sdesc[req->sw_desc_idx]); + int i, j; + dma_addr_t bus =3D xfer->res_bus; + + for (i =3D 0, j =3D 0; i < desc_max; i++, j++, sdesc++) { + /* fill in descriptor entry j with transfer details */ + xdma_desc_set(xfer->desc_virt + j, sdesc->addr, req->ep_addr, + sdesc->len, xfer->dir); + xfer->len +=3D sdesc->len; + + /* for non-inc-add mode don't increment ep_addr */ + if (!engine->non_incr_addr) + req->ep_addr +=3D sdesc->len; + + if (engine->streaming && engine->dir =3D=3D DMA_FROM_DEVICE) { + memset(xfer->res_virt + j, 0, + sizeof(struct xdma_result)); + xfer->desc_virt[j].src_addr_lo =3D + cpu_to_le32(PCI_DMA_L(bus)); + xfer->desc_virt[j].src_addr_hi =3D + cpu_to_le32(PCI_DMA_H(bus)); + bus +=3D sizeof(struct xdma_result); + } + + } + + req->sw_desc_idx +=3D desc_max; +} + +static void transfer_init(struct xdma_engine *engine, + struct xdma_request_cb *req, struct xdma_transfer *xfer) +{ + unsigned int desc_max =3D min_t(unsigned int, + req->sw_desc_cnt - req->sw_desc_idx, + XDMA_TRANSFER_MAX_DESC); + int i, last; + u32 control; + unsigned long flags; + + memset(xfer, 0, sizeof(*xfer)); + + spin_lock_irqsave(&engine->lock, flags); + init_swait_queue_head(&xfer->wq); + + /* remember direction of transfer */ + xfer->dir =3D engine->dir; + xfer->desc_virt =3D engine->desc + engine->desc_idx; + xfer->res_virt =3D engine->cyclic_result + engine->desc_idx; + xfer->desc_bus =3D engine->desc_bus + + (sizeof(struct xdma_desc) * engine->desc_idx); + xfer->res_bus =3D engine->cyclic_result_bus + + (sizeof(struct xdma_result) * engine->desc_idx); + xfer->desc_index =3D engine->desc_idx; + + if ((engine->desc_idx + desc_max) >=3D XDMA_TRANSFER_MAX_DESC) + desc_max =3D XDMA_TRANSFER_MAX_DESC - engine->desc_idx; + + transfer_desc_init(xfer, desc_max); + transfer_build(engine, req, xfer, desc_max); + + xfer->desc_adjacent =3D desc_max; + + /* terminate last descriptor */ + last =3D desc_max - 1; + /* stop engine, EOP for AXI ST, req IRQ on last descriptor */ + control =3D XDMA_DESC_STOPPED; + control |=3D XDMA_DESC_EOP; + control |=3D XDMA_DESC_COMPLETED; + xdma_desc_control_set(xfer->desc_virt + last, control); + + if (engine->eop_flush) { + for (i =3D 0; i < last; i++) + xdma_desc_control_set(xfer->desc_virt + i, + XDMA_DESC_COMPLETED); + xfer->desc_cmpl_th =3D 1; + } else + xfer->desc_cmpl_th =3D desc_max; + + xfer->desc_num =3D desc_max; + engine->desc_idx =3D (engine->desc_idx + desc_max) % XDMA_TRANSFER_MAX_DE= SC; + engine->desc_used +=3D desc_max; + + /* fill in adjacent numbers */ + for (i =3D 0; i < xfer->desc_num; i++) { + u32 next_adj =3D xdma_get_next_adj(xfer->desc_num - i - 1, + (xfer->desc_virt + i)->next_lo); + xdma_desc_adjacent(xfer->desc_virt + i, next_adj); + } + + spin_unlock_irqrestore(&engine->lock, flags); +} + +static void xdma_request_free(struct xdma_request_cb *req) +{ + kvfree(req); +} + +static struct xdma_request_cb *xdma_request_alloc(struct xdma_dev *xdev, + unsigned int sdesc_nr) +{ + unsigned int size =3D sizeof(struct xdma_request_cb) + + sdesc_nr * sizeof(struct sw_desc); + + return kvzalloc(size, GFP_KERNEL); +} + +static struct xdma_request_cb *xdma_init_request(struct xdma_dev *xdev, + struct sg_table *sgt, + u64 ep_addr) +{ + struct xdma_request_cb *req; + struct scatterlist *sg =3D sgt->sgl; + int max =3D sgt->nents; + int extra =3D 0; + int i, j =3D 0; + + for (i =3D 0; i < max; i++, sg =3D sg_next(sg)) { + unsigned int len =3D sg_dma_len(sg); + + if (unlikely(len > XDMA_DESC_BLEN_MAX)) + extra +=3D (len + XDMA_DESC_BLEN_MAX - 1) / XDMA_DESC_BLEN_MAX; + } + + max +=3D extra; + req =3D xdma_request_alloc(xdev, max); + if (!req) + return NULL; + + req->sgt =3D sgt; + req->ep_addr =3D ep_addr; + + for (i =3D 0, sg =3D sgt->sgl; i < sgt->nents; i++, sg =3D sg_next(sg)) { + unsigned int tlen =3D sg_dma_len(sg); + dma_addr_t addr =3D sg_dma_address(sg); + + req->total_len +=3D tlen; + while (tlen) { + req->sdesc[j].addr =3D addr; + if (tlen > XDMA_DESC_BLEN_MAX) { + req->sdesc[j].len =3D XDMA_DESC_BLEN_MAX; + addr +=3D XDMA_DESC_BLEN_MAX; + tlen -=3D XDMA_DESC_BLEN_MAX; + } else { + req->sdesc[j].len =3D tlen; + tlen =3D 0; + } + j++; + } + } + + if (j > max) { + pr_err("XDMA: Max. transfer length (%d) exceeded", + XDMA_DESC_BLEN_MAX); + xdma_request_free(req); + return NULL; + } + req->sw_desc_cnt =3D j; + + return req; +} + +static struct xdma_engine *channel_engine(struct xdma_core *xdma, int chan= nel, + bool write) +{ + if (write) { + if (channel >=3D xdma->h2c_channel_max) { + pr_err("XDMA: %d: invalid H2C channel\n", channel); + return NULL; + } else + return &xdma->xdev->engine_h2c[channel]; + } else { + if (channel >=3D xdma->c2h_channel_max) { + pr_err("XDMA: %d: invalid C2H channel\n", channel); + return NULL; + } else + return &xdma->xdev->engine_c2h[channel]; + } +} + +static struct xdma_dev *alloc_dev(struct pci_dev *pdev) +{ + int i; + struct xdma_dev *xdev; + struct xdma_engine *engine; + + xdev =3D kzalloc(sizeof(struct xdma_dev), GFP_KERNEL); + if (!xdev) + return NULL; + + xdev->pdev =3D pdev; + + engine =3D xdev->engine_h2c; + for (i =3D 0; i < XDMA_CHANNEL_NUM_MAX; i++, engine++) { + spin_lock_init(&engine->lock); + mutex_init(&engine->desc_lock); + INIT_LIST_HEAD(&engine->transfer_list); + init_swait_queue_head(&engine->shutdown_wq); + } + + engine =3D xdev->engine_c2h; + for (i =3D 0; i < XDMA_CHANNEL_NUM_MAX; i++, engine++) { + spin_lock_init(&engine->lock); + mutex_init(&engine->desc_lock); + INIT_LIST_HEAD(&engine->transfer_list); + init_swait_queue_head(&engine->shutdown_wq); + } + + return xdev; +} + +static int set_dma_mask(struct xdma_dev *xdev) +{ + if (!dma_set_mask(&xdev->pdev->dev, DMA_BIT_MASK(64))) { + pr_devel("XDMA: Using a 64-bit DMA mask\n"); + /* use 32-bit DMA for descriptors */ + dma_set_coherent_mask(&xdev->pdev->dev, DMA_BIT_MASK(32)); + } else if (!dma_set_mask(&xdev->pdev->dev, DMA_BIT_MASK(32))) { + pr_devel("XDMA: Using a 32-bit DMA mask\n"); + dma_set_coherent_mask(&xdev->pdev->dev, DMA_BIT_MASK(32)); + } else { + pr_err("XDMA: No suitable DMA possible.\n"); + return -EINVAL; + } + + return 0; +} + +static int get_engine_channel_id(struct engine_regs *regs) +{ + int value =3D ioread32(®s->identifier); + + return (value & 0x00000f00U) >> 8; +} + +static int get_engine_id(struct engine_regs *regs) +{ + int value =3D ioread32(®s->identifier); + + return (value & 0xffff0000U) >> 16; +} + +static void remove_engines(struct xdma_dev *xdev, int h2c_channel_max, + int c2h_channel_max) +{ + int i; + + for (i =3D 0; i < h2c_channel_max; i++) + engine_destroy(xdev, &xdev->engine_h2c[i]); + + for (i =3D 0; i < c2h_channel_max; i++) + engine_destroy(xdev, &xdev->engine_c2h[i]); +} + +static int probe_for_engine(struct xdma_dev *xdev, enum dma_data_direction= dir, + int channel) +{ + struct engine_regs *regs; + int offset =3D channel * CHANNEL_SPACING; + u32 engine_id; + u32 engine_id_expected; + u32 channel_id; + struct xdma_engine *engine; + + if (dir =3D=3D DMA_TO_DEVICE) { + engine_id_expected =3D XDMA_ID_H2C; + engine =3D &xdev->engine_h2c[channel]; + } else { + offset +=3D H2C_CHANNEL_OFFSET; + engine_id_expected =3D XDMA_ID_C2H; + engine =3D &xdev->engine_c2h[channel]; + } + + regs =3D xdev->config_bar + offset; + engine_id =3D get_engine_id(regs); + channel_id =3D get_engine_channel_id(regs); + + if ((engine_id !=3D engine_id_expected) || (channel_id !=3D channel)) { + pr_err("XDMA: %s engine #%d not found\n", + dir =3D=3D DMA_TO_DEVICE ? "H2C" : "C2H", channel); + return -EINVAL; + } + + engine_init(engine, xdev, offset, dir, channel); + + return 0; +} + +static int probe_engines(struct xdma_dev *xdev, int h2c_channel_max, + int c2h_channel_max) +{ + int i, rv; + + for (i =3D 0; i < h2c_channel_max; i++) { + rv =3D probe_for_engine(xdev, DMA_TO_DEVICE, i); + if (rv) + return rv; + } + + for (i =3D 0; i < c2h_channel_max; i++) { + rv =3D probe_for_engine(xdev, DMA_FROM_DEVICE, i); + if (rv) + return rv; + } + + return 0; +} + + +int xdma_probe(struct xdma_core *xdma) +{ + int rv; + + if (xdma->user_irq_max > MAX_USER_IRQ) { + pr_err("XDMA: %d: Invalid number of user IRQs\n", + xdma->user_irq_max); + return -EINVAL; + } + if (xdma->h2c_channel_max > XDMA_CHANNEL_NUM_MAX) { + pr_err("XDMA: %d: Invalid number of H2C channels\n", + xdma->h2c_channel_max); + return -EINVAL; + } + if (xdma->c2h_channel_max > XDMA_CHANNEL_NUM_MAX) { + pr_err("XDMA: %d: Invalid number of C2H channels\n", + xdma->c2h_channel_max); + return -EINVAL; + } + + xdma->xdev =3D alloc_dev(xdma->pdev); + if (!xdma->xdev) + return -ENOMEM; + + rv =3D map_config_bar(xdma->xdev, xdma->config_bar_id); + if (rv) + goto err_map; + + rv =3D set_dma_mask(xdma->xdev); + if (rv) + goto err_mask; + + channel_interrupts_disable(xdma->xdev, ~0); + user_interrupts_disable(xdma->xdev, ~0); + /* Flush writes */ + read_interrupts(xdma->xdev); + + rv =3D probe_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel= _max); + if (rv) + goto err_engines; + + rv =3D irq_setup(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max, + xdma->user_irq_max); + if (rv < 0) + goto err_interrupts; + channel_interrupts_enable(xdma->xdev, ~0); + /* Flush writes */ + read_interrupts(xdma->xdev); + + return 0; + +err_interrupts: + irq_teardown(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max, + xdma->user_irq_max); +err_engines: + remove_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max); +err_mask: + unmap_config_bar(xdma->xdev, xdma->config_bar_id); +err_map: + kfree(xdma->xdev); + + return rv; +} +EXPORT_SYMBOL_GPL(xdma_probe); + +void xdma_remove(struct xdma_core *xdma) +{ + channel_interrupts_disable(xdma->xdev, ~0); + user_interrupts_disable(xdma->xdev, ~0); + /* Flush writes */ + read_interrupts(xdma->xdev); + + irq_teardown(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max, + xdma->user_irq_max); + + remove_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max); + unmap_config_bar(xdma->xdev, xdma->config_bar_id); + + kfree(xdma->xdev); +} +EXPORT_SYMBOL_GPL(xdma_remove); + +/** + * xdma_irq_enable - enable XDMA user interrupt(s) + * @xdma: XDMA device handle + * @mask: bitmask of user interrupts (0 ~ 15) to be registered + */ +void xdma_irq_enable(struct xdma_core *xdma, unsigned int mask) +{ + xdma->xdev->mask_irq_user |=3D mask; + user_interrupts_enable(xdma->xdev, mask); + /* Flush writes */ + read_interrupts(xdma->xdev); +} +EXPORT_SYMBOL_GPL(xdma_irq_enable); + +/** + * xdma_irq_disable - disable XDMA user interrupt(s) + * @xdma: XDMA device handle + * @mask: bitmask of user interrupts (0 ~ 15) to be unregistered + */ +void xdma_irq_disable(struct xdma_core *xdma, unsigned int mask) +{ + xdma->xdev->mask_irq_user &=3D ~mask; + user_interrupts_disable(xdma->xdev, mask); + /* Flush writes */ + read_interrupts(xdma->xdev); +} +EXPORT_SYMBOL_GPL(xdma_irq_disable); + +/** + * xdma_transfer - do a DMA transfer + * @xdma: XDMA device handle + * @channel: channel number + * @write: slecets read/write operation + * @ep_addr: offset into the DDR/BRAM (card) memory to read from or write = to + * @sg_table: the scatter-gather list of data buffers + * @timeout_ms: timeout in mili-seconds + * + * Returns # of bytes transferred on success, negative on failure + */ +ssize_t xdma_transfer(struct xdma_core *xdma, int channel, bool write, + u64 ep_addr, struct sg_table *sgt, int timeout_ms) +{ + struct xdma_engine *engine; + int rv =3D 0, i, nents; + ssize_t done =3D 0; + struct xdma_request_cb *req =3D NULL; + + + engine =3D channel_engine(xdma, channel, write); + if (!engine) + return -EINVAL; + + req =3D xdma_init_request(xdma->xdev, sgt, ep_addr); + if (!req) + return -ENOMEM; + + nents =3D req->sw_desc_cnt; + mutex_lock(&engine->desc_lock); + + while (nents) { + unsigned long flags; + struct xdma_transfer *xfer; + + /* build transfer */ + transfer_init(engine, req, &req->tfer); + xfer =3D &req->tfer; + + /* last transfer for the given request? */ + nents -=3D xfer->desc_num; + if (!nents) { + xfer->last_in_request =3D 1; + xfer->sgt =3D sgt; + } + + rv =3D transfer_queue(engine, xfer); + if (rv < 0) + break; + + if (timeout_ms > 0) + swait_event_interruptible_timeout_exclusive(xfer->wq, + (xfer->state !=3D TRANSFER_STATE_SUBMITTED), + msecs_to_jiffies(timeout_ms)); + else + swait_event_interruptible_exclusive(xfer->wq, + (xfer->state !=3D TRANSFER_STATE_SUBMITTED)); + + spin_lock_irqsave(&engine->lock, flags); + + switch (xfer->state) { + case TRANSFER_STATE_COMPLETED: + spin_unlock_irqrestore(&engine->lock, flags); + /* For C2H streaming use writeback results */ + if (engine->streaming && + engine->dir =3D=3D DMA_FROM_DEVICE) { + struct xdma_result *result =3D xfer->res_virt; + + for (i =3D 0; i < xfer->desc_cmpl; i++) + done +=3D result[i].length; + + /* finish the whole request */ + if (engine->eop_flush) + nents =3D 0; + } else + done +=3D xfer->len; + rv =3D 0; + break; + case TRANSFER_STATE_FAILED: + pr_warn("XDMA: transfer failed\n"); + spin_unlock_irqrestore(&engine->lock, flags); + rv =3D -EIO; + break; + default: + /* transfer can still be in-flight */ + pr_warn("XDMA: transfer timed out\n"); + engine_status_read(engine, 0, 1); + transfer_abort(engine, xfer); + engine_stop(engine); + spin_unlock_irqrestore(&engine->lock, flags); + rv =3D -ERESTARTSYS; + break; + } + + engine->desc_used -=3D xfer->desc_num; + transfer_destroy(xdma->xdev, xfer); + + if (rv < 0) + break; + } + + mutex_unlock(&engine->desc_lock); + xdma_request_free(req); + + return rv ? rv : done; +} +EXPORT_SYMBOL_GPL(xdma_transfer); + +MODULE_AUTHOR("Digiteq Automotive s.r.o."); +MODULE_DESCRIPTION("Xilinx XDMA Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/dma/xilinx_xdma.h b/include/linux/dma/xilinx_xdm= a.h new file mode 100644 index 000000000000..c63dc7768e66 --- /dev/null +++ b/include/linux/dma/xilinx_xdma.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file is part of the Xilinx DMA IP Core driver for Linux + * + * Copyright (c) 2016-2021, Xilinx, Inc. + * Copyright (c) 2020-2022, Digiteq Automotive s.r.o. + */ + +#ifndef XILINX_XDMA_H +#define XILINX_XDMA_H + +#include +#include + +struct xdma_dev; + +/** + * struct xdma_core - representation of XDMA hardware + * @pdev: The parent PCIe device which contains the XDMA core + * @config_bar_id: PCI BAR id where XDMA config regs are located + * @user_irq_max: number of user IRQs + * @c2h_channel_max: number of C2H DMA channels + * @h2c_channel_max: number of H2C DMA channels + * @xdev: struct xdma_dev that is filed by ->probe() + */ +struct xdma_core { + struct pci_dev *pdev; + int config_bar_id; + unsigned int user_irq_max; + unsigned int c2h_channel_max; + unsigned int h2c_channel_max; + struct xdma_dev *xdev; +}; + +int xdma_probe(struct xdma_core *xdma); +void xdma_remove(struct xdma_core *xdma); + +void xdma_irq_enable(struct xdma_core *xdma, unsigned int mask); +void xdma_irq_disable(struct xdma_core *xdma, unsigned int mask); + +ssize_t xdma_transfer(struct xdma_core *xdma, int channel, bool write, + u64 ep_addr, struct sg_table *sgt, int timeout_ms); + +#endif /* XILINX_XDMA_H */ --=20 2.37.2 From nobody Thu Apr 2 22:58:53 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id DEAD6ECAAA1 for ; Mon, 19 Sep 2022 17:36:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229833AbiISRgK (ORCPT ); Mon, 19 Sep 2022 13:36:10 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47132 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230499AbiISRfz (ORCPT ); Mon, 19 Sep 2022 13:35:55 -0400 Received: from mx.gpxsee.org (mx.gpxsee.org [37.205.14.76]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 7A3733A4AF; Mon, 19 Sep 2022 10:35:49 -0700 (PDT) Received: from mgb4.digiteq.red (unknown [62.77.71.229]) by mx.gpxsee.org (Postfix) with ESMTPSA id 4A42540D5E; Mon, 19 Sep 2022 18:56:23 +0200 (CEST) From: tumic@gpxsee.org To: Mauro Carvalho Chehab , Vinod Koul , Michal Simek Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org, linux-i2c@vger.kernel.org, =?UTF-8?q?Martin=20T=C5=AFma?= Subject: [PATCH v2 3/3] Added Digiteq Automotive MGB4 driver Date: Mon, 19 Sep 2022 20:55:56 +0200 Message-Id: <20220919185556.5215-4-tumic@gpxsee.org> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220919185556.5215-1-tumic@gpxsee.org> References: <20220919185556.5215-1-tumic@gpxsee.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Martin T=C5=AFma Digiteq Automotive MGB4 is a modular frame grabber PCIe card for automotive video interfaces. As for now, two modules - FPD-Link and GMSL - are available and supported by the driver. The card has two inputs and two outputs (FPD-Link only). In addition to the video interfaces it also provides a trigger signal interface and a MTD interface for FPGA firmware upload. Signed-off-by: Martin T=C5=AFma --- Documentation/admin-guide/media/mgb4-iio.rst | 30 + Documentation/admin-guide/media/mgb4-mtd.rst | 16 + .../admin-guide/media/mgb4-sysfs.rst | 297 +++++++ drivers/media/pci/Kconfig | 1 + drivers/media/pci/Makefile | 1 + drivers/media/pci/mgb4/Kconfig | 17 + drivers/media/pci/mgb4/Makefile | 6 + drivers/media/pci/mgb4/mgb4_cmt.c | 243 ++++++ drivers/media/pci/mgb4/mgb4_cmt.h | 16 + drivers/media/pci/mgb4/mgb4_core.c | 554 +++++++++++++ drivers/media/pci/mgb4/mgb4_core.h | 58 ++ drivers/media/pci/mgb4/mgb4_i2c.c | 139 ++++ drivers/media/pci/mgb4/mgb4_i2c.h | 35 + drivers/media/pci/mgb4/mgb4_io.h | 36 + drivers/media/pci/mgb4/mgb4_regs.c | 30 + drivers/media/pci/mgb4/mgb4_regs.h | 35 + drivers/media/pci/mgb4/mgb4_sysfs.h | 18 + drivers/media/pci/mgb4/mgb4_sysfs_in.c | 750 ++++++++++++++++++ drivers/media/pci/mgb4/mgb4_sysfs_out.c | 734 +++++++++++++++++ drivers/media/pci/mgb4/mgb4_sysfs_pci.c | 83 ++ drivers/media/pci/mgb4/mgb4_trigger.c | 202 +++++ drivers/media/pci/mgb4/mgb4_trigger.h | 8 + drivers/media/pci/mgb4/mgb4_vin.c | 656 +++++++++++++++ drivers/media/pci/mgb4/mgb4_vin.h | 64 ++ drivers/media/pci/mgb4/mgb4_vout.c | 502 ++++++++++++ drivers/media/pci/mgb4/mgb4_vout.h | 58 ++ 26 files changed, 4589 insertions(+) create mode 100644 Documentation/admin-guide/media/mgb4-iio.rst create mode 100644 Documentation/admin-guide/media/mgb4-mtd.rst create mode 100644 Documentation/admin-guide/media/mgb4-sysfs.rst create mode 100644 drivers/media/pci/mgb4/Kconfig create mode 100644 drivers/media/pci/mgb4/Makefile create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.c create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.h create mode 100644 drivers/media/pci/mgb4/mgb4_core.c create mode 100644 drivers/media/pci/mgb4/mgb4_core.h create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.c create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.h create mode 100644 drivers/media/pci/mgb4/mgb4_io.h create mode 100644 drivers/media/pci/mgb4/mgb4_regs.c create mode 100644 drivers/media/pci/mgb4/mgb4_regs.h create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs.h create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_in.c create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_out.c create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_pci.c create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.c create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.h create mode 100644 drivers/media/pci/mgb4/mgb4_vin.c create mode 100644 drivers/media/pci/mgb4/mgb4_vin.h create mode 100644 drivers/media/pci/mgb4/mgb4_vout.c create mode 100644 drivers/media/pci/mgb4/mgb4_vout.h diff --git a/Documentation/admin-guide/media/mgb4-iio.rst b/Documentation/a= dmin-guide/media/mgb4-iio.rst new file mode 100644 index 000000000000..8e708ddd1b15 --- /dev/null +++ b/Documentation/admin-guide/media/mgb4-iio.rst @@ -0,0 +1,30 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +mgb4 iio (triggers) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The mgb4 driver creates an Industrial I/O (IIO) device that provides trigg= er and +signal level status capability. The following scan elements are available: + +**activity**: + The trigger levels and pending status. + + | bit 1 - trigger 1 pending + | bit 2 - trigger 2 pending + | bit 5 - trigger 1 level + | bit 6 - trigger 2 level + +**timestamp**: + The trigger event timestamp. + +The iio device can operate either in "raw" mode where you can fetch the si= gnal +levels (activity bits 5 and 6) using sysfs access or in triggered buffer m= ode. +In the triggered buffer mode you can follow the signal level changes (acti= vity +bits 1 and 2) using the iio device in /dev. If you enable the timestamps, = you +will also get the exact trigger event time that can be matched to a video = frame +(every mgb4 video frame has a timestamp with the same clock source). + +*Note: although the activity sample always contains all the status bits, i= t makes +no sense to get the pending bits in raw mode or the level bits in the trig= gered +buffer mode - the values do not represent valid data in such case.* diff --git a/Documentation/admin-guide/media/mgb4-mtd.rst b/Documentation/a= dmin-guide/media/mgb4-mtd.rst new file mode 100644 index 000000000000..ca20b799f00f --- /dev/null +++ b/Documentation/admin-guide/media/mgb4-mtd.rst @@ -0,0 +1,16 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +mgb4 mtd partitions +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The mgb4 driver creates a MTD device with two partitions: + - mgb4-fw.X - FPGA firmware. + - mgb4-data.X - Factory settings, e.g. card serial number. + +The *mgb4-fw* partition is writable and is used for FW updates, *mgb4-data= * is +read-only. The *X* attached to the partition name represents the card numb= er. +Depending on the CONFIG_MTD_PARTITIONED_MASTER kernel configuration, you m= ay +also have a third partition named *mgb4-flash* available in the system. Th= is +partition represents the whole, unpartitioned, card's FLASH memory and one= should +not fiddle with it... diff --git a/Documentation/admin-guide/media/mgb4-sysfs.rst b/Documentation= /admin-guide/media/mgb4-sysfs.rst new file mode 100644 index 000000000000..21ff1b5d026e --- /dev/null +++ b/Documentation/admin-guide/media/mgb4-sysfs.rst @@ -0,0 +1,297 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +mgb4 sysfs interface +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The mgb4 driver provides a sysfs interface, that is used to configure video +stream related parameters (some of them must be set properly before the v4= l2 +device can be opened) and obtain the video device/stream status. + +There are two types of parameters - global / PCI card related, found under +``/sys/class/video4linux/videoX/device`` and module specific found under +``/sys/class/video4linux/videoX``. + + +Global (PCI card) parameters +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D + +**module_type** (R): + Module type. + + | 0 - No module present + | 1 - FPDL3 + | 2 - GMSL + +**module_version** (R): + Module version number. Zero in case of a missing module. + +**fw_type** (R): + Firmware type. + + | 1 - FPDL3 + | 2 - GMSL + +**fw_version** (R): + Firmware version number. + +**serial_number** (R): + Card serial number. The format is:: + + PRODUCT-REVISION-SERIES-SERIAL + + where each component is a 8b number. + +**temperature** (R): + FPGA core temperature in Celsius degree. + +Common FPDL3/GMSL input parameters +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +**input_id** (R): + Input number ID, zero based. + +**oldi_lane_width** (RW): + Number of deserializer output lanes. + + | 0 - single + | 1 - dual + +**color_mapping** (RW): + Mapping of the incoming bits in the signal to the colour bits of the p= ixels. + + | 0 - OLDI/JEIDA + | 1 - SPWG/VESA + +**link_status** (R): + Video link status. If the link is locked, chips are properly connected= and + communicating at the same speed and protocol. The link can be locked w= ithout + an active video stream. + + | 0 - unlocked + | 1 - locked + +**stream_status** (R): + Video stream status. A stream is detected if the link is locked, the i= nput + pixel clock is running and the DE signal is moving. + + | 0 - not detected + | 1 - detected + +**vsync_status** (R): + The type of VSYNC pulses as detected by the video format detector. + + | 0 - active low + | 1 - active high + | 2 - not available + +**hsync_status** (R): + The type of HSYNC pulses as detected by the video format detector. + + | 0 - active low + | 1 - active high + | 2 - not available + +**vsync_gap_length** (RW): + If the incoming video signal does not contain synchronization VSYNC and + HSYNC pulses, these must be generated internally in the FPGA to achieve + the correct frame ordering. This value indicates, how many "empty" pix= els + (pixels with deasserted Data Enable signal) are necessary to generate = the + internal VSYNC pulse. + +**hsync_gap_length** (RW): + If the incoming video signal does not contain synchronization VSYNC and + HSYNC pulses, these must be generated internally in the FPGA to achieve + the correct frame ordering. This value indicates, how many "empty" pix= els + (pixels with deasserted Data Enable signal) are necessary to generate = the + internal HSYNC pulse. The value must be greater than 1 and smaller than + vsync_gap_length. + +**pclk_frequency** (R): + Input pixel clock frequency in kHz. + + *Note: The frequency_range parameter must be set properly first to get + a valid frequency here.* + +**hsync_width** (R): + Width of the HSYNC signal in PCLK clock ticks. + +**vsync_width** (R): + Width of the VSYNC signal in PCLK clock ticks. + +**hback_porch** (R): + Number of PCLK pulses between deassertion of the HSYNC signal and the = first + valid pixel in the video line (marked by DE=3D1). + +**hfront_porch** (R): + Number of PCLK pulses between the end of the last valid pixel in the v= ideo + line (marked by DE=3D1) and assertion of the HSYNC signal. + +**vback_porch** (R): + Number of video lines between deassertion of the VSYNC signal and the = video + line with the first valid pixel (marked by DE=3D1). + +**vfront_porch** (R): + Number of video lines between the end of the last valid pixel line (ma= rked + by DE=3D1) and assertion of the VSYNC signal. + +**frequency_range** (RW) + PLL frequency range of the OLDI input clock generator. The PLL frequen= cy is + derived from the Pixel Clock Frequency (PCLK) and is equal to PCLK if + oldi_lane_width is set to "single" and PCLK/2 if oldi_lane_width is se= t to + "dual". + + | 0 - PLL < 50MHz + | 1 - PLL >=3D 50MHz + + *Note: This parameter can not be changed while the input v4l2 device is + open.* + +**alignment** (RW) + Pixel line alignment. Sets the pixel line alignment in bytes of the fr= ame + buffers provided via the v4l2 interface. The number must be a power of= 2. + + *Note: This parameter can not be changed while the input v4l2 device is + open.* + +Common FPDL3/GMSL output parameters +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +**output_id** (R): + Output number ID, zero based. + +**video_source** (RW): + Output video source. If set to 0 or 1, the source is the corresponding= card + input and the v4l2 output devices are disabled. If set to 2 or 3, the = source + is the corresponding v4l2 video output device. + + | 0 - input 0 + | 1 - input 1 + | 2 - v4l2 output 0 + | 3 - v4l2 output 1 + + *Note: This parameter can not be changed while ANY of the input/output= v4l2 + devices is open.* + +**display_width** (RW): + Display width. There is no autodetection of the connected display, so = the + propper value must be set before the start of streaming. + + *Note: This parameter can not be changed while the output v4l2 device = is + open.* + +**display_height** (RW): + Display height. There is no autodetection of the connected display, so= the + propper value must be set before the start of streaming. + + *Note: This parameter can not be changed while the output v4l2 device = is + open.* + +**frame_rate** (RW): + Output video frame rate in frames per second. + +**hsync_polarity** (RW): + HSYNC signal polarity. + + | 0 - active low + | 1 - active high + +**vsync_polarity** (RW): + VSYNC signal polarity. + + | 0 - active low + | 1 - active high + +**de_polarity** (RW): + DE signal polarity. + + | 0 - active low + | 1 - active high + +**pclk_frequency** (RW): + Output pixel clock frequency. Allowed values are between 25000-190000(= kHz) + and there is a non-linear stepping between two consecutive allowed + frequencies. The driver finds the nearest allowed frequency to the giv= en + value and sets it. When reading this property, you get the exact + frequency set by the driver. + + *Note: This parameter can not be changed while the output v4l2 device = is + open.* + +**hsync_width** (RW): + Width of the HSYNC signal in PCLK clock ticks. + +**vsync_width** (RW): + Width of the VSYNC signal in PCLK clock ticks. + +**hback_porch** (RW): + Number of PCLK pulses between deassertion of the HSYNC signal and the = first + valid pixel in the video line (marked by DE=3D1). + +**hfront_porch** (RW): + Number of PCLK pulses between the end of the last valid pixel in the v= ideo + line (marked by DE=3D1) and assertion of the HSYNC signal. + +**vback_porch** (RW): + Number of video lines between deassertion of the VSYNC signal and the = video + line with the first valid pixel (marked by DE=3D1). + +**vfront_porch** (RW): + Number of video lines between the end of the last valid pixel line (ma= rked + by DE=3D1) and assertion of the VSYNC signal. + +**alignment** (RW) + Pixel line alignment. Sets the pixel line alignment in bytes of the fr= ame + buffers provided via the v4l2 interface. The number must be a power of= 2. + + *Note: This parameter can not be changed while the output v4l2 device = is + open.* + + *Note: This parameter can not be changed when loopback mode is active + (video_source is 0 or 1). When loopback mode is enabled, the alignment= is + automatically set to the alignment of the input device.* + +FPDL3 specific input parameters +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D + +**fpdl3_input_width** (RW): + Number of deserializer input lines. + + | 0 - auto + | 1 - single + | 2 - dual + +FPDL3 specific output parameters +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D + +**fpdl3_output_width** (RW): + Number of serializer output lines. + + | 0 - auto + | 1 - single + | 2 - dual + +GMSL specific input parameters +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D + +**gmsl_mode** (RW): + GMSL speed mode. + + | 0 - 12Gb/s + | 1 - 6Gb/s + | 2 - 3Gb/s + | 3 - 1.5Gb/s + +**gmsl_stream_id** (RW): + The GMSL multi-stream contains up to four video streams. This parameter + selects which stream is captured by the video input. The value is the + zero-based index of the stream. + + *Note: This parameter can not be changed while the input v4l2 device is + open.* + +**gmsl_fec** (RW): + GMSL Forward Error Correction (FEC). + + | 0 - disabled + | 1 - enabled diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig index 1224d908713a..e3f9d0d4e4cc 100644 --- a/drivers/media/pci/Kconfig +++ b/drivers/media/pci/Kconfig @@ -13,6 +13,7 @@ if MEDIA_PCI_SUPPORT if MEDIA_CAMERA_SUPPORT comment "Media capture support" =20 +source "drivers/media/pci/mgb4/Kconfig" source "drivers/media/pci/meye/Kconfig" source "drivers/media/pci/solo6x10/Kconfig" source "drivers/media/pci/sta2x11/Kconfig" diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile index 551169a3e434..8ca819cf3cc1 100644 --- a/drivers/media/pci/Makefile +++ b/drivers/media/pci/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_VIDEO_CX88) +=3D cx88/ obj-$(CONFIG_VIDEO_DT3155) +=3D dt3155/ obj-$(CONFIG_VIDEO_IVTV) +=3D ivtv/ obj-$(CONFIG_VIDEO_MEYE) +=3D meye/ +obj-$(CONFIG_VIDEO_MGB4) +=3D mgb4/ obj-$(CONFIG_VIDEO_SAA7134) +=3D saa7134/ obj-$(CONFIG_VIDEO_SAA7164) +=3D saa7164/ obj-$(CONFIG_VIDEO_SOLO6X10) +=3D solo6x10/ diff --git a/drivers/media/pci/mgb4/Kconfig b/drivers/media/pci/mgb4/Kconfig new file mode 100644 index 000000000000..13fad15a434c --- /dev/null +++ b/drivers/media/pci/mgb4/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_MGB4 + tristate "Digiteq Automotive MGB4 support" + depends on VIDEO_DEV && PCI && I2C && DMADEVICES && SPI && MTD && IIO + select VIDEOBUF2_DMA_SG + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select I2C_XILINX + select SPI_XILINX + select MTD_SPI_NOR + select XILINX_XDMA + help + This is a video4linux driver for Digiteq Automotive MGB4 grabber + cards. + + To compile this driver as a module, choose M here: the + module will be called mgb4. diff --git a/drivers/media/pci/mgb4/Makefile b/drivers/media/pci/mgb4/Makef= ile new file mode 100644 index 000000000000..aeac053b8031 --- /dev/null +++ b/drivers/media/pci/mgb4/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +mgb4-objs :=3D mgb4_regs.o mgb4_core.o mgb4_vin.o mgb4_vout.o \ + mgb4_sysfs_pci.o mgb4_sysfs_in.o mgb4_sysfs_out.o \ + mgb4_i2c.o mgb4_cmt.o mgb4_trigger.o + +obj-$(CONFIG_VIDEO_MGB4) +=3D mgb4.o diff --git a/drivers/media/pci/mgb4/mgb4_cmt.c b/drivers/media/pci/mgb4/mgb= 4_cmt.c new file mode 100644 index 000000000000..3ec394e46bd0 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_cmt.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include +#include "mgb4_core.h" +#include "mgb4_cmt.h" + +static const uint16_t cmt_vals_out[][15] =3D { + {0x1208, 0x0000, 0x171C, 0x0000, 0x1E38, 0x0000, 0x11C7, 0x0000, 0x1041, = 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x130D, 0x0080, 0x0041, = 0x0090, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, }, + {0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x165A, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x1187, 0x0080, 0x1041, = 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1451, 0x0000, 0x0042, = 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x134E, 0x0080, 0x0041, = 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1619, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x179E, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x179F, 0x0080, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17DF, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x128B, 0x0080, 0x0041, = 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, }, + {0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1820, 0x0000, 0x0083, = 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1187, 0x0080, 0x1041, = 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x169B, 0x0080, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x171C, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1515, 0x0080, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1493, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x15D8, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x124A, 0x0080, 0x0041, = 0x010D, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x175D, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1619, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17DF, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17E0, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1820, 0x0000, 0x0083, = 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x13D0, 0x0080, 0x0042, = 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x128B, 0x0080, 0x0041, = 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1820, 0x0000, 0x00C3, = 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x134E, 0x0080, 0x0041, = 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1515, 0x0080, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x00C4, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x11C7, 0x0000, 0x1041, = 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1209, 0x0080, 0x0041, = 0x013F, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x1100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1556, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179F, 0x0080, 0x00C4, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x15D8, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1105, 0x0080, 0x1041, = 0x01E8, 0x6401, 0x65E9, 0xFFFF, 0x9800, 0x1100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1820, 0x0000, 0x00C4, = 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1493, 0x0080, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x138E, 0x0000, 0x0042, = 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x17E0, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x165A, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1187, 0x0080, 0x1041, = 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175E, 0x0080, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179E, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x134E, 0x0080, 0x0041, = 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x165A, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x16DC, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x169A, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x11C7, 0x0000, 0x1041, = 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x169B, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x171D, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x16DB, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1146, 0x0080, 0x1041, = 0x0184, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x171C, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1451, 0x0000, 0x0042, = 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x171D, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1452, 0x0080, 0x0042, = 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x15D8, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1104, 0x0000, 0x1041, = 0x01E8, 0x5801, 0x59E9, 0xFFFF, 0x9900, 0x0900, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179F, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1515, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x17DF, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1659, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1555, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x14D3, 0x0000, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1820, 0x0000, 0x0083, = 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1556, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1187, 0x0080, 0x1041, = 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1452, 0x0080, 0x0082, = 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1514, 0x0000, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17E0, 0x0080, 0x00C4, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1515, 0x0080, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x16DC, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1493, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x15D8, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171D, 0x0080, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1618, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175D, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D4, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1619, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179E, 0x0000, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179F, 0x0080, 0x00C3, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1515, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x13D0, 0x0080, 0x0042, = 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169A, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x128B, 0x0080, 0x0041, = 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1820, 0x0000, 0x00C3, = 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1556, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x16DB, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1411, 0x0080, 0x0042, = 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1597, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1451, 0x0000, 0x0042, = 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171D, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x12CC, 0x0080, 0x0041, = 0x00A9, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175D, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1452, 0x0080, 0x0042, = 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x15D8, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175E, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1492, 0x0000, 0x0042, = 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179F, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1619, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1493, 0x0080, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17DF, 0x0000, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x130D, 0x0080, 0x0041, = 0x0090, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17E0, 0x0080, 0x0083, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D3, 0x0000, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x165A, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1820, 0x0000, 0x0083, = 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D4, 0x0080, 0x0042, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x0082, = 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, +}; + +static const uint16_t cmt_vals_in[][13] =3D { + {0x1082, 0x0000, 0x5104, 0x0000, 0x11C7, 0x0000, 0x1041, 0x02BC, 0x7C01, = 0xFFE9, 0x9900, 0x9908, 0x8100}, + {0x1104, 0x0000, 0x9208, 0x0000, 0x138E, 0x0000, 0x1041, 0x015E, 0x7C01, = 0xFFE9, 0x0100, 0x0908, 0x1000}, +}; + +static const uint32_t cmt_addrs_out[][15] =3D { + {0x420, 0x424, 0x428, 0x42C, 0x430, 0x434, 0x450, 0x454, 0x458, 0x460, 0x= 464, 0x468, 0x4A0, 0x538, 0x53C}, + {0x620, 0x624, 0x628, 0x62C, 0x630, 0x634, 0x650, 0x654, 0x658, 0x660, 0x= 664, 0x668, 0x6A0, 0x738, 0x73C}, +}; + +static const uint32_t cmt_addrs_in[][13] =3D { + {0x020, 0x024, 0x028, 0x02C, 0x050, 0x054, 0x058, 0x060, 0x064, 0x068, 0x= 0A0, 0x138, 0x13C}, + {0x220, 0x224, 0x228, 0x22C, 0x250, 0x254, 0x258, 0x260, 0x264, 0x268, 0x= 2A0, 0x338, 0x33C}, +}; + +static const uint32_t cmt_freq[] =3D { + 25000, 25510, 26020, 26530, 26983, 27551, 28000, 28570, + 29046, 29522, 30000, 30476, 30952, 31546, 32000, 32539, + 33035, 33571, 33928, 34522, 35000, 35428, 36000, 36571, + 36904, 37500, 38093, 38571, 39047, 39453, 40000, 40476, + 40952, 41494, 41964, 42857, 43535, 44047, 44444, 45000, + 45535, 46029, 46428, 46823, 47617, 48214, 48571, 49107, + 49523, 50000, 50476, 50892, 51428, 52380, 53333, 53967, + 54285, 55238, 55555, 55952, 57142, 58095, 58571, 59047, + 59521, 60000, 60316, 60952, 61428, 61904, 62500, 63092, + 63491, 64282, 65078, 65476, 66071, 66664, 67142, 67854, + 68571, 69044, 69642, 70000, 71425, 72616, 73214, 73808, + 74285, 75000, 75714, 76187, 76785, 77142, 78570, 80000, + 80357, 80951, 81428, 82142, 82857, 83332, 83928, 84285, + 85713, 87142, 87500, 88094, 88571, 89285, 90000, 90475, + 91071, 91428, 92856, 94642, +}; + + +static size_t freq_srch(u32 key, const u32 *array, size_t size) +{ + int l =3D 0; + int r =3D size - 1; + int m; + + while (l <=3D r) { + m =3D (l + r) / 2; + if (array[m] < key) + l =3D m + 1; + else if (array[m] > key) + r =3D m - 1; + else + return m; + } + + if (r < 0 || l > size - 1) + return m; + else + return (abs(key - array[l]) < abs(key - array[r])) ? l : r; +} + + +u32 mgb4_cmt_set_vout(struct mgb4_vout_dev *voutdev, unsigned int freq) +{ + const uint16_t *reg_set; + const uint32_t *addr; + u32 config; + size_t i, index; + + index =3D freq_srch(freq, cmt_freq, ARRAY_SIZE(cmt_freq)); + addr =3D cmt_addrs_out[voutdev->config->id]; + reg_set =3D cmt_vals_out[index]; + + config =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.c= onfig); + + mgb4_write_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, + 0x1 | (config & ~0x3)); + + for (i =3D 0; i < ARRAY_SIZE(cmt_addrs_out[0]); i++) + mgb4_write_reg(&voutdev->mgbdev->cmt, addr[i], reg_set[i]); + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, + 0x100, 0x100); + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, + 0x100, 0x0); + + mgb4_write_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, + config & ~0x1); + + return cmt_freq[index]; +} + +void mgb4_cmt_set_vin(struct mgb4_vin_dev *vindev, unsigned int freq_range) +{ + const uint16_t *reg_set; + const uint32_t *addr; + u32 config; + size_t i; + + addr =3D cmt_addrs_in[vindev->config->id]; + reg_set =3D cmt_vals_in[freq_range]; + + config =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.con= fig); + + mgb4_write_reg(&vindev->mgbdev->video, vindev->config->regs.config, + 0x1 | (config & ~0x3)); + + for (i =3D 0; i < ARRAY_SIZE(cmt_addrs_in[0]); i++) + mgb4_write_reg(&vindev->mgbdev->cmt, addr[i], reg_set[i]); + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, + 0x1000, 0x1000); + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, + 0x1000, 0x0); + + mgb4_write_reg(&vindev->mgbdev->video, vindev->config->regs.config, + config & ~0x1); +} diff --git a/drivers/media/pci/mgb4/mgb4_cmt.h b/drivers/media/pci/mgb4/mgb= 4_cmt.h new file mode 100644 index 000000000000..353966654c95 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_cmt.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_CMT_H__ +#define __MGB4_CMT_H__ + +#include "mgb4_vout.h" +#include "mgb4_vin.h" + +u32 mgb4_cmt_set_vout(struct mgb4_vout_dev *voutdev, unsigned int freq); +void mgb4_cmt_set_vin(struct mgb4_vin_dev *vindev, unsigned int freq_range= ); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_core.c b/drivers/media/pci/mgb4/mg= b4_core.c new file mode 100644 index 000000000000..1bfc2712401c --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_core.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This is the driver for the MGB4 video grabber card by Digiteq Automotiv= e. + * + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mgb4_i2c.h" +#include "mgb4_sysfs.h" +#include "mgb4_vout.h" +#include "mgb4_vin.h" +#include "mgb4_trigger.h" +#include "mgb4_core.h" + + +#define MGB4_USER_IRQS 16 + +static int flashid; + +static struct xspi_platform_data spi_platform_data =3D { + .num_chipselect =3D 1, + .bits_per_word =3D 8 +}; + +static const struct i2c_board_info extender_info =3D { + I2C_BOARD_INFO("extender", 0x21) +}; + +static int match_i2c_adap(struct device *dev, void *data) +{ + return (i2c_verify_adapter(dev) !=3D NULL); +} + +static struct i2c_adapter *get_i2c_adap(struct platform_device *pdev) +{ + struct device *dev; + int i; + + for (i =3D 0; i < 10; i++) { + msleep(100); + mutex_lock(&pdev->dev.mutex); + dev =3D device_find_child(&pdev->dev, NULL, match_i2c_adap); + mutex_unlock(&pdev->dev.mutex); + if (dev) + return to_i2c_adapter(dev); + } + + return NULL; +} + +static int match_spi_adap(struct device *dev, void *data) +{ + return (to_spi_device(dev) !=3D NULL); +} + +static struct spi_master *get_spi_adap(struct platform_device *pdev) +{ + struct device *dev; + int i; + + for (i =3D 0; i < 10; i++) { + msleep(100); + mutex_lock(&pdev->dev.mutex); + dev =3D device_find_child(&pdev->dev, NULL, match_spi_adap); + mutex_unlock(&pdev->dev.mutex); + if (dev) + return container_of(dev, struct spi_master, dev); + } + + return NULL; +} + +static int init_spi(struct mgb4_dev *mgbdev) +{ + struct resource spi_resources[] =3D { + { + .start =3D 0x400, + .end =3D 0x47f, + .flags =3D IORESOURCE_MEM, + .name =3D "io-memory", + }, + { + .start =3D 14, + .end =3D 14, + .flags =3D IORESOURCE_IRQ, + .name =3D "irq", + }, + }; + struct spi_board_info spi_info =3D { + .max_speed_hz =3D 10000000, + .modalias =3D "m25p80", + .chip_select =3D 0, + .mode =3D SPI_MODE_3, + }; + struct pci_dev *pdev =3D mgbdev->xdma.pdev; + struct device *dev =3D &pdev->dev; + struct spi_master *master; + struct spi_device *spi_dev; + int rv, id; + resource_size_t mapbase =3D pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + spi_resources[0].parent =3D &pdev->resource[0]; + spi_resources[0].start +=3D mapbase; + spi_resources[0].end +=3D mapbase; + spi_resources[1].start +=3D MGB4_IRQ_BASE(pdev); + spi_resources[1].end +=3D MGB4_IRQ_BASE(pdev); + + xdma_irq_enable(&mgbdev->xdma, 1U<<14); + + id =3D pci_dev_id(pdev); + mgbdev->spi_pdev =3D platform_device_register_resndata(dev, "xilinx_spi", + id, spi_resources, ARRAY_SIZE(spi_resources), &spi_platform_data, + sizeof(spi_platform_data)); + if (IS_ERR(mgbdev->spi_pdev)) { + dev_err(dev, "failed to register SPI device\n"); + return PTR_ERR(mgbdev->spi_pdev); + } + + master =3D get_spi_adap(mgbdev->spi_pdev); + if (!master) { + dev_err(dev, "failed to get SPI adapter\n"); + rv =3D -EINVAL; + goto err_pdev; + } + + snprintf(mgbdev->fw_part_name, sizeof(mgbdev->fw_part_name), "mgb4-fw.%d", + flashid); + mgbdev->partitions[0].name =3D mgbdev->fw_part_name; + mgbdev->partitions[0].size =3D 0x400000; + mgbdev->partitions[0].offset =3D 0x400000; + mgbdev->partitions[0].mask_flags =3D 0; + + snprintf(mgbdev->data_part_name, sizeof(mgbdev->data_part_name), + "mgb4-data.%d", flashid); + mgbdev->partitions[1].name =3D mgbdev->data_part_name; + mgbdev->partitions[1].size =3D 0x10000; + mgbdev->partitions[1].offset =3D 0xFF0000; + mgbdev->partitions[1].mask_flags =3D MTD_CAP_NORFLASH; + + snprintf(mgbdev->flash_name, sizeof(mgbdev->flash_name), "mgb4-flash.%d", + flashid); + mgbdev->flash_data.name =3D mgbdev->flash_name; + mgbdev->flash_data.parts =3D mgbdev->partitions; + mgbdev->flash_data.nr_parts =3D ARRAY_SIZE(mgbdev->partitions); + mgbdev->flash_data.type =3D "spi-nor"; + + spi_info.platform_data =3D &(mgbdev->flash_data); + + spi_dev =3D spi_new_device(master, &spi_info); + put_device(&master->dev); + if (!spi_dev) { + dev_err(dev, "failed to create MTD device\n"); + rv =3D -EINVAL; + goto err_pdev; + } + + return 0; + +err_pdev: + platform_device_unregister(mgbdev->spi_pdev); + + return rv; +} + +static void free_spi(struct mgb4_dev *mgbdev) +{ + platform_device_unregister(mgbdev->spi_pdev); +} + +static int init_i2c(struct mgb4_dev *mgbdev) +{ + struct resource i2c_resources[] =3D { + { + .start =3D 0x200, + .end =3D 0x3ff, + .flags =3D IORESOURCE_MEM, + .name =3D "io-memory", + }, + { + .start =3D 15, + .end =3D 15, + .flags =3D IORESOURCE_IRQ, + .name =3D "irq", + }, + }; + struct pci_dev *pdev =3D mgbdev->xdma.pdev; + struct device *dev =3D &pdev->dev; + char clk_name[16]; + int rv, id; + resource_size_t mapbase =3D pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + i2c_resources[0].parent =3D &pdev->resource[0]; + i2c_resources[0].start +=3D mapbase; + i2c_resources[0].end +=3D mapbase; + i2c_resources[1].start +=3D MGB4_IRQ_BASE(pdev); + i2c_resources[1].end +=3D MGB4_IRQ_BASE(pdev); + + id =3D pci_dev_id(pdev); + + // create dummy clock required by the xiic-i2c adapter + snprintf(clk_name, sizeof(clk_name), "xiic-i2c.%d", id); + mgbdev->i2c_clk =3D clk_hw_register_fixed_rate(NULL, clk_name, NULL, 0, 1= 25000000); + if (IS_ERR(mgbdev->i2c_clk)) { + dev_err(dev, "failed to register I2C clock\n"); + return PTR_ERR(mgbdev->i2c_clk); + } + mgbdev->i2c_cl =3D clkdev_hw_create(mgbdev->i2c_clk, NULL, "xiic-i2c.%d",= id); + if (!mgbdev->i2c_cl) { + dev_err(dev, "failed to register I2C clockdev\n"); + rv =3D -ENOMEM; + goto err_clk; + } + + xdma_irq_enable(&mgbdev->xdma, 1U<<15); + + mgbdev->i2c_pdev =3D platform_device_register_resndata(dev, "xiic-i2c", + id, i2c_resources, ARRAY_SIZE(i2c_resources), NULL, 0); + if (IS_ERR(mgbdev->i2c_pdev)) { + dev_err(dev, "failed to register I2C device\n"); + rv =3D PTR_ERR(mgbdev->i2c_pdev); + goto err_clkdev; + } + + mgbdev->i2c_adap =3D get_i2c_adap(mgbdev->i2c_pdev); + if (!mgbdev->i2c_adap) { + dev_err(dev, "failed to get I2C adapter\n"); + rv =3D -EINVAL; + goto err_pdev; + } + + mutex_init(&mgbdev->i2c_lock); + + return 0; + +err_pdev: + platform_device_unregister(mgbdev->i2c_pdev); +err_clkdev: + clkdev_drop(mgbdev->i2c_cl); +err_clk: + clk_hw_unregister(mgbdev->i2c_clk); + + return rv; +} + +static void free_i2c(struct mgb4_dev *mgbdev) +{ + put_device(&mgbdev->i2c_adap->dev); + platform_device_unregister(mgbdev->i2c_pdev); + clkdev_drop(mgbdev->i2c_cl); + clk_hw_unregister(mgbdev->i2c_clk); +} + +static int init_sysfs(struct pci_dev *pdev) +{ + struct device_attribute **attr, **eattr; + int rv; + + for (attr =3D mgb4_pci_attrs; *attr; attr++) { + rv =3D device_create_file(&pdev->dev, *attr); + if (rv < 0) + goto err_file; + } + + return 0; + +err_file: + for (eattr =3D mgb4_pci_attrs; eattr !=3D attr; eattr++) + device_remove_file(&pdev->dev, *eattr); + + return rv; +} + +static void free_sysfs(struct pci_dev *pdev) +{ + struct device_attribute **attr; + + for (attr =3D mgb4_pci_attrs; *attr; attr++) + device_remove_file(&pdev->dev, *attr); +} + +static int get_serial_number(struct mgb4_dev *mgbdev) +{ + struct device *dev =3D &mgbdev->xdma.pdev->dev; + struct mtd_info *mtd; + size_t rs; + int rv; + + mgbdev->serial_number =3D 0; + + mtd =3D get_mtd_device_nm(mgbdev->data_part_name); + if (IS_ERR(mtd)) { + dev_warn(dev, "failed to get data MTD device\n"); + return -ENOENT; + } + rv =3D mtd_read(mtd, 0, sizeof(mgbdev->serial_number), &rs, + (u_char *)&mgbdev->serial_number); + put_mtd_device(mtd); + if (rv < 0 || rs !=3D sizeof(mgbdev->serial_number)) { + dev_warn(dev, "error reading MTD device\n"); + return -EIO; + } + + return 0; +} + +static int get_module_version(struct mgb4_dev *mgbdev) +{ + struct device *dev =3D &mgbdev->xdma.pdev->dev; + struct mgb4_i2c_client extender; + s32 version; + u32 fw_version; + int rv; + + rv =3D mgb4_i2c_init(&extender, mgbdev->i2c_adap, &extender_info, 8); + if (rv < 0) { + dev_err(dev, "failed to create extender I2C device\n"); + return rv; + } + version =3D mgb4_i2c_read_byte(&extender, 0x00); + mgb4_i2c_free(&extender); + if (version < 0) { + dev_err(dev, "error reading module version\n"); + return -EIO; + } + + mgbdev->module_version =3D ~((u32)version) & 0xff; + if (!(MGB4_IS_FPDL3(mgbdev) || MGB4_IS_GMSL(mgbdev))) { + dev_err(dev, "unknown module type\n"); + return -EINVAL; + } + fw_version =3D mgb4_read_reg(&mgbdev->video, 0xC4); + if (fw_version >> 24 !=3D mgbdev->module_version >> 4) { + dev_err(dev, "module/firmware type mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int map_regs(struct pci_dev *pdev, struct resource *res, + struct mgb4_regs *regs) +{ + int rv; + resource_size_t mapbase =3D pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + res->start +=3D mapbase; + res->end +=3D mapbase; + + rv =3D mgb4_regs_map(res, regs); + if (rv < 0) { + dev_err(&pdev->dev, "failed to map %s registers\n", res->name); + return rv; + } + + return 0; +} + +static int mgb4_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int i, rv; + struct mgb4_dev *mgbdev; + struct resource video =3D { + .start =3D 0x0, + .end =3D 0x100, + .flags =3D IORESOURCE_MEM, + .name =3D "mgb4-video", + }; + struct resource cmt =3D { + .start =3D 0x1000, + .end =3D 0x1800, + .flags =3D IORESOURCE_MEM, + .name =3D "mgb4-cmt", + }; + int irqs =3D MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES + MGB4_USER_IRQS; + + + mgbdev =3D kzalloc(sizeof(*mgbdev), GFP_KERNEL); + if (!mgbdev) + return -ENOMEM; + + pci_set_drvdata(pdev, mgbdev); + + /* PCIe related stuff */ + rv =3D pci_enable_device(pdev); + if (rv) { + dev_err(&pdev->dev, "error enabling PCI device\n"); + goto err_mgbdev; + } + + rv =3D pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELA= X_EN); + if (rv) + dev_warn(&pdev->dev, "error enabling PCIe relaxed ordering\n"); + rv =3D pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_EXT_= TAG); + if (rv) + dev_warn(&pdev->dev, "error enabling PCIe extended tag field\n"); + rv =3D pcie_set_readrq(pdev, 512); + if (rv) + dev_warn(&pdev->dev, "error setting PCIe max. memory read size\n"); + pci_set_master(pdev); + + rv =3D pci_alloc_irq_vectors(pdev, irqs, irqs, PCI_IRQ_MSIX); + if (rv < 0) { + dev_err(&pdev->dev, "error allocating MSI-X IRQs\n"); + goto err_enable_pci; + } + + /* DMA + IRQ engine */ + mgbdev->xdma.pdev =3D pdev; + mgbdev->xdma.config_bar_id =3D MGB4_XDMA_BAR_ID; + mgbdev->xdma.user_irq_max =3D MGB4_USER_IRQS; + mgbdev->xdma.c2h_channel_max =3D MGB4_VIN_DEVICES; + mgbdev->xdma.h2c_channel_max =3D MGB4_VOUT_DEVICES; + + rv =3D xdma_probe(&mgbdev->xdma); + if (rv) { + dev_err(&pdev->dev, "failed to initialize XDMA device\n"); + goto err_alloc_irq; + } + + /* mgb4 video registers */ + rv =3D map_regs(pdev, &video, &mgbdev->video); + if (rv < 0) + goto err_xdev; + /* mgb4 cmt registers */ + rv =3D map_regs(pdev, &cmt, &mgbdev->cmt); + if (rv < 0) + goto err_video_regs; + + /* SPI FLASH */ + rv =3D init_spi(mgbdev); + if (rv < 0) + goto err_cmt_regs; + + /* I2C controller */ + rv =3D init_i2c(mgbdev); + if (rv < 0) + goto err_spi; + + /* PCI card related sysfs attributes */ + rv =3D init_sysfs(pdev); + if (rv < 0) + goto err_i2c; + + /* Get card serial number. On systems without MTD flash support we may + * get an error thus ignore the return value. An invalid serial number + * should not break anything... + */ + if (get_serial_number(mgbdev) < 0) + dev_warn(&pdev->dev, "error reading card serial number\n"); + + /* Get module type. If no valid module is found, skip the video device + * creation part but do not exit with error to allow flashing the card. + */ + rv =3D get_module_version(mgbdev); + if (rv < 0) + goto exit; + + /* Video input v4l2 devices */ + for (i =3D 0; i < MGB4_VIN_DEVICES; i++) + mgbdev->vin[i] =3D mgb4_vin_create(mgbdev, i); + + /* Video output v4l2 devices */ + for (i =3D 0; i < MGB4_VOUT_DEVICES; i++) + mgbdev->vout[i] =3D mgb4_vout_create(mgbdev, i); + + /* Triggers */ + mgbdev->indio_dev =3D mgb4_trigger_create(mgbdev); + +exit: + flashid++; + + return 0; + +err_i2c: + free_i2c(mgbdev); +err_spi: + free_spi(mgbdev); +err_cmt_regs: + mgb4_regs_free(&mgbdev->cmt); +err_video_regs: + mgb4_regs_free(&mgbdev->video); +err_xdev: + xdma_remove(&mgbdev->xdma); +err_alloc_irq: + pci_disable_msix(pdev); +err_enable_pci: + pci_disable_device(pdev); +err_mgbdev: + kfree(mgbdev); + + return rv; +} + +static void mgb4_remove(struct pci_dev *pdev) +{ + struct mgb4_dev *mgbdev =3D pci_get_drvdata(pdev); + int i; + + if (mgbdev->indio_dev) + mgb4_trigger_free(mgbdev->indio_dev); + + for (i =3D 0; i < MGB4_VOUT_DEVICES; i++) + if (mgbdev->vout[i]) + mgb4_vout_free(mgbdev->vout[i]); + for (i =3D 0; i < MGB4_VIN_DEVICES; i++) + if (mgbdev->vin[i]) + mgb4_vin_free(mgbdev->vin[i]); + + free_sysfs(mgbdev->xdma.pdev); + free_spi(mgbdev); + free_i2c(mgbdev); + mgb4_regs_free(&mgbdev->video); + mgb4_regs_free(&mgbdev->cmt); + + xdma_remove(&mgbdev->xdma); + + pci_disable_msix(mgbdev->xdma.pdev); + pci_disable_device(mgbdev->xdma.pdev); + + kfree(mgbdev); +} + +static const struct pci_device_id mgb4_pci_ids[] =3D { + { PCI_DEVICE(0x1ed8, 0x0101), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, mgb4_pci_ids); + +static struct pci_driver mgb4_pci_driver =3D { + .name =3D KBUILD_MODNAME, + .id_table =3D mgb4_pci_ids, + .probe =3D mgb4_probe, + .remove =3D mgb4_remove, +}; + +module_pci_driver(mgb4_pci_driver); + +MODULE_AUTHOR("Digiteq Automotive s.r.o."); +MODULE_DESCRIPTION("Digiteq Automotive MGB4 Driver"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: platform:xiic-i2c platform:xilinx_spi spi-nor"); diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mg= b4_core.h new file mode 100644 index 000000000000..e56dade71bbb --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_core.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_CORE_H__ +#define __MGB4_CORE_H__ + +#include +#include +#include +#include +#include "mgb4_regs.h" + +#define MGB4_VIN_DEVICES 2 +#define MGB4_VOUT_DEVICES 2 + +#define MGB4_MGB4_BAR_ID 0 +#define MGB4_XDMA_BAR_ID 1 + +#define MGB4_IRQ_BASE(pdev) \ + (pci_irq_vector((pdev), MGB4_MGB4_BAR_ID) \ + + MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES) + +#define MGB4_IS_GMSL(mgbdev) \ + ((mgbdev)->module_version >> 4 =3D=3D 2) +#define MGB4_IS_FPDL3(mgbdev) \ + ((mgbdev)->module_version >> 4 =3D=3D 1) + +struct mgb4_dev { + struct xdma_core xdma; + struct mgb4_vin_dev *vin[MGB4_VIN_DEVICES]; + struct mgb4_vout_dev *vout[MGB4_VOUT_DEVICES]; + + struct mgb4_regs video; + struct mgb4_regs cmt; + + struct clk_hw *i2c_clk; + struct clk_lookup *i2c_cl; + struct platform_device *i2c_pdev; + struct i2c_adapter *i2c_adap; + struct mutex i2c_lock; + + struct platform_device *spi_pdev; + struct flash_platform_data flash_data; + char flash_name[16]; + struct mtd_partition partitions[2]; + char fw_part_name[16]; + char data_part_name[16]; + + struct iio_dev *indio_dev; + + u8 module_version; + u32 serial_number; +}; + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_i2c.c b/drivers/media/pci/mgb4/mgb= 4_i2c.c new file mode 100644 index 000000000000..536dc34b7f82 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_i2c.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include "mgb4_i2c.h" + +static int read_r16(struct i2c_client *client, u16 reg, u8 *val, int len) +{ + int ret; + u8 buf[2]; + struct i2c_msg msg[2] =3D { + { + .addr =3D client->addr, + .flags =3D 0, + .len =3D 2, + .buf =3D buf, + }, { + .addr =3D client->addr, + .flags =3D I2C_M_RD, + .len =3D len, + .buf =3D val, + } + }; + + buf[0] =3D (reg >> 8) & 0xff; + buf[1] =3D (reg >> 0) & 0xff; + + ret =3D i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + else if (ret !=3D 2) + return -EREMOTEIO; + else + return 0; +} + +static int write_r16(struct i2c_client *client, u16 reg, const u8 *val, in= t len) +{ + int ret; + u8 buf[4]; + struct i2c_msg msg[1] =3D { + { + .addr =3D client->addr, + .flags =3D 0, + .len =3D 2 + len, + .buf =3D buf, + } + }; + + if (2 + len > sizeof(buf)) + return -EINVAL; + + buf[0] =3D (reg >> 8) & 0xff; + buf[1] =3D (reg >> 0) & 0xff; + memcpy(&buf[2], val, len); + + ret =3D i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + else if (ret !=3D 1) + return -EREMOTEIO; + else + return 0; +} + + +int mgb4_i2c_init(struct mgb4_i2c_client *client, struct i2c_adapter *adap, + struct i2c_board_info const *info, int addr_size) +{ + client->client =3D i2c_new_client_device(adap, info); + if (IS_ERR(client->client)) + return PTR_ERR(client->client); + + client->addr_size =3D addr_size; + + return 0; +} + +void mgb4_i2c_free(struct mgb4_i2c_client *client) +{ + i2c_unregister_device(client->client); +} + + +s32 mgb4_i2c_read_byte(struct mgb4_i2c_client *client, u16 reg) +{ + int ret; + u8 b; + + if (client->addr_size =3D=3D 8) + return i2c_smbus_read_byte_data(client->client, reg); + + ret =3D read_r16(client->client, reg, &b, 1); + if (ret < 0) + return ret; + + return (s32)b; +} + +s32 mgb4_i2c_write_byte(struct mgb4_i2c_client *client, u16 reg, u8 val) +{ + if (client->addr_size =3D=3D 8) + return i2c_smbus_write_byte_data(client->client, reg, val); + else + return write_r16(client->client, reg, &val, 1); +} + +s32 mgb4_i2c_mask_byte(struct mgb4_i2c_client *client, u16 reg, u8 mask, u= 8 val) +{ + s32 ret; + + if (mask !=3D 0xFF) { + ret =3D mgb4_i2c_read_byte(client, reg); + if (ret < 0) + return ret; + val |=3D (u8)ret & ~mask; + } + + return mgb4_i2c_write_byte(client, reg, val); +} + +int mgb4_i2c_configure(struct mgb4_i2c_client *client, + const struct mgb4_i2c_kv *values, size_t count) +{ + size_t i; + s32 res; + + for (i =3D 0; i < count; i++) { + res =3D mgb4_i2c_mask_byte(client, values[i].reg, values[i].mask, + values[i].val); + if (res < 0) + return res; + } + + return 0; +} diff --git a/drivers/media/pci/mgb4/mgb4_i2c.h b/drivers/media/pci/mgb4/mgb= 4_i2c.h new file mode 100644 index 000000000000..e5003927509f --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_i2c.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_I2C_H__ +#define __MGB4_I2C_H__ + +#include + +struct mgb4_i2c_client { + struct i2c_client *client; + int addr_size; +}; + +struct mgb4_i2c_kv { + u16 reg; + u8 mask; + u8 val; +}; + +extern int mgb4_i2c_init(struct mgb4_i2c_client *client, struct i2c_adapte= r *adap, + struct i2c_board_info const *info, int addr_size); +extern void mgb4_i2c_free(struct mgb4_i2c_client *client); + +extern s32 mgb4_i2c_read_byte(struct mgb4_i2c_client *client, u16 reg); +extern s32 mgb4_i2c_write_byte(struct mgb4_i2c_client *client, u16 reg, u8= val); +extern s32 mgb4_i2c_mask_byte(struct mgb4_i2c_client *client, u16 reg, u8 = mask, + u8 val); + +extern int mgb4_i2c_configure(struct mgb4_i2c_client *client, + const struct mgb4_i2c_kv *values, size_t count); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4= _io.h new file mode 100644 index 000000000000..dff92065f2c0 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_io.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_IO_H__ +#define __MGB4_IO_H__ + +#include + +#ifndef VFL_TYPE_GRABBER +#define VFL_TYPE_GRABBER VFL_TYPE_VIDEO +#endif + +#define ERR_NO_REG 0xFFFFFFFE +#define ERR_QUEUE_TIMEOUT 0xFFFFFFFD +#define ERR_QUEUE_EMPTY 0xFFFFFFFC +#define ERR_QUEUE_FULL 0xFFFFFFFB + +#define BYTESPERLINE(width, alignment) \ + (((((width) * 4) - 1) | ((alignment) - 1)) + 1) +#define PADDING(width, alignment) \ + ((BYTESPERLINE((width), (alignment)) - ((width) * 4)) / 4) + +struct frame_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +extern inline struct frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer = *vbuf) +{ + return container_of(vbuf, struct frame_buffer, vb); +} + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_regs.c b/drivers/media/pci/mgb4/mg= b4_regs.c new file mode 100644 index 000000000000..53d4e4503a74 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_regs.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include "mgb4_regs.h" + +int mgb4_regs_map(struct resource *res, struct mgb4_regs *regs) +{ + regs->mapbase =3D res->start; + regs->mapsize =3D res->end - res->start; + + if (!request_mem_region(regs->mapbase, regs->mapsize, res->name)) + return -EINVAL; + regs->membase =3D ioremap(regs->mapbase, regs->mapsize); + if (!regs->membase) { + release_mem_region(regs->mapbase, regs->mapsize); + return -EINVAL; + } + + return 0; +} + +void mgb4_regs_free(struct mgb4_regs *regs) +{ + iounmap(regs->membase); + release_mem_region(regs->mapbase, regs->mapsize); +} diff --git a/drivers/media/pci/mgb4/mgb4_regs.h b/drivers/media/pci/mgb4/mg= b4_regs.h new file mode 100644 index 000000000000..1cc16941ea45 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_regs.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_REGS_H__ +#define __MGB4_REGS_H__ + +#include + +struct mgb4_regs { + resource_size_t mapbase; + resource_size_t mapsize; + void __iomem *membase; +}; + +#define mgb4_write_reg(regs, offset, val) \ + iowrite32(val, (regs)->membase + (offset)) +#define mgb4_read_reg(regs, offset) \ + ioread32((regs)->membase + (offset)) + +static inline void mgb4_mask_reg(struct mgb4_regs *regs, u32 reg, u32 mask, + u32 val) +{ + u32 ret =3D mgb4_read_reg(regs, reg); + + val |=3D ret & ~mask; + mgb4_write_reg(regs, reg, val); +} + +extern int mgb4_regs_map(struct resource *res, struct mgb4_regs *regs); +extern void mgb4_regs_free(struct mgb4_regs *regs); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_sysfs.h b/drivers/media/pci/mgb4/m= gb4_sysfs.h new file mode 100644 index 000000000000..2b42a8ba37f7 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_SYSFS_H__ +#define __MGB4_SYSFS_H__ + +#include + +extern struct device_attribute *mgb4_pci_attrs[]; +extern struct device_attribute *mgb4_fpdl3_in_attrs[]; +extern struct device_attribute *mgb4_gmsl_in_attrs[]; +extern struct device_attribute *mgb4_fpdl3_out_attrs[]; +extern struct device_attribute *mgb4_gmsl_out_attrs[]; + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_in.c b/drivers/media/pci/mgb= 4/mgb4_sysfs_in.c new file mode 100644 index 000000000000..fc024393c7ba --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_in.c @@ -0,0 +1,750 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include "mgb4_core.h" +#include "mgb4_i2c.h" +#include "mgb4_vin.h" +#include "mgb4_cmt.h" +#include "mgb4_sysfs.h" + +/* Common for both FPDL3 and GMSL */ + +static ssize_t read_input_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", vindev->config->id); +} + +static ssize_t read_oldi_lane_width(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u16 i2c_reg; + u8 i2c_mask, i2c_single_val, i2c_dual_val; + u32 config; + int ret; + + i2c_reg =3D MGB4_IS_GMSL(vindev->mgbdev) ? 0x1CE : 0x49; + i2c_mask =3D MGB4_IS_GMSL(vindev->mgbdev) ? 0x0E : 0x03; + i2c_single_val =3D MGB4_IS_GMSL(vindev->mgbdev) ? 0x00 : 0x02; + i2c_dual_val =3D MGB4_IS_GMSL(vindev->mgbdev) ? 0x0E : 0x00; + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_read_byte(&vindev->deser, i2c_reg); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + config =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.con= fig); + + if (((config & (1U<<9)) && ((ret & i2c_mask) !=3D i2c_dual_val)) + || (!(config & (1U<<9)) && ((ret & i2c_mask) !=3D i2c_single_val))) { + dev_err(dev, "I2C/FPGA register value mismatch\n"); + return -EINVAL; + } + + return sprintf(buf, "%s\n", config & (1U<<9) ? "1" : "0"); +} + +static ssize_t write_oldi_lane_width(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 fpga_data; + u16 i2c_reg; + u8 i2c_mask, i2c_data; + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* single */ + fpga_data =3D 0; + i2c_data =3D MGB4_IS_GMSL(vindev->mgbdev) ? 0x00 : 0x02; + break; + case 1: /* dual */ + fpga_data =3D 1U<<9; + i2c_data =3D MGB4_IS_GMSL(vindev->mgbdev) ? 0x0E : 0x00; + break; + default: + return -EINVAL; + } + + i2c_reg =3D MGB4_IS_GMSL(vindev->mgbdev) ? 0x1CE : 0x49; + i2c_mask =3D MGB4_IS_GMSL(vindev->mgbdev) ? 0x0E : 0x03; + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_mask_byte(&vindev->deser, i2c_reg, i2c_mask, i2c_data); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, 1U<<9, + fpga_data); + if (MGB4_IS_GMSL(vindev->mgbdev)) { + /* reset input link */ + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_mask_byte(&vindev->deser, 0x10, 1U<<5, 1U<<5); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + } + + return count; +} + +static ssize_t read_color_mapping(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 config =3D mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.config); + + return sprintf(buf, "%s\n", config & (1U<<8) ? "0" : "1"); +} + +static ssize_t write_color_mapping(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 fpga_data; + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* OLDI/JEIDA */ + fpga_data =3D (1U<<8); + break; + case 1: /* SPWG/VESA */ + fpga_data =3D 0; + break; + default: + return -EINVAL; + } + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, 1U<<8, + fpga_data); + + return count; +} + +static ssize_t read_link_status(struct device *dev, struct device_attribut= e *attr, + char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 status =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs= .status); + + return sprintf(buf, "%s\n", status & (1U<<2) ? "1" : "0"); +} + +static ssize_t read_stream_status(struct device *dev, struct device_attrib= ute *attr, + char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 status =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs= .status); + + return sprintf(buf, "%s\n", ((status & (1<<14)) && (status & (1<<2)) + && (status & (3<<9))) ? "1" : "0"); +} + +static ssize_t read_hsync_status(struct device *dev, struct device_attribu= te *attr, + char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 status =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs= .status); + u32 res; + + if (!(status & (1U<<11))) + res =3D 0x02; // not available + else if (status & (1U<<12)) + res =3D 0x01; // active high + else + res =3D 0x00; // active low + + return sprintf(buf, "%u\n", res); +} + +static ssize_t read_vsync_status(struct device *dev, struct device_attribu= te *attr, + char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 status =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs= .status); + u32 res; + + if (!(status & (1U<<11))) + res =3D 0x02; // not available + else if (status & (1U<<13)) + res =3D 0x01; // active high + else + res =3D 0x00; // active low + + return sprintf(buf, "%u\n", res); +} + +static ssize_t read_hsync_gap(struct device *dev, struct device_attribute = *attr, + char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 sync =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.s= ync); + + return sprintf(buf, "%u\n", sync >> 16); +} + +static ssize_t write_hsync_gap(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync, 0xFFFF00= 00, + val << 16); + + return count; +} + +static ssize_t read_vsync_gap(struct device *dev, struct device_attribute = *attr, + char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 sync =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.s= ync); + + return sprintf(buf, "%u\n", sync & 0xFFFF); +} + +static ssize_t write_vsync_gap(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync, 0xFFFF, = val); + + return count; +} + +static ssize_t read_pclk_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 freq =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.p= clk); + + return sprintf(buf, "%u\n", freq); +} + +static ssize_t read_hsync_width(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.si= gnal); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +static ssize_t read_vsync_width(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.si= gnal2); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +static ssize_t read_hback_porch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.si= gnal); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +static ssize_t read_hfront_porch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.si= gnal); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +static ssize_t read_vback_porch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.si= gnal2); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +static ssize_t read_vfront_porch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.si= gnal2); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +static ssize_t read_frequency_range(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", vindev->freq_range); +} + +static ssize_t write_frequency_range(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + unsigned long val, flags; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + spin_lock_irqsave(&(vindev->vdev.fh_lock), flags); + if (!list_empty(&(vindev->vdev.fh_list))) { + spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags); + return -EBUSY; + } + + mgb4_cmt_set_vin(vindev, val); + vindev->freq_range =3D val; + + spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags); + + return count; +} + +static ssize_t read_alignment(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", vindev->alignment); +} + +static ssize_t write_alignment(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + unsigned long val, flags; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (!val || (val & (val - 1))) + return -EINVAL; + + spin_lock_irqsave(&(vindev->vdev.fh_lock), flags); + if (!list_empty(&(vindev->vdev.fh_list))) { + spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags); + return -EBUSY; + } + + vindev->alignment =3D val; + + spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags); + + return count; +} + +/* FPDL3 only */ + +static ssize_t read_fpdl3_input_width(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_read_byte(&vindev->deser, 0x34); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + switch ((u8)ret & 0x18) { + case 0: + return sprintf(buf, "0\n"); + case 0x10: + return sprintf(buf, "1\n"); + case 0x08: + return sprintf(buf, "2\n"); + default: + return -EINVAL; + } +} + +static ssize_t write_fpdl3_input_width(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + u8 i2c_data; + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* auto */ + i2c_data =3D 0x00; + break; + case 1: /* single */ + i2c_data =3D 0x10; + break; + case 2: /* dual */ + i2c_data =3D 0x08; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_mask_byte(&vindev->deser, 0x34, 0x18, i2c_data); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + + +/* GMSL only */ + +static ssize_t read_gmsl_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + s32 r1, r300, r3; + + mutex_lock(&vindev->mgbdev->i2c_lock); + r1 =3D mgb4_i2c_read_byte(&vindev->deser, 0x01); + r300 =3D mgb4_i2c_read_byte(&vindev->deser, 0x300); + r3 =3D mgb4_i2c_read_byte(&vindev->deser, 0x03); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (r1 < 0 || r300 < 0 || r3 < 0) + return -EIO; + + if ((r1 & 0x03) =3D=3D 0x03 && (r300 & 0x0C) =3D=3D 0x0C && (r3 & 0xC0) = =3D=3D 0xC0) + return sprintf(buf, "0\n"); + else if ((r1 & 0x03) =3D=3D 0x02 && (r300 & 0x0C) =3D=3D 0x08 && (r3 & 0x= C0) =3D=3D 0x00) + return sprintf(buf, "1\n"); + else if ((r1 & 0x03) =3D=3D 0x01 && (r300 & 0x0C) =3D=3D 0x04 && (r3 & 0x= C0) =3D=3D 0x00) + return sprintf(buf, "2\n"); + else if ((r1 & 0x03) =3D=3D 0x00 && (r300 & 0x0C) =3D=3D 0x00 && (r3 & 0x= C0) =3D=3D 0x00) + return sprintf(buf, "3\n"); + else + return -EINVAL; +} + +static ssize_t write_gmsl_mode(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + static const struct mgb4_i2c_kv G12[] =3D { + {0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0}}; + static const struct mgb4_i2c_kv G6[] =3D { + {0x01, 0x03, 0x02}, {0x300, 0x0C, 0x08}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv G3[] =3D { + {0x01, 0x03, 0x01}, {0x300, 0x0C, 0x04}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv G1[] =3D { + {0x01, 0x03, 0x00}, {0x300, 0x0C, 0x00}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv reset[] =3D { + {0x10, 1U<<5, 1U<<5}, {0x300, 1U<<6, 1U<<6}}; + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + const struct mgb4_i2c_kv *values; + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* 12Gb/s */ + values =3D G12; + break; + case 1: /* 6Gb/s */ + values =3D G6; + break; + case 2: /* 3Gb/s */ + values =3D G3; + break; + case 3: /* 1.5Gb/s */ + values =3D G1; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_configure(&vindev->deser, values, 3); + ret |=3D mgb4_i2c_configure(&vindev->deser, reset, 2); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static ssize_t read_gmsl_stream_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_read_byte(&vindev->deser, 0xA0); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return sprintf(buf, "%d\n", ret & 0x03); +} + +static ssize_t write_gmsl_stream_id(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + unsigned long val, flags; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 3) + return -EINVAL; + + spin_lock_irqsave(&(vindev->vdev.fh_lock), flags); + ret =3D list_empty(&(vindev->vdev.fh_list)); + spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags); + if (!ret) + return -EBUSY; + + /* Formaly, there is a race condition here as the change should be formaly + * done under the spinlock, but we only want to prevent a resolution chan= ge + * where possible. However, resolution changes can happen anyway and the + * driver can handle them (they only break the image, not the system). + * + * So instead of trying to workaround the spinlock - mgb4_i2c_mask_byte() + * does sleep - we simply let the rare race condition happen... + */ + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_mask_byte(&vindev->deser, 0xA0, 0x03, (u8)val); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static ssize_t read_gmsl_fec(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + s32 r3e0, r308; + + mutex_lock(&vindev->mgbdev->i2c_lock); + r3e0 =3D mgb4_i2c_read_byte(&vindev->deser, 0x3E0); + r308 =3D mgb4_i2c_read_byte(&vindev->deser, 0x308); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (r3e0 < 0 || r308 < 0) + return -EIO; + + if ((r3e0 & 0x07) =3D=3D 0x00 && (r308 & 0x01) =3D=3D 0x00) + return sprintf(buf, "0\n"); + else if ((r3e0 & 0x07) =3D=3D 0x07 && (r308 & 0x01) =3D=3D 0x01) + return sprintf(buf, "1\n"); + else + return -EINVAL; +} + +static ssize_t write_gmsl_fec(struct device *dev, struct device_attribute = *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vin_dev *vindev =3D video_get_drvdata(vdev); + static const struct mgb4_i2c_kv enable[] =3D { + {0x3E0, 0x07, 0x07}, {0x308, 0x01, 0x01}}; + static const struct mgb4_i2c_kv disable[] =3D { + {0x3E0, 0x07, 0x00}, {0x308, 0x01, 0x00}}; + static const struct mgb4_i2c_kv reset[] =3D { + {0x10, 1U<<5, 1U<<5}, {0x300, 1U<<6, 1U<<6}}; + const struct mgb4_i2c_kv *values; + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* disabled */ + values =3D disable; + break; + case 1: /* enabled */ + values =3D enable; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_configure(&vindev->deser, values, 2); + ret |=3D mgb4_i2c_configure(&vindev->deser, reset, 2); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static DEVICE_ATTR(input_id, 0444, read_input_id, NULL); +static DEVICE_ATTR(oldi_lane_width, 0644, read_oldi_lane_width, + write_oldi_lane_width); +static DEVICE_ATTR(color_mapping, 0644, read_color_mapping, + write_color_mapping); +static DEVICE_ATTR(link_status, 0444, read_link_status, NULL); +static DEVICE_ATTR(stream_status, 0444, read_stream_status, NULL); +static DEVICE_ATTR(hsync_status, 0444, read_hsync_status, NULL); +static DEVICE_ATTR(vsync_status, 0444, read_vsync_status, NULL); +static DEVICE_ATTR(hsync_gap_length, 0644, read_hsync_gap, write_hsync_gap= ); +static DEVICE_ATTR(vsync_gap_length, 0644, read_vsync_gap, write_vsync_gap= ); +static DEVICE_ATTR(pclk_frequency, 0444, read_pclk_frequency, NULL); +static DEVICE_ATTR(hsync_width, 0444, read_hsync_width, NULL); +static DEVICE_ATTR(vsync_width, 0444, read_vsync_width, NULL); +static DEVICE_ATTR(hback_porch, 0444, read_hback_porch, NULL); +static DEVICE_ATTR(hfront_porch, 0444, read_hfront_porch, NULL); +static DEVICE_ATTR(vback_porch, 0444, read_vback_porch, NULL); +static DEVICE_ATTR(vfront_porch, 0444, read_vfront_porch, NULL); +static DEVICE_ATTR(frequency_range, 0644, read_frequency_range, + write_frequency_range); +static DEVICE_ATTR(alignment, 0644, read_alignment, write_alignment); + +static DEVICE_ATTR(fpdl3_input_width, 0644, read_fpdl3_input_width, + write_fpdl3_input_width); + +static DEVICE_ATTR(gmsl_mode, 0644, read_gmsl_mode, write_gmsl_mode); +static DEVICE_ATTR(gmsl_stream_id, 0644, read_gmsl_stream_id, + write_gmsl_stream_id); +static DEVICE_ATTR(gmsl_fec, 0644, read_gmsl_fec, write_gmsl_fec); + +struct device_attribute *mgb4_fpdl3_in_attrs[] =3D { + &dev_attr_input_id, + &dev_attr_link_status, + &dev_attr_stream_status, + &dev_attr_hsync_status, + &dev_attr_vsync_status, + &dev_attr_oldi_lane_width, + &dev_attr_color_mapping, + &dev_attr_hsync_gap_length, + &dev_attr_vsync_gap_length, + &dev_attr_pclk_frequency, + &dev_attr_hsync_width, + &dev_attr_vsync_width, + &dev_attr_hback_porch, + &dev_attr_hfront_porch, + &dev_attr_vback_porch, + &dev_attr_vfront_porch, + &dev_attr_frequency_range, + &dev_attr_alignment, + &dev_attr_fpdl3_input_width, + NULL +}; + +struct device_attribute *mgb4_gmsl_in_attrs[] =3D { + &dev_attr_input_id, + &dev_attr_link_status, + &dev_attr_stream_status, + &dev_attr_hsync_status, + &dev_attr_vsync_status, + &dev_attr_oldi_lane_width, + &dev_attr_color_mapping, + &dev_attr_hsync_gap_length, + &dev_attr_vsync_gap_length, + &dev_attr_pclk_frequency, + &dev_attr_hsync_width, + &dev_attr_vsync_width, + &dev_attr_hback_porch, + &dev_attr_hfront_porch, + &dev_attr_vback_porch, + &dev_attr_vfront_porch, + &dev_attr_frequency_range, + &dev_attr_alignment, + &dev_attr_gmsl_mode, + &dev_attr_gmsl_stream_id, + &dev_attr_gmsl_fec, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mg= b4/mgb4_sysfs_out.c new file mode 100644 index 000000000000..be80b635d1d7 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include "mgb4_core.h" +#include "mgb4_i2c.h" +#include "mgb4_vout.h" +#include "mgb4_vin.h" +#include "mgb4_cmt.h" +#include "mgb4_sysfs.h" + +static int loopin_cnt(struct mgb4_vin_dev *vindev) +{ + struct mgb4_vout_dev *voutdev; + u32 config; + int i, cnt =3D 0; + + for (i =3D 0; i < MGB4_VOUT_DEVICES; i++) { + voutdev =3D vindev->mgbdev->vout[i]; + if (!voutdev) + continue; + + config =3D mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + if ((config & 0xc) >> 2 =3D=3D vindev->config->id) + cnt++; + } + + return cnt; +} + + +/* Common for both FPDL3 and GMSL */ + +static ssize_t read_output_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", voutdev->config->id); +} + +static ssize_t read_video_source(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 config =3D mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + + return sprintf(buf, "%u\n", (config & 0xc) >> 2); +} + +static ssize_t write_video_source(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + struct mgb4_dev *mgbdev =3D voutdev->mgbdev; + struct mgb4_vin_dev *loopin_new =3D 0, *loopin_old =3D 0; + unsigned long val; + unsigned long flags_in[MGB4_VIN_DEVICES], flags_out[MGB4_VOUT_DEVICES]; + ssize_t ret; + u32 config; + int i; + + + memset(flags_in, 0, sizeof(flags_in)); + memset(flags_out, 0, sizeof(flags_out)); + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 3) + return -EINVAL; + + for (i =3D 0; i < MGB4_VIN_DEVICES; i++) + if (mgbdev->vin[i]) + spin_lock_irqsave(&(mgbdev->vin[i]->vdev.fh_lock), flags_in[i]); + for (i =3D 0; i < MGB4_VOUT_DEVICES; i++) + if (mgbdev->vout[i]) + spin_lock_irqsave(&(mgbdev->vout[i]->vdev.fh_lock), flags_out[i]); + + ret =3D -EBUSY; + for (i =3D 0; i < MGB4_VIN_DEVICES; i++) + if (mgbdev->vin[i] && !list_empty(&(mgbdev->vin[i]->vdev.fh_list))) + goto error; + for (i =3D 0; i < MGB4_VOUT_DEVICES; i++) + if (mgbdev->vout[i] && !list_empty(&(mgbdev->vout[i]->vdev.fh_list))) + goto error; + + config =3D mgb4_read_reg(&mgbdev->video, voutdev->config->regs.config); + + if (((config & 0xc) >> 2) < MGB4_VIN_DEVICES) + loopin_old =3D mgbdev->vin[(config & 0xc) >> 2]; + if (val < MGB4_VIN_DEVICES) + loopin_new =3D mgbdev->vin[val]; + if (loopin_old && loopin_cnt(loopin_old) =3D=3D 1) + mgb4_mask_reg(&mgbdev->video, loopin_old->config->regs.config, 0x2, 0x0); + if (loopin_new) + mgb4_mask_reg(&mgbdev->video, loopin_new->config->regs.config, 0x2, 0x2); + + if (val =3D=3D voutdev->config->id + MGB4_VIN_DEVICES) + mgb4_write_reg(&mgbdev->video, voutdev->config->regs.config, + config & ~(1<<1)); + else + mgb4_write_reg(&mgbdev->video, voutdev->config->regs.config, + config | (1U<<1)); + + mgb4_mask_reg(&mgbdev->video, voutdev->config->regs.config, 0xc, val << 2= ); + + ret =3D count; + +error: + for (i =3D MGB4_VOUT_DEVICES - 1; i >=3D 0; i--) + if (mgbdev->vout[i]) + spin_unlock_irqrestore(&(mgbdev->vout[i]->vdev.fh_lock), flags_out[i]); + for (i =3D MGB4_VIN_DEVICES - 1; i >=3D 0; i--) + if (mgbdev->vin[i]) + spin_unlock_irqrestore(&(mgbdev->vin[i]->vdev.fh_lock), flags_in[i]); + + return ret; +} + +static ssize_t read_display_width(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 config =3D mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.resolution); + + return sprintf(buf, "%u\n", config >> 16); +} + +static ssize_t write_display_width(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val, flags; + int ret, busy; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + spin_lock_irqsave(&(voutdev->vdev.fh_lock), flags); + busy =3D !list_empty(&(voutdev->vdev.fh_list)); + if (busy) { + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + return -EBUSY; + } + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.resolution, + 0xFFFF0000, val << 16); + + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + + return count; +} + +static ssize_t read_display_height(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 config =3D mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.resolution); + + return sprintf(buf, "%u\n", config & 0xFFFF); +} + +static ssize_t write_display_height(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val, flags; + int ret, busy; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + spin_lock_irqsave(&(voutdev->vdev.fh_lock), flags); + busy =3D !list_empty(&(voutdev->vdev.fh_list)); + if (busy) { + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + return -EBUSY; + } + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.resolution, + 0xFFFF, val); + + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + + return count; +} + +static ssize_t read_frame_rate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 period =3D mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.frame_period); + + return sprintf(buf, "%u\n", 125000000 / period); +} + +static ssize_t write_frame_rate(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mgb4_write_reg(&voutdev->mgbdev->video, voutdev->config->regs.frame_perio= d, + 125000000 / val); + + return count; +} + +static ssize_t read_hsync_width(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.= hsync); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +static ssize_t write_hsync_width(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, + 0x00FF0000, val << 16); + + return count; +} + +static ssize_t read_vsync_width(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.= vsync); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +static ssize_t write_vsync_width(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + 0x00FF0000, val << 16); + + return count; +} + +static ssize_t read_hback_porch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.= hsync); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +static ssize_t write_hback_porch(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, + 0x0000FF00, val << 8); + + return count; +} + +static ssize_t read_vback_porch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.= vsync); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +static ssize_t write_vback_porch(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + 0x0000FF00, val << 8); + + return count; +} + +static ssize_t read_hfront_porch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.= hsync); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +static ssize_t write_hfront_porch(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, + 0x000000FF, val); + + return count; +} + +static ssize_t read_vfront_porch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 sig =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.= vsync); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +static ssize_t write_vfront_porch(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + 0x000000FF, val); + + return count; +} + +static ssize_t read_alignment(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", voutdev->alignment); +} + +static ssize_t write_alignment(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val, flags; + u32 config; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (!val || (val & (val - 1))) + return -EINVAL; + + spin_lock_irqsave(&(voutdev->vdev.fh_lock), flags); + if (!list_empty(&(voutdev->vdev.fh_list))) { + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + return -EBUSY; + } + /* Do not allow the change if loopback is active */ + config =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.c= onfig); + if (((config & 0xc) >> 2) < 2) { + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + return -EPERM; + } + + voutdev->alignment =3D val; + + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + + return count; +} + +/* FPDL3 only */ + +static ssize_t read_hsync_polarity(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 config =3D mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (config & (1<<31)) >> 31); +} + +static ssize_t write_hsync_polarity(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, (1<<3= 1), + val << 31); + + return count; +} + +static ssize_t read_vsync_polarity(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 config =3D mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (config & (1<<31)) >> 31); +} + +static ssize_t write_vsync_polarity(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, (1<<3= 1), + val << 31); + + return count; +} + +static ssize_t read_de_polarity(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u32 config =3D mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (config & (1<<30)) >> 30); +} + +static ssize_t write_de_polarity(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, (1<<3= 0), + val << 30); + + return count; +} + +static ssize_t read_fpdl3_output_width(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&voutdev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_read_byte(&voutdev->ser, 0x5B); + mutex_unlock(&voutdev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + switch ((u8)ret & 0x03) { + case 0: + return sprintf(buf, "0\n"); + case 1: + return sprintf(buf, "1\n"); + case 3: + return sprintf(buf, "2\n"); + default: + return -EINVAL; + } +} + +static ssize_t write_fpdl3_output_width(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + u8 i2c_data; + unsigned long val; + int ret; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* auto */ + i2c_data =3D 0x00; + break; + case 1: /* single */ + i2c_data =3D 0x01; + break; + case 2: /* dual */ + i2c_data =3D 0x03; + break; + default: + return -EINVAL; + } + + mutex_lock(&voutdev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_mask_byte(&voutdev->ser, 0x5B, 0x03, i2c_data); + mutex_unlock(&voutdev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static ssize_t read_pclk_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + + return sprintf(buf, "%u\n", voutdev->freq); +} + +static ssize_t write_pclk_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev =3D to_video_device(dev); + struct mgb4_vout_dev *voutdev =3D video_get_drvdata(vdev); + unsigned long val, flags; + int ret, busy; + unsigned int dp; + + ret =3D kstrtoul(buf, 10, &val); + if (ret) + return ret; + + spin_lock_irqsave(&(voutdev->vdev.fh_lock), flags); + busy =3D !list_empty(&(voutdev->vdev.fh_list)); + if (busy) { + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + return -EBUSY; + } + + dp =3D (val > 50000) ? 1 : 0; + voutdev->freq =3D mgb4_cmt_set_vout(voutdev, val >> dp) << dp; + + spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags); + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, + 0x10, dp << 4); + mutex_lock(&voutdev->mgbdev->i2c_lock); + ret =3D mgb4_i2c_mask_byte(&voutdev->ser, 0x4F, 1<<6, ((~dp)&1)<<6); + mutex_unlock(&voutdev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + + +static DEVICE_ATTR(output_id, 0444, read_output_id, NULL); +static DEVICE_ATTR(video_source, 0644, read_video_source, write_video_sour= ce); +static DEVICE_ATTR(display_width, 0644, read_display_width, + write_display_width); +static DEVICE_ATTR(display_height, 0644, read_display_height, + write_display_height); +static DEVICE_ATTR(frame_rate, 0644, read_frame_rate, write_frame_rate); +static DEVICE_ATTR(hsync_polarity, 0644, read_hsync_polarity, + write_hsync_polarity); +static DEVICE_ATTR(vsync_polarity, 0644, read_vsync_polarity, + write_vsync_polarity); +static DEVICE_ATTR(de_polarity, 0644, read_de_polarity, + write_de_polarity); +static DEVICE_ATTR(pclk_frequency, 0644, read_pclk_frequency, + write_pclk_frequency); +static DEVICE_ATTR(hsync_width, 0644, read_hsync_width, write_hsync_width); +static DEVICE_ATTR(vsync_width, 0644, read_vsync_width, write_vsync_width); +static DEVICE_ATTR(hback_porch, 0644, read_hback_porch, write_hback_porch); +static DEVICE_ATTR(hfront_porch, 0644, read_hfront_porch, write_hfront_por= ch); +static DEVICE_ATTR(vback_porch, 0644, read_vback_porch, write_vback_porch); +static DEVICE_ATTR(vfront_porch, 0644, read_vfront_porch, write_vfront_por= ch); +static DEVICE_ATTR(alignment, 0644, read_alignment, write_alignment); + +static DEVICE_ATTR(fpdl3_output_width, 0644, read_fpdl3_output_width, + write_fpdl3_output_width); + + +struct device_attribute *mgb4_fpdl3_out_attrs[] =3D { + &dev_attr_output_id, + &dev_attr_video_source, + &dev_attr_display_width, + &dev_attr_display_height, + &dev_attr_frame_rate, + &dev_attr_hsync_polarity, + &dev_attr_vsync_polarity, + &dev_attr_de_polarity, + &dev_attr_pclk_frequency, + &dev_attr_hsync_width, + &dev_attr_vsync_width, + &dev_attr_hback_porch, + &dev_attr_hfront_porch, + &dev_attr_vback_porch, + &dev_attr_vfront_porch, + &dev_attr_alignment, + &dev_attr_fpdl3_output_width, + NULL +}; + +struct device_attribute *mgb4_gmsl_out_attrs[] =3D { + &dev_attr_output_id, + &dev_attr_video_source, + &dev_attr_display_width, + &dev_attr_display_height, + &dev_attr_frame_rate, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_pci.c b/drivers/media/pci/mg= b4/mgb4_sysfs_pci.c new file mode 100644 index 000000000000..a7f59cd9cfc9 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_pci.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include "mgb4_core.h" +#include "mgb4_sysfs.h" + +static ssize_t read_module_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev =3D dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", mgbdev->module_version & 0x0F); +} + +static ssize_t read_module_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev =3D dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", mgbdev->module_version >> 4); +} + +static ssize_t read_fw_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev =3D dev_get_drvdata(dev); + u32 config =3D mgb4_read_reg(&mgbdev->video, 0xC4); + + return sprintf(buf, "%u\n", config & 0xFFFF); +} + +static ssize_t read_fw_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev =3D dev_get_drvdata(dev); + u32 config =3D mgb4_read_reg(&mgbdev->video, 0xC4); + + return sprintf(buf, "%u\n", config >> 24); +} + +static ssize_t read_serial_number(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev =3D dev_get_drvdata(dev); + u32 sn =3D mgbdev->serial_number; + + return sprintf(buf, "%03d-%03d-%03d-%03d\n", sn >> 24, (sn >> 16) & 0xFF, + (sn >> 8) & 0xFF, sn & 0xFF); +} + +static ssize_t read_temperature(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev =3D dev_get_drvdata(dev); + u32 val10, val; + + val =3D mgb4_read_reg(&mgbdev->video, 0xD0); + val10 =3D ((((val >> 20) & 0xFFF) * 503975) - 1118822400) / 409600; + + return sprintf(buf, "%u\n", ((val10 % 10) > 4) + ? (val10 / 10) + 1 : val10 / 10); +} + +static DEVICE_ATTR(module_version, 0444, read_module_version, NULL); +static DEVICE_ATTR(module_type, 0444, read_module_type, NULL); +static DEVICE_ATTR(fw_version, 0444, read_fw_version, NULL); +static DEVICE_ATTR(fw_type, 0444, read_fw_type, NULL); +static DEVICE_ATTR(serial_number, 0444, read_serial_number, NULL); +static DEVICE_ATTR(temperature, 0444, read_temperature, NULL); + +struct device_attribute *mgb4_pci_attrs[] =3D { + &dev_attr_module_type, + &dev_attr_module_version, + &dev_attr_fw_type, + &dev_attr_fw_version, + &dev_attr_serial_number, + &dev_attr_temperature, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_trigger.c b/drivers/media/pci/mgb4= /mgb4_trigger.c new file mode 100644 index 000000000000..c8f87eec768d --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_trigger.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mgb4_core.h" +#include "mgb4_trigger.h" + +struct trigger_data { + struct mgb4_dev *mgbdev; + struct iio_trigger *trig; +}; + +static int trigger_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct trigger_data *st =3D iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + *val =3D mgb4_read_reg(&st->mgbdev->video, 0xA0); + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev =3D iio_trigger_get_drvdata(trig); + struct trigger_data *st =3D iio_priv(indio_dev); + + if (state) + xdma_irq_enable(&st->mgbdev->xdma, 1U<<11); + else + xdma_irq_disable(&st->mgbdev->xdma, 1U<<11); + + return 0; + +} + +static const struct iio_trigger_ops trigger_ops =3D { + .set_trigger_state =3D &trigger_set_state, +}; + +static const struct iio_info trigger_info =3D { + .read_raw =3D trigger_read_raw, +}; + +#define TRIGGER_CHANNEL(_si) { \ + .type =3D IIO_ACTIVITY, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW), \ + .scan_index =3D _si, \ + .scan_type =3D { \ + .sign =3D 'u', \ + .realbits =3D 32, \ + .storagebits =3D 32, \ + .shift =3D 0, \ + .endianness =3D IIO_CPU \ + }, \ +} + +static const struct iio_chan_spec trigger_channels[] =3D { + TRIGGER_CHANNEL(0), + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static irqreturn_t trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf =3D p; + struct iio_dev *indio_dev =3D pf->indio_dev; + struct trigger_data *st =3D iio_priv(indio_dev); + struct { + u32 data; + s64 ts __aligned(8); + } scan; + + scan.data =3D mgb4_read_reg(&st->mgbdev->video, 0xA0); + mgb4_write_reg(&st->mgbdev->video, 0xA0, scan.data); + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, pf->timestamp); + iio_trigger_notify_done(indio_dev->trig); + + mgb4_write_reg(&st->mgbdev->video, 0xB4, 1U<<11); + + return IRQ_HANDLED; +} + +static int probe_trigger(struct iio_dev *indio_dev, int irq) +{ + int ret; + struct trigger_data *st =3D iio_priv(indio_dev); + + st->trig =3D iio_trigger_alloc(&st->mgbdev->xdma.pdev->dev, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + ret =3D request_irq(irq, &iio_trigger_generic_data_rdy_poll, + 0, "mgb4-trigger", st->trig); + if (ret) + goto error_free_trig; + + st->trig->ops =3D &trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret =3D iio_trigger_register(st->trig); + if (ret) + goto error_free_irq; + + indio_dev->trig =3D iio_trigger_get(st->trig); + + return 0; + +error_free_irq: + free_irq(irq, st->trig); +error_free_trig: + iio_trigger_free(st->trig); + + return ret; +} + +static void remove_trigger(struct iio_dev *indio_dev, int irq) +{ + struct trigger_data *st =3D iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(irq, st->trig); + iio_trigger_free(st->trig); +} + +struct iio_dev *mgb4_trigger_create(struct mgb4_dev *mgbdev) +{ + struct iio_dev *indio_dev; + struct trigger_data *data; + struct pci_dev *pdev =3D mgbdev->xdma.pdev; + struct device *dev =3D &pdev->dev; + int rv; + + indio_dev =3D iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return NULL; + + indio_dev->info =3D &trigger_info; + indio_dev->name =3D "mgb4"; + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->channels =3D trigger_channels; + indio_dev->num_channels =3D ARRAY_SIZE(trigger_channels); + + data =3D iio_priv(indio_dev); + data->mgbdev =3D mgbdev; + + rv =3D probe_trigger(indio_dev, MGB4_IRQ_BASE(pdev) + 11); + if (rv < 0) { + dev_err(dev, "iio triggered setup failed\n"); + goto error_alloc; + } + rv =3D iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + trigger_handler, NULL); + if (rv < 0) { + dev_err(dev, "iio triggered buffer setup failed\n"); + goto error_trigger; + } + rv =3D iio_device_register(indio_dev); + if (rv < 0) { + dev_err(dev, "iio device register failed\n"); + goto error_buffer; + } + + return indio_dev; + +error_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_trigger: + remove_trigger(indio_dev, MGB4_IRQ_BASE(pdev) + 11); +error_alloc: + iio_device_free(indio_dev); + + return NULL; +} + +void mgb4_trigger_free(struct iio_dev *indio_dev) +{ + struct trigger_data *st =3D iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + remove_trigger(indio_dev, MGB4_IRQ_BASE(st->mgbdev->xdma.pdev) + 11); + iio_device_free(indio_dev); +} diff --git a/drivers/media/pci/mgb4/mgb4_trigger.h b/drivers/media/pci/mgb4= /mgb4_trigger.h new file mode 100644 index 000000000000..9e6a651817d5 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_trigger.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +extern struct iio_dev *mgb4_trigger_create(struct mgb4_dev *mgbdev); +extern void mgb4_trigger_free(struct iio_dev *indio_dev); diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb= 4_vin.c new file mode 100644 index 000000000000..057ab39f509f --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vin.c @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include +#include +#include +#include +#include +#include "mgb4_core.h" +#include "mgb4_sysfs.h" +#include "mgb4_io.h" +#include "mgb4_vout.h" +#include "mgb4_vin.h" + +static const struct mgb4_vin_config vin_cfg[] =3D { + {0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28}= }, + {1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58}} +}; + +static const struct i2c_board_info fpdl3_deser_info[] =3D { + {I2C_BOARD_INFO("deserializer1", 0x36)}, + {I2C_BOARD_INFO("deserializer2", 0x38)}, +}; +static const struct i2c_board_info gmsl_deser_info[] =3D { + {I2C_BOARD_INFO("deserializer1", 0x4C)}, + {I2C_BOARD_INFO("deserializer2", 0x2A)}, +}; + +static const struct mgb4_i2c_kv fpdl3_i2c[] =3D { + {0x06, 0xFF, 0x04}, {0x07, 0xFF, 0x01}, {0x45, 0xFF, 0xE8}, + {0x49, 0xFF, 0x00}, {0x34, 0xFF, 0x00}, {0x23, 0xFF, 0x00} +}; + +static const struct mgb4_i2c_kv gmsl_i2c[] =3D { + {0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0}, + {0x1CE, 0x0E, 0x0E}, {0x11, 0x05, 0x00}, {0x05, 0xC0, 0x40}, + {0x307, 0x0F, 0x00}, {0xA0, 0x03, 0x00}, {0x3E0, 0x07, 0x07}, + {0x308, 0x01, 0x01}, {0x10, 0x20, 0x20}, {0x300, 0x40, 0x40} +}; + + +static struct mgb4_vout_dev *loopback_dev(struct mgb4_vin_dev *vindev, int= i) +{ + struct mgb4_vout_dev *voutdev; + u32 config; + + voutdev =3D vindev->mgbdev->vout[i]; + if (!voutdev) + return NULL; + + config =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.c= onfig); + if ((config & 0xc) >> 2 =3D=3D vindev->config->id) + return voutdev; + + return NULL; +} + +static int loopback_active(struct mgb4_vin_dev *vindev) +{ + int i; + + for (i =3D 0; i < MGB4_VOUT_DEVICES; i++) + if (loopback_dev(vindev, i)) + return 1; + + return 0; +} + +static void set_loopback_padding(struct mgb4_vin_dev *vindev, u32 padding) +{ + struct mgb4_regs *video =3D &vindev->mgbdev->video; + struct mgb4_vout_dev *voutdev; + int i; + + for (i =3D 0; i < MGB4_VOUT_DEVICES; i++) { + voutdev =3D loopback_dev(vindev, i); + if (voutdev) + mgb4_write_reg(video, voutdev->config->regs.padding, padding); + } +} + +static void return_all_buffers(struct mgb4_vin_dev *vindev, + enum vb2_buffer_state state) +{ + struct frame_buffer *buf, *node; + unsigned long flags; + + spin_lock_irqsave(&vindev->qlock, flags); + list_for_each_entry_safe(buf, node, &vindev->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + spin_unlock_irqrestore(&vindev->qlock, flags); +} + +static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mgb4_vin_dev *vindev =3D vb2_get_drv_priv(q); + unsigned int size =3D BYTESPERLINE(vindev->width, vindev->alignment) + * vindev->height; + + if (*nbuffers < 2) + *nbuffers =3D 2; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + *nplanes =3D 1; + sizes[0] =3D size; + + return 0; +} + +static int buffer_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct frame_buffer *buf =3D to_frame_buffer(vbuf); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct mgb4_vin_dev *vindev =3D vb2_get_drv_priv(vb->vb2_queue); + struct device *dev =3D &vindev->mgbdev->xdma.pdev->dev; + unsigned int size =3D BYTESPERLINE(vindev->width, vindev->alignment) + * vindev->height; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(dev, "buffer too small (%lu < %u)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct mgb4_vin_dev *vindev =3D vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct frame_buffer *buf =3D to_frame_buffer(vbuf); + unsigned long flags; + + spin_lock_irqsave(&vindev->qlock, flags); + list_add_tail(&buf->list, &vindev->buf_list); + spin_unlock_irqrestore(&vindev->qlock, flags); +} + +static void stop_streaming(struct vb2_queue *vq) +{ + struct mgb4_vin_dev *vindev =3D vb2_get_drv_priv(vq); + + xdma_irq_disable(&vindev->mgbdev->xdma, 1U<config->vin_irq); + + if (!loopback_active(vindev)) + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, + 0x2, 0x0); + + cancel_work_sync(&vindev->dma_work); + return_all_buffers(vindev, VB2_BUF_STATE_ERROR); +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct mgb4_vin_dev *vindev =3D vb2_get_drv_priv(vq); + u32 resolution =3D mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.resolution); + + if ((vindev->width !=3D (resolution >> 16)) + || (vindev->height !=3D (resolution & 0xFFFF))) { + return_all_buffers(vindev, VB2_BUF_STATE_ERROR); + return -EIO; + } + + vindev->sequence =3D 0; + + if (!loopback_active(vindev)) + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, + 0x2, 0x2); + + xdma_irq_enable(&vindev->mgbdev->xdma, 1U<config->vin_irq); + + return 0; +} + +static const struct vb2_ops queue_ops =3D { + .queue_setup =3D queue_setup, + .buf_init =3D buffer_init, + .buf_prepare =3D buffer_prepare, + .buf_queue =3D buffer_queue, + .start_streaming =3D start_streaming, + .stop_streaming =3D stop_streaming, + .wait_prepare =3D vb2_ops_wait_prepare, + .wait_finish =3D vb2_ops_wait_finish +}; + +static int fh_open(struct file *file) +{ + struct mgb4_vin_dev *vindev =3D video_drvdata(file); + struct mgb4_regs *video =3D &vindev->mgbdev->video; + u32 resolution, padding; + int ret; + + ret =3D v4l2_fh_open(file); + if (ret) + return ret; + + resolution =3D mgb4_read_reg(video, vindev->config->regs.resolution); + if (resolution >=3D ERR_NO_REG) + goto error; + + vindev->width =3D resolution >> 16; + vindev->height =3D resolution & 0xFFFF; + if (!vindev->width || !vindev->height) + goto error; + + xdma_irq_enable(&vindev->mgbdev->xdma, 1U<config->err_irq); + + vindev->period =3D mgb4_read_reg(video, vindev->config->regs.frame_period= ); + + padding =3D PADDING(vindev->width, vindev->alignment); + mgb4_write_reg(video, vindev->config->regs.padding, padding); + set_loopback_padding(vindev, padding); + + return 0; + +error: + v4l2_fh_release(file); + return -EIO; +} + +static int fh_release(struct file *file) +{ + struct mgb4_vin_dev *vindev =3D video_drvdata(file); + struct mgb4_regs *video =3D &vindev->mgbdev->video; + + xdma_irq_disable(&vindev->mgbdev->xdma, 1U<config->err_irq); + + mgb4_write_reg(video, vindev->config->regs.padding, 0); + set_loopback_padding(vindev, 0); + + return vb2_fop_release(file); +} + +static const struct v4l2_file_operations video_fops =3D { + .owner =3D THIS_MODULE, + .open =3D fh_open, + .release =3D fh_release, + .unlocked_ioctl =3D video_ioctl2, + .read =3D vb2_fop_read, + .mmap =3D vb2_fop_mmap, + .poll =3D vb2_fop_poll, +}; + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mgb4_vin_dev *vindev =3D video_drvdata(file); + + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "MGB4 PCIe Card", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(vindev->mgbdev->xdma.pdev)); + + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index !=3D 0) + return -EINVAL; + + f->pixelformat =3D V4L2_PIX_FMT_ABGR32; + + return 0; +} + +static int vidioc_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *ival) +{ + struct mgb4_vin_dev *vindev =3D video_drvdata(file); + + if (ival->index !=3D 0) + return -EINVAL; + if (ival->pixel_format !=3D V4L2_PIX_FMT_ABGR32) + return -EINVAL; + if (ival->width !=3D vindev->width || ival->height !=3D vindev->height) + return -EINVAL; + + ival->type =3D V4L2_FRMIVAL_TYPE_DISCRETE; + ival->discrete.numerator =3D vindev->period; + ival->discrete.denominator =3D 125000000; + + return 0; +} + +static int vidioc_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vin_dev *vindev =3D video_drvdata(file); + + f->fmt.pix.pixelformat =3D V4L2_PIX_FMT_ABGR32; + f->fmt.pix.width =3D vindev->width; + f->fmt.pix.height =3D vindev->height; + f->fmt.pix.field =3D V4L2_FIELD_NONE; + f->fmt.pix.colorspace =3D V4L2_COLORSPACE_RAW; + f->fmt.pix.bytesperline =3D BYTESPERLINE(vindev->width, vindev->alignment= ); + f->fmt.pix.sizeimage =3D f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index !=3D 0) + return -EINVAL; + + i->type =3D V4L2_INPUT_TYPE_CAMERA; + strscpy(i->name, "MGB4", sizeof(i->name)); + + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct mgb4_vin_dev *vindev =3D video_drvdata(file); + + if (fsize->index !=3D 0 || fsize->pixel_format !=3D V4L2_PIX_FMT_ABGR32) + return -EINVAL; + + fsize->discrete.width =3D vindev->width; + fsize->discrete.height =3D vindev->height; + fsize->type =3D V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return (i =3D=3D 0) ? 0 : -EINVAL; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i =3D 0; + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + return -EINVAL; +} + +static int vidioc_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct mgb4_vin_dev *vindev =3D video_drvdata(file); + struct v4l2_fract timeperframe =3D { + .numerator =3D vindev->period, + .denominator =3D 125000000, + }; + + if (parm->type !=3D V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + parm->parm.capture.readbuffers =3D 2; + parm->parm.capture.capability =3D V4L2_CAP_TIMEPERFRAME; + parm->parm.capture.timeperframe =3D timeperframe; + + return 0; +} + +static const struct v4l2_ioctl_ops video_ioctl_ops =3D { + .vidioc_querycap =3D vidioc_querycap, + .vidioc_enum_fmt_vid_cap =3D vidioc_enum_fmt, + .vidioc_try_fmt_vid_cap =3D vidioc_fmt, + .vidioc_s_fmt_vid_cap =3D vidioc_fmt, + .vidioc_g_fmt_vid_cap =3D vidioc_fmt, + .vidioc_enum_framesizes =3D vidioc_enum_framesizes, + .vidioc_enum_frameintervals =3D vidioc_enum_frameintervals, + .vidioc_enum_input =3D vidioc_enum_input, + .vidioc_g_input =3D vidioc_g_input, + .vidioc_s_input =3D vidioc_s_input, + .vidioc_queryctrl =3D vidioc_queryctrl, + .vidioc_reqbufs =3D vb2_ioctl_reqbufs, + .vidioc_create_bufs =3D vb2_ioctl_create_bufs, + .vidioc_querybuf =3D vb2_ioctl_querybuf, + .vidioc_qbuf =3D vb2_ioctl_qbuf, + .vidioc_dqbuf =3D vb2_ioctl_dqbuf, + .vidioc_expbuf =3D vb2_ioctl_expbuf, + .vidioc_streamon =3D vb2_ioctl_streamon, + .vidioc_streamoff =3D vb2_ioctl_streamoff, + .vidioc_g_parm =3D vidioc_parm, + .vidioc_s_parm =3D vidioc_parm +}; + + +static void dma_transfer(struct work_struct *work) +{ + struct mgb4_vin_dev *vindev =3D container_of(work, struct mgb4_vin_dev, d= ma_work); + struct device *dev =3D &vindev->mgbdev->xdma.pdev->dev; + struct frame_buffer *buf =3D 0; + unsigned long flags; + u32 addr; + + + spin_lock_irqsave(&vindev->qlock, flags); + if (!list_empty(&vindev->buf_list)) { + buf =3D list_first_entry(&vindev->buf_list, struct frame_buffer, list); + list_del_init(vindev->buf_list.next); + } + spin_unlock_irqrestore(&vindev->qlock, flags); + + if (!buf) + return; + + + addr =3D mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.addre= ss); + if (addr >=3D ERR_QUEUE_FULL) { + dev_warn(dev, "frame queue error (%d)\n", (int)addr); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + if (xdma_transfer(&vindev->mgbdev->xdma, vindev->config->dma_channel, fal= se, + addr, vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0), 1000) > 0) { + buf->vb.vb2_buf.timestamp =3D ktime_get_ns(); + buf->vb.sequence =3D vindev->sequence++; + buf->vb.field =3D V4L2_FIELD_NONE; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } else { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } +} + +static irqreturn_t vin_handler(int irq, void *ctx) +{ + struct mgb4_vin_dev *vindev =3D (struct mgb4_vin_dev *)ctx; + + schedule_work(&vindev->dma_work); + + mgb4_write_reg(&vindev->mgbdev->video, 0xB4, 1U<config->vin_irq); + + return IRQ_HANDLED; +} + +static irqreturn_t err_handler(int irq, void *ctx) +{ + struct mgb4_vin_dev *vindev =3D (struct mgb4_vin_dev *)ctx; + struct device *dev =3D &vindev->mgbdev->xdma.pdev->dev; + struct vb2_queue *vq =3D &(vindev->queue); + + u32 resolution =3D mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.resolution); + + if ((vindev->width !=3D (resolution >> 16)) + || (vindev->height !=3D (resolution & 0xFFFF))) { + dev_warn(dev, "stream changed (%ux%u -> %ux%u)\n", + vindev->width, vindev->height, resolution>>16, + resolution & 0xFFFF); + + if (vb2_is_streaming(vq)) { + return_all_buffers(vindev, VB2_BUF_STATE_ERROR); + vb2_queue_error(vq); + } + } + + mgb4_write_reg(&vindev->mgbdev->video, 0xB4, 1U<config->err_irq); + + return IRQ_HANDLED; +} + +static int deser_init(struct mgb4_vin_dev *vindev, int id) +{ + int rv, addr_size; + size_t values_count; + const struct mgb4_i2c_kv *values; + const struct i2c_board_info *info; + struct device *dev =3D &vindev->mgbdev->xdma.pdev->dev; + + + if (MGB4_IS_GMSL(vindev->mgbdev)) { + info =3D &gmsl_deser_info[id]; + addr_size =3D 16; + values =3D gmsl_i2c; + values_count =3D ARRAY_SIZE(gmsl_i2c); + } else { + info =3D &fpdl3_deser_info[id]; + addr_size =3D 8; + values =3D fpdl3_i2c; + values_count =3D ARRAY_SIZE(fpdl3_i2c); + } + + rv =3D mgb4_i2c_init(&vindev->deser, vindev->mgbdev->i2c_adap, info, addr= _size); + if (rv < 0) { + dev_err(dev, "failed to create deserializer\n"); + return rv; + } + rv =3D mgb4_i2c_configure(&vindev->deser, values, values_count); + if (rv < 0) { + dev_err(dev, "failed to configure deserializer\n"); + goto err_i2c_dev; + } + + return 0; + +err_i2c_dev: + mgb4_i2c_free(&vindev->deser); + + return rv; +} + +struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id) +{ + int rv; + struct device_attribute **attr, **module_attr; + struct mgb4_vin_dev *vindev; + struct pci_dev *pdev =3D mgbdev->xdma.pdev; + struct device *dev =3D &pdev->dev; + int base_irq =3D MGB4_IRQ_BASE(pdev); + + vindev =3D kzalloc(sizeof(struct mgb4_vin_dev), GFP_KERNEL); + if (!vindev) + return NULL; + + vindev->mgbdev =3D mgbdev; + vindev->config =3D &(vin_cfg[id]); + + /* Frame queue*/ + INIT_LIST_HEAD(&vindev->buf_list); + spin_lock_init(&vindev->qlock); + + /* DMA transfer stuff */ + INIT_WORK(&vindev->dma_work, dma_transfer); + + /* IRQ callback */ + rv =3D request_irq(base_irq + vindev->config->vin_irq, vin_handler, 0, + "mgb4-vin", vindev); + if (rv) { + dev_err(dev, "failed to register vin irq handler\n"); + goto err_alloc; + } + /* Error IRQ callback */ + rv =3D request_irq(base_irq + vindev->config->err_irq, err_handler, 0, + "mgb4-err", vindev); + if (rv) { + dev_err(dev, "failed to register err irq handler\n"); + goto err_vin_irq; + } + + /* V4L2 */ + rv =3D v4l2_device_register(dev, &vindev->v4l2dev); + if (rv) { + dev_err(dev, "failed to register v4l2 device\n"); + goto err_err_irq; + } + + mutex_init(&vindev->lock); + + vindev->queue.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + vindev->queue.io_modes =3D VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + vindev->queue.buf_struct_size =3D sizeof(struct frame_buffer); + vindev->queue.ops =3D &queue_ops; + vindev->queue.mem_ops =3D &vb2_dma_sg_memops; + vindev->queue.gfp_flags =3D GFP_DMA32; + vindev->queue.timestamp_flags =3D V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vindev->queue.min_buffers_needed =3D 2; + vindev->queue.drv_priv =3D vindev; + vindev->queue.lock =3D &vindev->lock; + vindev->queue.dev =3D dev; + rv =3D vb2_queue_init(&vindev->queue); + if (rv) { + dev_err(dev, "failed to initialize vb2 queue\n"); + goto err_v4l2_dev; + } + + snprintf(vindev->vdev.name, sizeof(vindev->vdev.name), "mgb4-in%d", id+1); + vindev->vdev.device_caps =3D V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE + | V4L2_CAP_STREAMING; + vindev->vdev.fops =3D &video_fops; + vindev->vdev.ioctl_ops =3D &video_ioctl_ops; + vindev->vdev.release =3D video_device_release_empty; + vindev->vdev.v4l2_dev =3D &vindev->v4l2dev; + vindev->vdev.lock =3D &vindev->lock; + vindev->vdev.queue =3D &vindev->queue; + video_set_drvdata(&vindev->vdev, vindev); + + rv =3D video_register_device(&vindev->vdev, VFL_TYPE_GRABBER, -1); + if (rv) { + dev_err(dev, "failed to register video device\n"); + goto err_v4l2_dev; + } + + /* Deserializer */ + rv =3D deser_init(vindev, id); + if (rv) + goto err_video_dev; + + /* Set FPGA regs to comply with the deserializer state */ + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, + 1U<<9, 1U<<9); + + /* Module sysfs attributes */ + vindev->alignment =3D 1; + module_attr =3D MGB4_IS_GMSL(mgbdev) + ? mgb4_gmsl_in_attrs : mgb4_fpdl3_in_attrs; + for (attr =3D module_attr; *attr; attr++) + device_create_file(&vindev->vdev.dev, *attr); + + return vindev; + +err_video_dev: + video_unregister_device(&vindev->vdev); +err_v4l2_dev: + v4l2_device_unregister(&vindev->v4l2dev); +err_err_irq: + free_irq(base_irq + vindev->config->err_irq, vindev); +err_vin_irq: + free_irq(base_irq + vindev->config->vin_irq, vindev); +err_alloc: + kfree(vindev); + + return NULL; +} + +void mgb4_vin_free(struct mgb4_vin_dev *vindev) +{ + struct device_attribute **attr, **module_attr; + int base_irq =3D MGB4_IRQ_BASE(vindev->mgbdev->xdma.pdev); + + free_irq(base_irq + vindev->config->err_irq, vindev); + free_irq(base_irq + vindev->config->vin_irq, vindev); + + module_attr =3D MGB4_IS_GMSL(vindev->mgbdev) + ? mgb4_gmsl_in_attrs : mgb4_fpdl3_in_attrs; + for (attr =3D module_attr; *attr; attr++) + device_remove_file(&vindev->vdev.dev, *attr); + + mgb4_i2c_free(&vindev->deser); + video_unregister_device(&vindev->vdev); + v4l2_device_unregister(&vindev->v4l2dev); +} diff --git a/drivers/media/pci/mgb4/mgb4_vin.h b/drivers/media/pci/mgb4/mgb= 4_vin.h new file mode 100644 index 000000000000..4b01b61c3f90 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vin.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_VIN_H__ +#define __MGB4_VIN_H__ + +#include +#include +#include +#include +#include "mgb4_i2c.h" + +struct mgb4_vin_regs { + u32 address; + u32 config; + u32 status; + u32 resolution; + u32 frame_period; + u32 sync; + u32 pclk; + u32 signal; + u32 signal2; + u32 padding; +}; + +struct mgb4_vin_config { + int id; + int dma_channel; + int vin_irq; + int err_irq; + struct mgb4_vin_regs regs; +}; + +struct mgb4_vin_dev { + struct mgb4_dev *mgbdev; + struct v4l2_device v4l2dev; + struct video_device vdev; + struct vb2_queue queue; + struct mutex lock; + + spinlock_t qlock; + struct list_head buf_list; + struct work_struct dma_work; + + unsigned int sequence; + + u32 width; + u32 height; + u32 period; + u32 freq_range; + u32 alignment; + + struct mgb4_i2c_client deser; + + const struct mgb4_vin_config *config; +}; + +extern struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int i= d); +extern void mgb4_vin_free(struct mgb4_vin_dev *vindev); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mg= b4_vout.c new file mode 100644 index 000000000000..eb1fa693f42f --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vout.c @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include +#include +#include +#include +#include "mgb4_core.h" +#include "mgb4_sysfs.h" +#include "mgb4_io.h" +#include "mgb4_vout.h" + +#define DEFAULT_WIDTH 1280 +#define DEFAULT_HEIGHT 640 +#define DEFAULT_PERIOD (125000000 / 60) + +static const struct mgb4_vout_config vout_cfg[] =3D { + {0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7c}}, + {1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8c, 0x90, 0x9c}} +}; + +static const struct i2c_board_info fpdl3_ser_info[] =3D { + {I2C_BOARD_INFO("serializer1", 0x14)}, + {I2C_BOARD_INFO("serializer2", 0x16)}, +}; + +static const struct mgb4_i2c_kv fpdl3_i2c[] =3D { + {0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80} +}; + +static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mgb4_vout_dev *voutdev =3D vb2_get_drv_priv(q); + unsigned long size =3D BYTESPERLINE(voutdev->width, voutdev->alignment) + * voutdev->height; + + if (*nbuffers < 2) + *nbuffers =3D 2; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + *nplanes =3D 1; + sizes[0] =3D size; + + return 0; +} + +static int buffer_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct frame_buffer *buf =3D to_frame_buffer(vbuf); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct mgb4_vout_dev *voutdev =3D vb2_get_drv_priv(vb->vb2_queue); + struct device *dev =3D &voutdev->mgbdev->xdma.pdev->dev; + unsigned long size =3D BYTESPERLINE(voutdev->width, voutdev->alignment) + * voutdev->height; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct mgb4_vout_dev *vindev =3D vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct frame_buffer *buf =3D to_frame_buffer(vbuf); + unsigned long flags; + + spin_lock_irqsave(&vindev->qlock, flags); + list_add_tail(&buf->list, &vindev->buf_list); + spin_unlock_irqrestore(&vindev->qlock, flags); +} + +static void stop_streaming(struct vb2_queue *vq) +{ + struct mgb4_vout_dev *voutdev =3D vb2_get_drv_priv(vq); + struct frame_buffer *buf, *node; + unsigned long flags; + + xdma_irq_disable(&voutdev->mgbdev->xdma, 1U<config->irq); + + cancel_work_sync(&voutdev->dma_work); + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, + 0x2, 0x0); + + spin_lock_irqsave(&voutdev->qlock, flags); + list_for_each_entry_safe(buf, node, &voutdev->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + list_del(&buf->list); + } + spin_unlock_irqrestore(&voutdev->qlock, flags); +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct mgb4_vout_dev *voutdev =3D vb2_get_drv_priv(vq); + struct device *dev =3D &voutdev->mgbdev->xdma.pdev->dev; + struct frame_buffer *buf =3D 0; + struct mgb4_regs *video =3D &voutdev->mgbdev->video; + u32 addr; + + mgb4_mask_reg(video, voutdev->config->regs.config, 0x2, 0x2); + + addr =3D mgb4_read_reg(video, voutdev->config->regs.address); + if (addr >=3D ERR_QUEUE_FULL) { + dev_err(dev, "frame queue error (%d)\n", (int)addr); + return -EIO; + } + + if (!list_empty(&voutdev->buf_list)) { + buf =3D list_first_entry(&voutdev->buf_list, struct frame_buffer, list); + list_del_init(voutdev->buf_list.next); + } + if (!buf) { + dev_err(dev, "empty v4l2 queue\n"); + return -EIO; + } + + if (xdma_transfer(&voutdev->mgbdev->xdma, voutdev->config->dma_channel, + true, addr, vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0), 1000) > 0) { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } else { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + xdma_irq_enable(&voutdev->mgbdev->xdma, 1U<config->irq); + + return 0; +} + +static const struct vb2_ops queue_ops =3D { + .queue_setup =3D queue_setup, + .buf_init =3D buffer_init, + .buf_prepare =3D buffer_prepare, + .buf_queue =3D buffer_queue, + .start_streaming =3D start_streaming, + .stop_streaming =3D stop_streaming, + .wait_prepare =3D vb2_ops_wait_prepare, + .wait_finish =3D vb2_ops_wait_finish +}; + + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mgb4_vout_dev *voutdev =3D video_drvdata(file); + + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "MGB4 PCIe Card", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(voutdev->mgbdev->xdma.pdev)); + + return 0; +} + + +static int vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index !=3D 0) + return -EINVAL; + + f->pixelformat =3D V4L2_PIX_FMT_ABGR32; + + return 0; +} + +static int vidioc_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vout_dev *voutdev =3D video_drvdata(file); + + f->fmt.pix.pixelformat =3D V4L2_PIX_FMT_ABGR32; + f->fmt.pix.width =3D voutdev->width; + f->fmt.pix.height =3D voutdev->height; + f->fmt.pix.field =3D V4L2_FIELD_NONE; + f->fmt.pix.colorspace =3D V4L2_COLORSPACE_RAW; + f->fmt.pix.bytesperline =3D BYTESPERLINE(voutdev->width, voutdev->alignme= nt); + f->fmt.pix.sizeimage =3D f->fmt.pix.width * 4 * f->fmt.pix.height; + + return 0; +} + +static int vidioc_g_output(struct file *file, void *priv, unsigned int *i) +{ + *i =3D 0; + return 0; +} + +static int vidioc_s_output(struct file *file, void *priv, unsigned int i) +{ + return i ? -EINVAL : 0; +} + +static int vidioc_enum_output(struct file *file, void *priv, struct v4l2_o= utput *out) +{ + if (out->index !=3D 0) + return -EINVAL; + + out->type =3D V4L2_OUTPUT_TYPE_ANALOG; + strscpy(out->name, "MGB4", sizeof(out->name)); + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + return -EINVAL; +} + +static const struct v4l2_ioctl_ops video_ioctl_ops =3D { + .vidioc_querycap =3D vidioc_querycap, + .vidioc_enum_fmt_vid_out =3D vidioc_enum_fmt, + .vidioc_try_fmt_vid_out =3D vidioc_fmt, + .vidioc_s_fmt_vid_out =3D vidioc_fmt, + .vidioc_g_fmt_vid_out =3D vidioc_fmt, + .vidioc_enum_output =3D vidioc_enum_output, + .vidioc_g_output =3D vidioc_g_output, + .vidioc_s_output =3D vidioc_s_output, + .vidioc_queryctrl =3D vidioc_queryctrl, + .vidioc_reqbufs =3D vb2_ioctl_reqbufs, + .vidioc_create_bufs =3D vb2_ioctl_create_bufs, + .vidioc_querybuf =3D vb2_ioctl_querybuf, + .vidioc_qbuf =3D vb2_ioctl_qbuf, + .vidioc_dqbuf =3D vb2_ioctl_dqbuf, + .vidioc_expbuf =3D vb2_ioctl_expbuf, + .vidioc_streamon =3D vb2_ioctl_streamon, + .vidioc_streamoff =3D vb2_ioctl_streamoff, +}; + +static int fh_open(struct file *file) +{ + struct mgb4_vout_dev *voutdev =3D video_drvdata(file); + struct mgb4_regs *video =3D &voutdev->mgbdev->video; + u32 config, resolution; + int ret; + + ret =3D v4l2_fh_open(file); + if (ret) + return ret; + + config =3D mgb4_read_reg(video, voutdev->config->regs.config); + if ((config & 0xc) >> 2 !=3D voutdev->config->id + MGB4_VIN_DEVICES) + goto error; + + resolution =3D mgb4_read_reg(video, voutdev->config->regs.resolution); + voutdev->width =3D resolution >> 16; + voutdev->height =3D resolution & 0xFFFF; + + mgb4_write_reg(video, voutdev->config->regs.padding, + PADDING(voutdev->width, voutdev->alignment)); + + return 0; + +error: + v4l2_fh_release(file); + return -EBUSY; +} + +static const struct v4l2_file_operations video_fops =3D { + .owner =3D THIS_MODULE, + .open =3D fh_open, + .release =3D vb2_fop_release, + .unlocked_ioctl =3D video_ioctl2, + .write =3D vb2_fop_write, + .mmap =3D vb2_fop_mmap, + .poll =3D vb2_fop_poll, +}; + +static void dma_transfer(struct work_struct *work) +{ + struct mgb4_vout_dev *voutdev =3D container_of(work, struct mgb4_vout_dev, + dma_work); + struct device *dev =3D &voutdev->mgbdev->xdma.pdev->dev; + struct frame_buffer *buf =3D 0; + unsigned long flags; + u32 addr; + + spin_lock_irqsave(&voutdev->qlock, flags); + if (!list_empty(&voutdev->buf_list)) { + buf =3D list_first_entry(&voutdev->buf_list, struct frame_buffer, list); + list_del_init(voutdev->buf_list.next); + } + spin_unlock_irqrestore(&voutdev->qlock, flags); + + if (!buf) + return; + + addr =3D mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.add= ress); + if (addr >=3D ERR_QUEUE_FULL) { + dev_warn(dev, "frame queue error (%d)\n", (int)addr); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + if (xdma_transfer(&voutdev->mgbdev->xdma, voutdev->config->dma_channel, + true, addr, vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0), 1000) > 0) { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } else { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } +} + +static irqreturn_t handler(int irq, void *ctx) +{ + struct mgb4_vout_dev *voutdev =3D (struct mgb4_vout_dev *)ctx; + + schedule_work(&voutdev->dma_work); + + mgb4_write_reg(&voutdev->mgbdev->video, 0xB4, 1U<config->irq); + + return IRQ_HANDLED; +} + +static int ser_init(struct mgb4_vout_dev *voutdev, int id) +{ + int rv, addr_size; + size_t values_count; + const struct mgb4_i2c_kv *values; + const struct i2c_board_info *info; + struct device *dev =3D &voutdev->mgbdev->xdma.pdev->dev; + + + if (MGB4_IS_GMSL(voutdev->mgbdev)) + return 0; + + info =3D &fpdl3_ser_info[id]; + addr_size =3D 8; + values =3D fpdl3_i2c; + values_count =3D ARRAY_SIZE(fpdl3_i2c); + + rv =3D mgb4_i2c_init(&voutdev->ser, voutdev->mgbdev->i2c_adap, info, addr= _size); + if (rv < 0) { + dev_err(dev, "failed to create serializer\n"); + return rv; + } + rv =3D mgb4_i2c_configure(&voutdev->ser, values, values_count); + if (rv < 0) { + dev_err(dev, "failed to configure serializer\n"); + goto err_i2c_dev; + } + + return 0; + +err_i2c_dev: + mgb4_i2c_free(&voutdev->ser); + + return rv; +} + +struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id) +{ + int rv; + struct device_attribute **attr, **module_attr; + struct mgb4_vout_dev *voutdev; + struct mgb4_regs *video; + struct pci_dev *pdev =3D mgbdev->xdma.pdev; + struct device *dev =3D &pdev->dev; + + voutdev =3D kzalloc(sizeof(struct mgb4_vout_dev), GFP_KERNEL); + if (!voutdev) + return NULL; + + voutdev->mgbdev =3D mgbdev; + voutdev->config =3D &(vout_cfg[id]); + video =3D &voutdev->mgbdev->video; + + /* Frame queue*/ + INIT_LIST_HEAD(&voutdev->buf_list); + spin_lock_init(&voutdev->qlock); + + /* DMA transfer stuff */ + INIT_WORK(&voutdev->dma_work, dma_transfer); + + /* IRQ callback */ + rv =3D request_irq(MGB4_IRQ_BASE(pdev) + voutdev->config->irq, handler, + 0, "mgb4-vout", voutdev); + if (rv) { + dev_err(dev, "failed to register irq handler\n"); + goto err_alloc; + } + + /* V4L2 */ + rv =3D v4l2_device_register(dev, &voutdev->v4l2dev); + if (rv) { + dev_err(dev, "failed to register v4l2 device\n"); + goto err_irq; + } + + mutex_init(&voutdev->lock); + + voutdev->queue.type =3D V4L2_BUF_TYPE_VIDEO_OUTPUT; + voutdev->queue.io_modes =3D VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_WRI= TE; + voutdev->queue.buf_struct_size =3D sizeof(struct frame_buffer); + voutdev->queue.ops =3D &queue_ops; + voutdev->queue.mem_ops =3D &vb2_dma_sg_memops; + voutdev->queue.gfp_flags =3D GFP_DMA32; + voutdev->queue.timestamp_flags =3D V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + voutdev->queue.min_buffers_needed =3D 2; + voutdev->queue.drv_priv =3D voutdev; + voutdev->queue.lock =3D &voutdev->lock; + voutdev->queue.dev =3D dev; + rv =3D vb2_queue_init(&voutdev->queue); + if (rv) { + dev_err(dev, "failed to initialize vb2 queue\n"); + goto err_v4l2_dev; + } + + snprintf(voutdev->vdev.name, sizeof(voutdev->vdev.name), "mgb4-out%d", id= +1); + voutdev->vdev.device_caps =3D V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE + | V4L2_CAP_STREAMING; + voutdev->vdev.vfl_dir =3D VFL_DIR_TX; + voutdev->vdev.fops =3D &video_fops; + voutdev->vdev.ioctl_ops =3D &video_ioctl_ops; + voutdev->vdev.release =3D video_device_release_empty; + voutdev->vdev.v4l2_dev =3D &voutdev->v4l2dev; + voutdev->vdev.lock =3D &voutdev->lock; + voutdev->vdev.queue =3D &voutdev->queue; + video_set_drvdata(&voutdev->vdev, voutdev); + + rv =3D video_register_device(&voutdev->vdev, VFL_TYPE_GRABBER, -1); + if (rv) { + dev_err(dev, "failed to register video device\n"); + goto err_v4l2_dev; + } + + /* Serializer */ + rv =3D ser_init(voutdev, id); + if (rv) + goto err_video_dev; + + /* Set the FPGA registers default values */ + mgb4_mask_reg(video, voutdev->config->regs.config, 0xc, + (voutdev->config->id + MGB4_VIN_DEVICES) << 2); + mgb4_write_reg(video, voutdev->config->regs.resolution, + (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT); + mgb4_write_reg(video, voutdev->config->regs.frame_period, + DEFAULT_PERIOD); + + /* Module sysfs attributes */ + voutdev->alignment =3D 1; + module_attr =3D MGB4_IS_GMSL(mgbdev) + ? mgb4_gmsl_out_attrs : mgb4_fpdl3_out_attrs; + for (attr =3D module_attr; *attr; attr++) + device_create_file(&voutdev->vdev.dev, *attr); + + /* Set the output frequency according to the FPGA defaults */ + voutdev->freq =3D 70000; + + return voutdev; + +err_video_dev: + video_unregister_device(&voutdev->vdev); +err_v4l2_dev: + v4l2_device_unregister(&voutdev->v4l2dev); +err_irq: + free_irq(MGB4_IRQ_BASE(pdev) + voutdev->config->irq, voutdev); +err_alloc: + kfree(voutdev); + + return NULL; +} + +void mgb4_vout_free(struct mgb4_vout_dev *voutdev) +{ + struct device_attribute **attr, **module_attr; + struct pci_dev *pdev =3D voutdev->mgbdev->xdma.pdev; + + free_irq(MGB4_IRQ_BASE(pdev) + voutdev->config->irq, voutdev); + + module_attr =3D MGB4_IS_GMSL(voutdev->mgbdev) + ? mgb4_gmsl_out_attrs : mgb4_fpdl3_out_attrs; + for (attr =3D module_attr; *attr; attr++) + device_remove_file(&voutdev->vdev.dev, *attr); + + mgb4_i2c_free(&voutdev->ser); + video_unregister_device(&voutdev->vdev); + v4l2_device_unregister(&voutdev->v4l2dev); +} diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mg= b4_vout.h new file mode 100644 index 000000000000..902b6b8deb21 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vout.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_VOUT_H__ +#define __MGB4_VOUT_H__ + +#include +#include +#include +#include +#include "mgb4_i2c.h" + +struct mgb4_vout_regs { + u32 address; + u32 config; + u32 status; + u32 resolution; + u32 frame_period; + u32 hsync; + u32 vsync; + u32 padding; +}; + +struct mgb4_vout_config { + int id; + int dma_channel; + int irq; + struct mgb4_vout_regs regs; +}; + +struct mgb4_vout_dev { + struct mgb4_dev *mgbdev; + struct v4l2_device v4l2dev; + struct video_device vdev; + struct vb2_queue queue; + struct mutex lock; + + spinlock_t qlock; + struct list_head buf_list; + struct work_struct dma_work; + + u32 width; + u32 height; + u32 freq; + u32 alignment; + + struct mgb4_i2c_client ser; + + const struct mgb4_vout_config *config; +}; + +extern struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int= id); +extern void mgb4_vout_free(struct mgb4_vout_dev *voutdev); + +#endif --=20 2.37.2