From e986ab55e7576053a6a764ec71e0517245eae758 Mon Sep 17 00:00:00 2001 From: Mikhail Rudenko Date: Thu, 3 Aug 2023 15:37:42 +0300 Subject: MAINTAINERS: fix file path for Omnvision OV4689 Correct the driver source path specified in the MAINTAINERS file, which was mistakenly set due to an oversight during the driver's initial addition. Signed-off-by: Mikhail Rudenko Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index b19995690904..20e37b562c9e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15772,7 +15772,7 @@ L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml -F: drivers/media/i2c/ov5647.c +F: drivers/media/i2c/ov4689.c OMNIVISION OV5640 SENSOR DRIVER M: Steve Longerbeam -- cgit v1.2.3 From 46c15a4ff1f4fe078c5b250fb2570020211eab38 Mon Sep 17 00:00:00 2001 From: Marvin Lin Date: Fri, 22 Sep 2023 14:24:05 +0800 Subject: media: nuvoton: Add driver for NPCM video capture and encoding engine Add driver for Video Capture/Differentiation Engine (VCD) and Encoding Compression Engine (ECE) present on Nuvoton NPCM SoCs. As described in the datasheet NPCM750D_DS_Rev_1.0, the VCD can capture frames from digital video input and compare two frames in memory, and then the ECE can compress the frame data into HEXTILE format. This driver implements V4L2 interfaces and provides user controls to support KVM feature, also tested with VNC Viewer ver.6.22.826 and openbmc/obmc-ikvm. Signed-off-by: Marvin Lin Signed-off-by: Hans Verkuil --- MAINTAINERS | 12 + drivers/media/platform/Kconfig | 1 + drivers/media/platform/Makefile | 1 + drivers/media/platform/nuvoton/Kconfig | 15 + drivers/media/platform/nuvoton/Makefile | 2 + drivers/media/platform/nuvoton/npcm-regs.h | 152 +++ drivers/media/platform/nuvoton/npcm-video.c | 1831 +++++++++++++++++++++++++++ 7 files changed, 2014 insertions(+) create mode 100644 drivers/media/platform/nuvoton/Kconfig create mode 100644 drivers/media/platform/nuvoton/Makefile create mode 100644 drivers/media/platform/nuvoton/npcm-regs.h create mode 100644 drivers/media/platform/nuvoton/npcm-video.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 20e37b562c9e..03dae6ab6a88 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2480,6 +2480,18 @@ F: drivers/rtc/rtc-nct3018y.c F: include/dt-bindings/clock/nuvoton,npcm7xx-clock.h F: include/dt-bindings/clock/nuvoton,npcm845-clk.h +ARM/NUVOTON NPCM VIDEO ENGINE DRIVER +M: Joseph Liu +M: Marvin Lin +L: linux-media@vger.kernel.org +L: openbmc@lists.ozlabs.org (moderated for non-subscribers) +S: Maintained +F: Documentation/devicetree/bindings/media/nuvoton,npcm-ece.yaml +F: Documentation/devicetree/bindings/media/nuvoton,npcm-vcd.yaml +F: Documentation/userspace-api/media/drivers/npcm-video.rst +F: drivers/media/platform/nuvoton/ +F: include/uapi/linux/npcm-video.h + ARM/NUVOTON WPCM450 ARCHITECTURE M: Jonathan Neuschäfer L: openbmc@lists.ozlabs.org (moderated for non-subscribers) diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index ee579916f874..91e54215de3a 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -73,6 +73,7 @@ source "drivers/media/platform/intel/Kconfig" source "drivers/media/platform/marvell/Kconfig" source "drivers/media/platform/mediatek/Kconfig" source "drivers/media/platform/microchip/Kconfig" +source "drivers/media/platform/nuvoton/Kconfig" source "drivers/media/platform/nvidia/Kconfig" source "drivers/media/platform/nxp/Kconfig" source "drivers/media/platform/qcom/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 5453bb868e67..3296ec1ebe16 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -16,6 +16,7 @@ obj-y += intel/ obj-y += marvell/ obj-y += mediatek/ obj-y += microchip/ +obj-y += nuvoton/ obj-y += nvidia/ obj-y += nxp/ obj-y += qcom/ diff --git a/drivers/media/platform/nuvoton/Kconfig b/drivers/media/platform/nuvoton/Kconfig new file mode 100644 index 000000000000..919d3166756c --- /dev/null +++ b/drivers/media/platform/nuvoton/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Nuvoton media platform drivers" + +config VIDEO_NPCM_VCD_ECE + tristate "Nuvoton NPCM Video Capture/Encode Engine driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + help + Support for the Video Capture/Differentiation Engine (VCD) and + Encoding Compression Engine (ECE) present on Nuvoton NPCM SoCs. + The VCD can capture a frame from digital video input and compare + two frames in memory, and then the ECE can compress the frame + data into HEXTILE format. diff --git a/drivers/media/platform/nuvoton/Makefile b/drivers/media/platform/nuvoton/Makefile new file mode 100644 index 000000000000..74a4e3fc8555 --- /dev/null +++ b/drivers/media/platform/nuvoton/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_NPCM_VCD_ECE) += npcm-video.o diff --git a/drivers/media/platform/nuvoton/npcm-regs.h b/drivers/media/platform/nuvoton/npcm-regs.h new file mode 100644 index 000000000000..4a44f47f026e --- /dev/null +++ b/drivers/media/platform/nuvoton/npcm-regs.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Register definition header for NPCM video driver + * + * Copyright (C) 2022 Nuvoton Technologies + */ + +#ifndef _NPCM_REGS_H +#define _NPCM_REGS_H + +/* VCD Registers */ +#define VCD_DIFF_TBL 0x0000 +#define VCD_FBA_ADR 0x8000 +#define VCD_FBB_ADR 0x8004 + +#define VCD_FB_LP 0x8008 +#define VCD_FBA_LP GENMASK(15, 0) +#define VCD_FBB_LP GENMASK(31, 16) + +#define VCD_CAP_RES 0x800c +#define VCD_CAP_RES_VERT_RES GENMASK(10, 0) +#define VCD_CAP_RES_HOR_RES GENMASK(26, 16) + +#define VCD_MODE 0x8014 +#define VCD_MODE_VCDE BIT(0) +#define VCD_MODE_CM565 BIT(1) +#define VCD_MODE_IDBC BIT(3) +#define VCD_MODE_KVM_BW_SET BIT(16) + +#define VCD_CMD 0x8018 +#define VCD_CMD_GO BIT(0) +#define VCD_CMD_RST BIT(1) +#define VCD_CMD_OPERATION GENMASK(6, 4) +#define VCD_CMD_OPERATION_CAPTURE 0 +#define VCD_CMD_OPERATION_COMPARE 2 + +#define VCD_STAT 0x801c +#define VCD_STAT_DONE BIT(0) +#define VCD_STAT_IFOT BIT(2) +#define VCD_STAT_IFOR BIT(3) +#define VCD_STAT_VHT_CHG BIT(5) +#define VCD_STAT_HAC_CHG BIT(8) +#define VCD_STAT_BUSY BIT(30) +#define VCD_STAT_CLEAR 0x3fff + +#define VCD_INTE 0x8020 +#define VCD_INTE_DONE_IE BIT(0) +#define VCD_INTE_IFOT_IE BIT(2) +#define VCD_INTE_IFOR_IE BIT(3) +#define VCD_INTE_VHT_IE BIT(5) +#define VCD_INTE_HAC_IE BIT(8) + +#define VCD_RCHG 0x8028 +#define VCD_RCHG_IG_CHG0 GENMASK(2, 0) +#define VCD_RCHG_TIM_PRSCL GENMASK(12, 9) + +#define VCD_VER_HI_TIM 0x8044 +#define VCD_VER_HI_TIME GENMASK(23, 0) + +#define VCD_VER_HI_LST 0x8048 +#define VCD_VER_HI_LAST GENMASK(23, 0) + +#define VCD_HOR_AC_TIM 0x804c +#define VCD_HOR_AC_TIME GENMASK(13, 0) + +#define VCD_HOR_AC_LST 0x8050 +#define VCD_HOR_AC_LAST GENMASK(13, 0) + +#define VCD_FIFO 0x805c +#define VCD_FIFO_TH 0x100350ff + +#define VCD_FB_SIZE 0x500000 /* support up to 1920 x 1200 */ +#define VCD_KVM_BW_PCLK 120000000UL +#define VCD_TIMEOUT_US 300000 + +/* ECE Registers */ +#define ECE_DDA_CTRL 0x0000 +#define ECE_DDA_CTRL_ECEEN BIT(0) +#define ECE_DDA_CTRL_INTEN BIT(8) + +#define ECE_DDA_STS 0x0004 +#define ECE_DDA_STS_CDREADY BIT(8) +#define ECE_DDA_STS_ACDRDY BIT(10) + +#define ECE_FBR_BA 0x0008 +#define ECE_ED_BA 0x000c +#define ECE_RECT_XY 0x0010 + +#define ECE_RECT_DIMEN 0x0014 +#define ECE_RECT_DIMEN_WR GENMASK(10, 0) +#define ECE_RECT_DIMEN_WLTR GENMASK(14, 11) +#define ECE_RECT_DIMEN_HR GENMASK(26, 16) +#define ECE_RECT_DIMEN_HLTR GENMASK(30, 27) + +#define ECE_RESOL 0x001c +#define ECE_RESOL_FB_LP_512 0 +#define ECE_RESOL_FB_LP_1024 1 +#define ECE_RESOL_FB_LP_2048 2 +#define ECE_RESOL_FB_LP_2560 3 +#define ECE_RESOL_FB_LP_4096 4 + +#define ECE_HEX_CTRL 0x0040 +#define ECE_HEX_CTRL_ENCDIS BIT(0) +#define ECE_HEX_CTRL_ENC_GAP GENMASK(12, 8) + +#define ECE_HEX_RECT_OFFSET 0x0048 +#define ECE_HEX_RECT_OFFSET_MASK GENMASK(22, 0) + +#define ECE_TILE_W 16 +#define ECE_TILE_H 16 +#define ECE_POLL_TIMEOUT_US 300000 + +/* GCR Registers */ +#define INTCR 0x3c +#define INTCR_GFXIFDIS GENMASK(9, 8) +#define INTCR_DEHS BIT(27) + +#define INTCR2 0x60 +#define INTCR2_GIRST2 BIT(2) +#define INTCR2_GIHCRST BIT(5) +#define INTCR2_GIVCRST BIT(6) + +/* GFXI Register */ +#define DISPST 0x00 +#define DISPST_HSCROFF BIT(1) +#define DISPST_MGAMODE BIT(7) + +#define HVCNTL 0x10 +#define HVCNTL_MASK GENMASK(7, 0) + +#define HVCNTH 0x14 +#define HVCNTH_MASK GENMASK(2, 0) + +#define VVCNTL 0x20 +#define VVCNTL_MASK GENMASK(7, 0) + +#define VVCNTH 0x24 +#define VVCNTH_MASK GENMASK(2, 0) + +#define GPLLINDIV 0x40 +#define GPLLINDIV_MASK GENMASK(5, 0) +#define GPLLINDIV_GPLLFBDV8 BIT(7) + +#define GPLLFBDIV 0x44 +#define GPLLFBDIV_MASK GENMASK(7, 0) + +#define GPLLST 0x48 +#define GPLLST_PLLOTDIV1 GENMASK(2, 0) +#define GPLLST_PLLOTDIV2 GENMASK(5, 3) +#define GPLLST_GPLLFBDV109 GENMASK(7, 6) + +#endif /* _NPCM_REGS_H */ diff --git a/drivers/media/platform/nuvoton/npcm-video.c b/drivers/media/platform/nuvoton/npcm-video.c new file mode 100644 index 000000000000..ac8d73b794d3 --- /dev/null +++ b/drivers/media/platform/nuvoton/npcm-video.c @@ -0,0 +1,1831 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Video Capture/Differentiation Engine (VCD) and Encoding + * Compression Engine (ECE) present on Nuvoton NPCM SoCs. + * + * Copyright (C) 2022 Nuvoton Technologies + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "npcm-regs.h" + +#define DEVICE_NAME "npcm-video" +#define MAX_WIDTH 1920 +#define MAX_HEIGHT 1200 +#define MIN_WIDTH 320 +#define MIN_HEIGHT 240 +#define MIN_LP 512 +#define MAX_LP 4096 +#define RECT_W 16 +#define RECT_H 16 +#define BITMAP_SIZE 32 + +struct npcm_video_addr { + size_t size; + dma_addr_t dma; + void *virt; +}; + +struct npcm_video_buffer { + struct vb2_v4l2_buffer vb; + struct list_head link; +}; + +#define to_npcm_video_buffer(x) \ + container_of((x), struct npcm_video_buffer, vb) + +/* + * VIDEO_STREAMING: a flag indicating if the video has started streaming + * VIDEO_CAPTURING: a flag indicating if the VCD is capturing a frame + * VIDEO_RES_CHANGING: a flag indicating if the resolution is changing + * VIDEO_STOPPED: a flag indicating if the video has stopped streaming + */ +enum { + VIDEO_STREAMING, + VIDEO_CAPTURING, + VIDEO_RES_CHANGING, + VIDEO_STOPPED, +}; + +struct rect_list { + struct v4l2_clip clip; + struct list_head list; +}; + +struct rect_list_info { + struct rect_list *list; + struct rect_list *first; + struct list_head *head; + unsigned int index; + unsigned int tile_perline; + unsigned int tile_perrow; + unsigned int offset_perline; + unsigned int tile_size; + unsigned int tile_cnt; +}; + +struct npcm_ece { + struct regmap *regmap; + atomic_t clients; + struct reset_control *reset; + bool enable; +}; + +struct npcm_video { + struct regmap *gcr_regmap; + struct regmap *gfx_regmap; + struct regmap *vcd_regmap; + + struct device *dev; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *rect_cnt_ctrl; + struct v4l2_device v4l2_dev; + struct v4l2_pix_format pix_fmt; + struct v4l2_bt_timings active_timings; + struct v4l2_bt_timings detected_timings; + unsigned int v4l2_input_status; + struct vb2_queue queue; + struct video_device vdev; + struct mutex video_lock; /* v4l2 and videobuf2 lock */ + + struct list_head buffers; + spinlock_t lock; /* buffer list lock */ + unsigned long flags; + unsigned int sequence; + + struct npcm_video_addr src; + struct reset_control *reset; + struct npcm_ece ece; + + unsigned int bytesperline; + unsigned int bytesperpixel; + unsigned int rect_cnt; + struct list_head list[VIDEO_MAX_FRAME]; + unsigned int rect[VIDEO_MAX_FRAME]; + unsigned int ctrl_cmd; + unsigned int op_cmd; +}; + +#define to_npcm_video(x) container_of((x), struct npcm_video, v4l2_dev) + +struct npcm_fmt { + unsigned int fourcc; + unsigned int bpp; /* bytes per pixel */ +}; + +static const struct npcm_fmt npcm_fmt_list[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_HEXTILE, + .bpp = 2, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(npcm_fmt_list) + +static const struct v4l2_dv_timings_cap npcm_video_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .min_width = MIN_WIDTH, + .max_width = MAX_WIDTH, + .min_height = MIN_HEIGHT, + .max_height = MAX_HEIGHT, + .min_pixelclock = 6574080, /* 640 x 480 x 24Hz */ + .max_pixelclock = 138240000, /* 1920 x 1200 x 60Hz */ + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +static DECLARE_BITMAP(bitmap, BITMAP_SIZE); + +static const struct npcm_fmt *npcm_video_find_format(struct v4l2_format *f) +{ + const struct npcm_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &npcm_fmt_list[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &npcm_fmt_list[k]; +} + +static void npcm_video_ece_prepend_rect_header(void *addr, u16 x, u16 y, u16 w, u16 h) +{ + __be16 x_pos = cpu_to_be16(x); + __be16 y_pos = cpu_to_be16(y); + __be16 width = cpu_to_be16(w); + __be16 height = cpu_to_be16(h); + __be32 encoding = cpu_to_be32(5); /* Hextile encoding */ + + memcpy(addr, &x_pos, 2); + memcpy(addr + 2, &y_pos, 2); + memcpy(addr + 4, &width, 2); + memcpy(addr + 6, &height, 2); + memcpy(addr + 8, &encoding, 4); +} + +static unsigned int npcm_video_ece_get_ed_size(struct npcm_video *video, + unsigned int offset, void *addr) +{ + struct regmap *ece = video->ece.regmap; + unsigned int size, gap, val; + int ret; + + ret = regmap_read_poll_timeout(ece, ECE_DDA_STS, val, + (val & ECE_DDA_STS_CDREADY), 0, + ECE_POLL_TIMEOUT_US); + + if (ret) { + dev_warn(video->dev, "Wait for ECE_DDA_STS_CDREADY timeout\n"); + return 0; + } + + size = readl((void __iomem *)addr + offset); + regmap_read(ece, ECE_HEX_CTRL, &val); + gap = FIELD_GET(ECE_HEX_CTRL_ENC_GAP, val); + + dev_dbg(video->dev, "offset = %u, ed_size = %u, gap = %u\n", offset, + size, gap); + + return size + gap; +} + +static void npcm_video_ece_enc_rect(struct npcm_video *video, + unsigned int r_off_x, unsigned int r_off_y, + unsigned int r_w, unsigned int r_h) +{ + struct regmap *ece = video->ece.regmap; + unsigned int rect_offset = (r_off_y * video->bytesperline) + (r_off_x * 2); + unsigned int w_size = ECE_TILE_W, h_size = ECE_TILE_H; + unsigned int temp, w_tile, h_tile; + + regmap_update_bits(ece, ECE_DDA_CTRL, ECE_DDA_CTRL_ECEEN, 0); + regmap_update_bits(ece, ECE_DDA_CTRL, ECE_DDA_CTRL_ECEEN, ECE_DDA_CTRL_ECEEN); + regmap_write(ece, ECE_DDA_STS, ECE_DDA_STS_CDREADY | ECE_DDA_STS_ACDRDY); + regmap_write(ece, ECE_RECT_XY, rect_offset); + + w_tile = r_w / ECE_TILE_W; + h_tile = r_h / ECE_TILE_H; + + if (r_w % ECE_TILE_W) { + w_tile += 1; + w_size = r_w % ECE_TILE_W; + } + if (r_h % ECE_TILE_H || !h_tile) { + h_tile += 1; + h_size = r_h % ECE_TILE_H; + } + + temp = FIELD_PREP(ECE_RECT_DIMEN_WLTR, w_size - 1) | + FIELD_PREP(ECE_RECT_DIMEN_HLTR, h_size - 1) | + FIELD_PREP(ECE_RECT_DIMEN_WR, w_tile - 1) | + FIELD_PREP(ECE_RECT_DIMEN_HR, h_tile - 1); + + regmap_write(ece, ECE_RECT_DIMEN, temp); +} + +static unsigned int npcm_video_ece_read_rect_offset(struct npcm_video *video) +{ + struct regmap *ece = video->ece.regmap; + unsigned int offset; + + regmap_read(ece, ECE_HEX_RECT_OFFSET, &offset); + return FIELD_GET(ECE_HEX_RECT_OFFSET_MASK, offset); +} + +/* + * Set the line pitch (in bytes) for the frame buffers. + * Can be on of those values: 512, 1024, 2048, 2560 or 4096 bytes. + */ +static void npcm_video_ece_set_lp(struct npcm_video *video, unsigned int pitch) +{ + struct regmap *ece = video->ece.regmap; + unsigned int lp; + + switch (pitch) { + case 512: + lp = ECE_RESOL_FB_LP_512; + break; + case 1024: + lp = ECE_RESOL_FB_LP_1024; + break; + case 2048: + lp = ECE_RESOL_FB_LP_2048; + break; + case 2560: + lp = ECE_RESOL_FB_LP_2560; + break; + case 4096: + lp = ECE_RESOL_FB_LP_4096; + break; + default: + return; + } + + regmap_write(ece, ECE_RESOL, lp); +} + +static inline void npcm_video_ece_set_fb_addr(struct npcm_video *video, + unsigned int buffer) +{ + struct regmap *ece = video->ece.regmap; + + regmap_write(ece, ECE_FBR_BA, buffer); +} + +static inline void npcm_video_ece_set_enc_dba(struct npcm_video *video, + unsigned int addr) +{ + struct regmap *ece = video->ece.regmap; + + regmap_write(ece, ECE_ED_BA, addr); +} + +static inline void npcm_video_ece_clear_rect_offset(struct npcm_video *video) +{ + struct regmap *ece = video->ece.regmap; + + regmap_write(ece, ECE_HEX_RECT_OFFSET, 0); +} + +static void npcm_video_ece_ctrl_reset(struct npcm_video *video) +{ + struct regmap *ece = video->ece.regmap; + + regmap_update_bits(ece, ECE_DDA_CTRL, ECE_DDA_CTRL_ECEEN, 0); + regmap_update_bits(ece, ECE_HEX_CTRL, ECE_HEX_CTRL_ENCDIS, ECE_HEX_CTRL_ENCDIS); + regmap_update_bits(ece, ECE_DDA_CTRL, ECE_DDA_CTRL_ECEEN, ECE_DDA_CTRL_ECEEN); + regmap_update_bits(ece, ECE_HEX_CTRL, ECE_HEX_CTRL_ENCDIS, 0); + + npcm_video_ece_clear_rect_offset(video); +} + +static void npcm_video_ece_ip_reset(struct npcm_video *video) +{ + /* + * After resetting a module and clearing the reset bit, it should wait + * at least 10 us before accessing the module. + */ + reset_control_assert(video->ece.reset); + usleep_range(10, 20); + reset_control_deassert(video->ece.reset); + usleep_range(10, 20); +} + +static void npcm_video_ece_stop(struct npcm_video *video) +{ + struct regmap *ece = video->ece.regmap; + + regmap_update_bits(ece, ECE_DDA_CTRL, ECE_DDA_CTRL_ECEEN, 0); + regmap_update_bits(ece, ECE_DDA_CTRL, ECE_DDA_CTRL_INTEN, 0); + regmap_update_bits(ece, ECE_HEX_CTRL, ECE_HEX_CTRL_ENCDIS, ECE_HEX_CTRL_ENCDIS); + npcm_video_ece_clear_rect_offset(video); +} + +static bool npcm_video_alloc_fb(struct npcm_video *video, + struct npcm_video_addr *addr) +{ + addr->virt = dma_alloc_coherent(video->dev, VCD_FB_SIZE, &addr->dma, + GFP_KERNEL); + if (!addr->virt) + return false; + + addr->size = VCD_FB_SIZE; + return true; +} + +static void npcm_video_free_fb(struct npcm_video *video, + struct npcm_video_addr *addr) +{ + dma_free_coherent(video->dev, addr->size, addr->virt, addr->dma); + addr->size = 0; + addr->dma = 0ULL; + addr->virt = NULL; +} + +static void npcm_video_free_diff_table(struct npcm_video *video) +{ + struct list_head *head, *pos, *nx; + struct rect_list *tmp; + unsigned int i; + + for (i = 0; i < video->queue.num_buffers; i++) { + head = &video->list[i]; + list_for_each_safe(pos, nx, head) { + tmp = list_entry(pos, struct rect_list, list); + list_del(&tmp->list); + kfree(tmp); + } + } +} + +static unsigned int npcm_video_add_rect(struct npcm_video *video, + unsigned int index, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h) +{ + struct list_head *head = &video->list[index]; + struct rect_list *list = NULL; + struct v4l2_rect *r; + + list = kzalloc(sizeof(*list), GFP_KERNEL); + if (!list) + return 0; + + r = &list->clip.c; + r->left = x; + r->top = y; + r->width = w; + r->height = h; + + list_add_tail(&list->list, head); + return 1; +} + +static void npcm_video_merge_rect(struct npcm_video *video, + struct rect_list_info *info) +{ + struct list_head *head = info->head; + struct rect_list *list = info->list, *first = info->first; + struct v4l2_rect *r = &list->clip.c, *f = &first->clip.c; + + if (!first) { + first = list; + info->first = first; + list_add_tail(&list->list, head); + video->rect_cnt++; + } else { + if ((r->left == (f->left + f->width)) && r->top == f->top) { + f->width += r->width; + kfree(list); + } else if ((r->top == (f->top + f->height)) && + (r->left == f->left)) { + f->height += r->height; + kfree(list); + } else if (((r->top > f->top) && + (r->top < (f->top + f->height))) && + ((r->left > f->left) && + (r->left < (f->left + f->width)))) { + kfree(list); + } else { + list_add_tail(&list->list, head); + video->rect_cnt++; + info->first = list; + } + } +} + +static struct rect_list *npcm_video_new_rect(struct npcm_video *video, + unsigned int offset, + unsigned int index) +{ + struct v4l2_bt_timings *act = &video->active_timings; + struct rect_list *list = NULL; + struct v4l2_rect *r; + + list = kzalloc(sizeof(*list), GFP_KERNEL); + if (!list) + return NULL; + + r = &list->clip.c; + + r->left = (offset << 4); + r->top = (index >> 2); + r->width = RECT_W; + r->height = RECT_H; + if ((r->left + RECT_W) > act->width) + r->width = act->width - r->left; + if ((r->top + RECT_H) > act->height) + r->height = act->height - r->top; + + return list; +} + +static int npcm_video_find_rect(struct npcm_video *video, + struct rect_list_info *info, + unsigned int offset) +{ + if (offset < info->tile_perline) { + info->list = npcm_video_new_rect(video, offset, info->index); + if (!info->list) { + dev_err(video->dev, "Failed to allocate rect_list\n"); + return -ENOMEM; + } + + npcm_video_merge_rect(video, info); + } + return 0; +} + +static int npcm_video_build_table(struct npcm_video *video, + struct rect_list_info *info) +{ + struct regmap *vcd = video->vcd_regmap; + unsigned int j, bit, value; + int ret; + + for (j = 0; j < info->offset_perline; j += 4) { + regmap_read(vcd, VCD_DIFF_TBL + (j + info->index), &value); + + bitmap_from_arr32(bitmap, &value, BITMAP_SIZE); + + for_each_set_bit(bit, bitmap, BITMAP_SIZE) { + ret = npcm_video_find_rect(video, info, bit + (j << 3)); + if (ret) + return ret; + } + } + info->index += 64; + return info->tile_perline; +} + +static void npcm_video_get_rect_list(struct npcm_video *video, unsigned int index) +{ + struct v4l2_bt_timings *act = &video->active_timings; + struct rect_list_info info; + unsigned int tile_cnt = 0, mod; + int ret = 0; + + memset(&info, 0, sizeof(struct rect_list_info)); + info.head = &video->list[index]; + + info.tile_perline = act->width >> 4; + mod = act->width % RECT_W; + if (mod != 0) + info.tile_perline += 1; + + info.tile_perrow = act->height >> 4; + mod = act->height % RECT_H; + if (mod != 0) + info.tile_perrow += 1; + + info.tile_size = info.tile_perrow * info.tile_perline; + + info.offset_perline = info.tile_perline >> 5; + mod = info.tile_perline % 32; + if (mod != 0) + info.offset_perline += 1; + + info.offset_perline *= 4; + + do { + ret = npcm_video_build_table(video, &info); + if (ret < 0) + return; + + tile_cnt += ret; + } while (tile_cnt < info.tile_size); +} + +static unsigned int npcm_video_is_mga(struct npcm_video *video) +{ + struct regmap *gfxi = video->gfx_regmap; + unsigned int dispst; + + regmap_read(gfxi, DISPST, &dispst); + return ((dispst & DISPST_MGAMODE) == DISPST_MGAMODE); +} + +static unsigned int npcm_video_hres(struct npcm_video *video) +{ + struct regmap *gfxi = video->gfx_regmap; + unsigned int hvcnth, hvcntl, apb_hor_res; + + regmap_read(gfxi, HVCNTH, &hvcnth); + regmap_read(gfxi, HVCNTL, &hvcntl); + apb_hor_res = (((hvcnth & HVCNTH_MASK) << 8) + (hvcntl & HVCNTL_MASK) + 1); + + return apb_hor_res; +} + +static unsigned int npcm_video_vres(struct npcm_video *video) +{ + struct regmap *gfxi = video->gfx_regmap; + unsigned int vvcnth, vvcntl, apb_ver_res; + + regmap_read(gfxi, VVCNTH, &vvcnth); + regmap_read(gfxi, VVCNTL, &vvcntl); + + apb_ver_res = (((vvcnth & VVCNTH_MASK) << 8) + (vvcntl & VVCNTL_MASK)); + + return apb_ver_res; +} + +static int npcm_video_capres(struct npcm_video *video, unsigned int hor_res, + unsigned int vert_res) +{ + struct regmap *vcd = video->vcd_regmap; + unsigned int res, cap_res; + + if (hor_res > MAX_WIDTH || vert_res > MAX_HEIGHT) + return -EINVAL; + + res = FIELD_PREP(VCD_CAP_RES_VERT_RES, vert_res) | + FIELD_PREP(VCD_CAP_RES_HOR_RES, hor_res); + + regmap_write(vcd, VCD_CAP_RES, res); + regmap_read(vcd, VCD_CAP_RES, &cap_res); + + if (cap_res != res) + return -EINVAL; + + return 0; +} + +static void npcm_video_vcd_ip_reset(struct npcm_video *video) +{ + /* + * After resetting a module and clearing the reset bit, it should wait + * at least 10 us before accessing the module. + */ + reset_control_assert(video->reset); + usleep_range(10, 20); + reset_control_deassert(video->reset); + usleep_range(10, 20); +} + +static void npcm_video_vcd_state_machine_reset(struct npcm_video *video) +{ + struct regmap *vcd = video->vcd_regmap; + + regmap_update_bits(vcd, VCD_MODE, VCD_MODE_VCDE, 0); + regmap_update_bits(vcd, VCD_MODE, VCD_MODE_IDBC, 0); + regmap_update_bits(vcd, VCD_CMD, VCD_CMD_RST, VCD_CMD_RST); + + /* + * VCD_CMD_RST will reset VCD internal state machines and clear FIFOs, + * it should wait at least 800 us for the reset operations completed. + */ + usleep_range(800, 1000); + + regmap_write(vcd, VCD_STAT, VCD_STAT_CLEAR); + regmap_update_bits(vcd, VCD_MODE, VCD_MODE_VCDE, VCD_MODE_VCDE); + regmap_update_bits(vcd, VCD_MODE, VCD_MODE_IDBC, VCD_MODE_IDBC); +} + +static void npcm_video_gfx_reset(struct npcm_video *video) +{ + struct regmap *gcr = video->gcr_regmap; + + regmap_update_bits(gcr, INTCR2, INTCR2_GIRST2, INTCR2_GIRST2); + npcm_video_vcd_state_machine_reset(video); + regmap_update_bits(gcr, INTCR2, INTCR2_GIRST2, 0); +} + +static void npcm_video_kvm_bw(struct npcm_video *video, bool set_bw) +{ + struct regmap *vcd = video->vcd_regmap; + + if (set_bw || !npcm_video_is_mga(video)) + regmap_update_bits(vcd, VCD_MODE, VCD_MODE_KVM_BW_SET, + VCD_MODE_KVM_BW_SET); + else + regmap_update_bits(vcd, VCD_MODE, VCD_MODE_KVM_BW_SET, 0); +} + +static unsigned int npcm_video_pclk(struct npcm_video *video) +{ + struct regmap *gfxi = video->gfx_regmap; + unsigned int tmp, pllfbdiv, pllinotdiv, gpllfbdiv; + unsigned int gpllfbdv109, gpllfbdv8, gpllindiv; + unsigned int gpllst_pllotdiv1, gpllst_pllotdiv2; + + regmap_read(gfxi, GPLLST, &tmp); + gpllfbdv109 = FIELD_GET(GPLLST_GPLLFBDV109, tmp); + gpllst_pllotdiv1 = FIELD_GET(GPLLST_PLLOTDIV1, tmp); + gpllst_pllotdiv2 = FIELD_GET(GPLLST_PLLOTDIV2, tmp); + + regmap_read(gfxi, GPLLINDIV, &tmp); + gpllfbdv8 = FIELD_GET(GPLLINDIV_GPLLFBDV8, tmp); + gpllindiv = FIELD_GET(GPLLINDIV_MASK, tmp); + + regmap_read(gfxi, GPLLFBDIV, &tmp); + gpllfbdiv = FIELD_GET(GPLLFBDIV_MASK, tmp); + + pllfbdiv = (512 * gpllfbdv109 + 256 * gpllfbdv8 + gpllfbdiv); + pllinotdiv = (gpllindiv * gpllst_pllotdiv1 * gpllst_pllotdiv2); + if (pllfbdiv == 0 || pllinotdiv == 0) + return 0; + + return ((pllfbdiv * 25000) / pllinotdiv) * 1000; +} + +static unsigned int npcm_video_get_bpp(struct npcm_video *video) +{ + const struct npcm_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &npcm_fmt_list[k]; + if (fmt->fourcc == video->pix_fmt.pixelformat) + break; + } + + return fmt->bpp; +} + +/* + * Pitch must be a power of 2, >= linebytes, + * at least 512, and no more than 4096. + */ +static void npcm_video_set_linepitch(struct npcm_video *video, + unsigned int linebytes) +{ + struct regmap *vcd = video->vcd_regmap; + unsigned int pitch = MIN_LP; + + while ((pitch < linebytes) && (pitch < MAX_LP)) + pitch *= 2; + + regmap_write(vcd, VCD_FB_LP, FIELD_PREP(VCD_FBA_LP, pitch) | + FIELD_PREP(VCD_FBB_LP, pitch)); +} + +static unsigned int npcm_video_get_linepitch(struct npcm_video *video) +{ + struct regmap *vcd = video->vcd_regmap; + unsigned int linepitch; + + regmap_read(vcd, VCD_FB_LP, &linepitch); + return FIELD_GET(VCD_FBA_LP, linepitch); +} + +static void npcm_video_command(struct npcm_video *video, unsigned int value) +{ + struct regmap *vcd = video->vcd_regmap; + unsigned int cmd; + + regmap_write(vcd, VCD_STAT, VCD_STAT_CLEAR); + regmap_read(vcd, VCD_CMD, &cmd); + cmd |= FIELD_PREP(VCD_CMD_OPERATION, value); + + regmap_write(vcd, VCD_CMD, cmd); + regmap_update_bits(vcd, VCD_CMD, VCD_CMD_GO, VCD_CMD_GO); + video->op_cmd = value; +} + +static void npcm_video_init_reg(struct npcm_video *video) +{ + struct regmap *gcr = video->gcr_regmap, *vcd = video->vcd_regmap; + + /* Selects Data Enable */ + regmap_update_bits(gcr, INTCR, INTCR_DEHS, 0); + + /* Enable display of KVM GFX and access to memory */ + regmap_update_bits(gcr, INTCR, INTCR_GFXIFDIS, 0); + + /* Active Vertical/Horizontal Counters Reset */ + regmap_update_bits(gcr, INTCR2, INTCR2_GIHCRST | INTCR2_GIVCRST, + INTCR2_GIHCRST | INTCR2_GIVCRST); + + /* Reset video modules */ + npcm_video_vcd_ip_reset(video); + npcm_video_gfx_reset(video); + + /* Set the FIFO thresholds */ + regmap_write(vcd, VCD_FIFO, VCD_FIFO_TH); + + /* Set RCHG timer */ + regmap_write(vcd, VCD_RCHG, FIELD_PREP(VCD_RCHG_TIM_PRSCL, 0xf) | + FIELD_PREP(VCD_RCHG_IG_CHG0, 0x3)); + + /* Set video mode */ + regmap_write(vcd, VCD_MODE, VCD_MODE_VCDE | VCD_MODE_CM565 | + VCD_MODE_IDBC | VCD_MODE_KVM_BW_SET); +} + +static int npcm_video_start_frame(struct npcm_video *video) +{ + struct npcm_video_buffer *buf; + struct regmap *vcd = video->vcd_regmap; + unsigned long flags; + unsigned int val; + int ret; + + if (video->v4l2_input_status) { + dev_dbg(video->dev, "No video signal; skip capture frame\n"); + return 0; + } + + ret = regmap_read_poll_timeout(vcd, VCD_STAT, val, !(val & VCD_STAT_BUSY), + 1000, VCD_TIMEOUT_US); + if (ret) { + dev_err(video->dev, "Wait for VCD_STAT_BUSY timeout\n"); + return -EBUSY; + } + + spin_lock_irqsave(&video->lock, flags); + buf = list_first_entry_or_null(&video->buffers, + struct npcm_video_buffer, link); + if (!buf) { + spin_unlock_irqrestore(&video->lock, flags); + dev_dbg(video->dev, "No empty buffers; skip capture frame\n"); + return 0; + } + + set_bit(VIDEO_CAPTURING, &video->flags); + spin_unlock_irqrestore(&video->lock, flags); + + npcm_video_vcd_state_machine_reset(video); + + regmap_read(vcd, VCD_HOR_AC_TIM, &val); + regmap_update_bits(vcd, VCD_HOR_AC_LST, VCD_HOR_AC_LAST, + FIELD_GET(VCD_HOR_AC_TIME, val)); + + regmap_read(vcd, VCD_VER_HI_TIM, &val); + regmap_update_bits(vcd, VCD_VER_HI_LST, VCD_VER_HI_LAST, + FIELD_GET(VCD_VER_HI_TIME, val)); + + regmap_update_bits(vcd, VCD_INTE, VCD_INTE_DONE_IE | VCD_INTE_IFOT_IE | + VCD_INTE_IFOR_IE | VCD_INTE_HAC_IE | VCD_INTE_VHT_IE, + VCD_INTE_DONE_IE | VCD_INTE_IFOT_IE | VCD_INTE_IFOR_IE | + VCD_INTE_HAC_IE | VCD_INTE_VHT_IE); + + npcm_video_command(video, video->ctrl_cmd); + + return 0; +} + +static void npcm_video_bufs_done(struct npcm_video *video, + enum vb2_buffer_state state) +{ + struct npcm_video_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&video->lock, flags); + list_for_each_entry(buf, &video->buffers, link) + vb2_buffer_done(&buf->vb.vb2_buf, state); + + INIT_LIST_HEAD(&video->buffers); + spin_unlock_irqrestore(&video->lock, flags); +} + +static void npcm_video_get_diff_rect(struct npcm_video *video, unsigned int index) +{ + unsigned int width = video->active_timings.width; + unsigned int height = video->active_timings.height; + + if (video->op_cmd != VCD_CMD_OPERATION_CAPTURE) { + video->rect_cnt = 0; + npcm_video_get_rect_list(video, index); + video->rect[index] = video->rect_cnt; + } else { + video->rect[index] = npcm_video_add_rect(video, index, 0, 0, + width, height); + } +} + +static void npcm_video_detect_resolution(struct npcm_video *video) +{ + struct v4l2_bt_timings *act = &video->active_timings; + struct v4l2_bt_timings *det = &video->detected_timings; + struct regmap *gfxi = video->gfx_regmap; + unsigned int dispst; + + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + det->width = npcm_video_hres(video); + det->height = npcm_video_vres(video); + + if (act->width != det->width || act->height != det->height) { + dev_dbg(video->dev, "Resolution changed\n"); + + if (npcm_video_hres(video) > 0 && npcm_video_vres(video) > 0) { + if (test_bit(VIDEO_STREAMING, &video->flags)) { + /* + * Wait for resolution is available, + * and it is also captured by host. + */ + do { + mdelay(100); + regmap_read(gfxi, DISPST, &dispst); + } while (npcm_video_vres(video) < 100 || + npcm_video_pclk(video) == 0 || + (dispst & DISPST_HSCROFF)); + } + + det->width = npcm_video_hres(video); + det->height = npcm_video_vres(video); + det->pixelclock = npcm_video_pclk(video); + } + + clear_bit(VIDEO_RES_CHANGING, &video->flags); + } + + if (det->width && det->height) + video->v4l2_input_status = 0; + + dev_dbg(video->dev, "Got resolution[%dx%d] -> [%dx%d], status %d\n", + act->width, act->height, det->width, det->height, + video->v4l2_input_status); +} + +static int npcm_video_set_resolution(struct npcm_video *video, + struct v4l2_bt_timings *timing) +{ + struct regmap *vcd = video->vcd_regmap; + unsigned int mode; + + if (npcm_video_capres(video, timing->width, timing->height)) { + dev_err(video->dev, "Failed to set VCD_CAP_RES\n"); + return -EINVAL; + } + + video->active_timings = *timing; + video->bytesperpixel = npcm_video_get_bpp(video); + npcm_video_set_linepitch(video, timing->width * video->bytesperpixel); + video->bytesperline = npcm_video_get_linepitch(video); + video->pix_fmt.width = timing->width ? timing->width : MIN_WIDTH; + video->pix_fmt.height = timing->height ? timing->height : MIN_HEIGHT; + video->pix_fmt.sizeimage = video->pix_fmt.width * video->pix_fmt.height * + video->bytesperpixel; + video->pix_fmt.bytesperline = video->bytesperline; + + npcm_video_kvm_bw(video, timing->pixelclock > VCD_KVM_BW_PCLK); + npcm_video_gfx_reset(video); + regmap_read(vcd, VCD_MODE, &mode); + + dev_dbg(video->dev, "VCD mode = 0x%x, %s mode\n", mode, + npcm_video_is_mga(video) ? "Hi Res" : "VGA"); + + dev_dbg(video->dev, + "Digital mode: %d x %d x %d, pixelclock %lld, bytesperline %d\n", + timing->width, timing->height, video->bytesperpixel, + timing->pixelclock, video->bytesperline); + + return 0; +} + +static void npcm_video_start(struct npcm_video *video) +{ + npcm_video_init_reg(video); + + if (!npcm_video_alloc_fb(video, &video->src)) { + dev_err(video->dev, "Failed to allocate VCD frame buffer\n"); + return; + } + + npcm_video_detect_resolution(video); + if (npcm_video_set_resolution(video, &video->detected_timings)) { + dev_err(video->dev, "Failed to set resolution\n"); + return; + } + + /* Set frame buffer physical address */ + regmap_write(video->vcd_regmap, VCD_FBA_ADR, video->src.dma); + regmap_write(video->vcd_regmap, VCD_FBB_ADR, video->src.dma); + + if (video->ece.enable && atomic_inc_return(&video->ece.clients) == 1) { + npcm_video_ece_ip_reset(video); + npcm_video_ece_ctrl_reset(video); + npcm_video_ece_set_fb_addr(video, video->src.dma); + npcm_video_ece_set_lp(video, video->bytesperline); + + dev_dbg(video->dev, "ECE open: client %d\n", + atomic_read(&video->ece.clients)); + } +} + +static void npcm_video_stop(struct npcm_video *video) +{ + struct regmap *vcd = video->vcd_regmap; + + set_bit(VIDEO_STOPPED, &video->flags); + + regmap_write(vcd, VCD_INTE, 0); + regmap_write(vcd, VCD_MODE, 0); + regmap_write(vcd, VCD_RCHG, 0); + regmap_write(vcd, VCD_STAT, VCD_STAT_CLEAR); + + if (video->src.size) + npcm_video_free_fb(video, &video->src); + + npcm_video_free_diff_table(video); + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + video->flags = 0; + video->ctrl_cmd = VCD_CMD_OPERATION_CAPTURE; + + if (video->ece.enable && atomic_dec_return(&video->ece.clients) == 0) { + npcm_video_ece_stop(video); + dev_dbg(video->dev, "ECE close: client %d\n", + atomic_read(&video->ece.clients)); + } +} + +static unsigned int npcm_video_raw(struct npcm_video *video, int index, void *addr) +{ + unsigned int width = video->active_timings.width; + unsigned int height = video->active_timings.height; + unsigned int i, len, offset, bytes = 0; + + video->rect[index] = npcm_video_add_rect(video, index, 0, 0, width, height); + + for (i = 0; i < height; i++) { + len = width * video->bytesperpixel; + offset = i * video->bytesperline; + + memcpy(addr + bytes, video->src.virt + offset, len); + bytes += len; + } + + return bytes; +} + +static unsigned int npcm_video_hextile(struct npcm_video *video, unsigned int index, + unsigned int dma_addr, void *vaddr) +{ + struct rect_list *rect_list; + struct v4l2_rect *rect; + unsigned int offset, len, bytes = 0; + + npcm_video_ece_ctrl_reset(video); + npcm_video_ece_clear_rect_offset(video); + npcm_video_ece_set_fb_addr(video, video->src.dma); + + /* Set base address of encoded data to video buffer */ + npcm_video_ece_set_enc_dba(video, dma_addr); + + npcm_video_ece_set_lp(video, video->bytesperline); + npcm_video_get_diff_rect(video, index); + + list_for_each_entry(rect_list, &video->list[index], list) { + rect = &rect_list->clip.c; + offset = npcm_video_ece_read_rect_offset(video); + npcm_video_ece_enc_rect(video, rect->left, rect->top, + rect->width, rect->height); + + len = npcm_video_ece_get_ed_size(video, offset, vaddr); + npcm_video_ece_prepend_rect_header(vaddr + offset, + rect->left, rect->top, + rect->width, rect->height); + bytes += len; + } + + return bytes; +} + +static irqreturn_t npcm_video_irq(int irq, void *arg) +{ + struct npcm_video *video = arg; + struct regmap *vcd = video->vcd_regmap; + struct npcm_video_buffer *buf; + unsigned int index, size, status, fmt; + dma_addr_t dma_addr; + void *addr; + static const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + regmap_read(vcd, VCD_STAT, &status); + dev_dbg(video->dev, "VCD irq status 0x%x\n", status); + + regmap_write(vcd, VCD_STAT, VCD_STAT_CLEAR); + + if (test_bit(VIDEO_STOPPED, &video->flags) || + !test_bit(VIDEO_STREAMING, &video->flags)) + return IRQ_NONE; + + if (status & VCD_STAT_DONE) { + regmap_write(vcd, VCD_INTE, 0); + spin_lock(&video->lock); + clear_bit(VIDEO_CAPTURING, &video->flags); + buf = list_first_entry_or_null(&video->buffers, + struct npcm_video_buffer, link); + if (!buf) { + spin_unlock(&video->lock); + return IRQ_NONE; + } + + addr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0); + index = buf->vb.vb2_buf.index; + fmt = video->pix_fmt.pixelformat; + + switch (fmt) { + case V4L2_PIX_FMT_RGB565: + size = npcm_video_raw(video, index, addr); + break; + case V4L2_PIX_FMT_HEXTILE: + dma_addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + size = npcm_video_hextile(video, index, dma_addr, addr); + break; + default: + spin_unlock(&video->lock); + return IRQ_NONE; + } + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size); + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = video->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + list_del(&buf->link); + spin_unlock(&video->lock); + + if (npcm_video_start_frame(video)) + dev_err(video->dev, "Failed to capture next frame\n"); + } + + /* Resolution changed */ + if (status & VCD_STAT_VHT_CHG || status & VCD_STAT_HAC_CHG) { + if (!test_bit(VIDEO_RES_CHANGING, &video->flags)) { + set_bit(VIDEO_RES_CHANGING, &video->flags); + + vb2_queue_error(&video->queue); + v4l2_event_queue(&video->vdev, &ev); + } + } + + if (status & VCD_STAT_IFOR || status & VCD_STAT_IFOT) { + dev_warn(video->dev, "VCD FIFO overrun or over thresholds\n"); + if (npcm_video_start_frame(video)) + dev_err(video->dev, "Failed to recover from FIFO overrun\n"); + } + + return IRQ_HANDLED; +} + +static int npcm_video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DEVICE_NAME, sizeof(cap->driver)); + strscpy(cap->card, "NPCM Video Engine", sizeof(cap->card)); + + return 0; +} + +static int npcm_video_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct npcm_video *video = video_drvdata(file); + const struct npcm_fmt *fmt; + + if (f->index >= NUM_FORMATS) + return -EINVAL; + + fmt = &npcm_fmt_list[f->index]; + if (fmt->fourcc == V4L2_PIX_FMT_HEXTILE && !video->ece.enable) + return -EINVAL; + + f->pixelformat = fmt->fourcc; + return 0; +} + +static int npcm_video_try_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct npcm_video *video = video_drvdata(file); + const struct npcm_fmt *fmt; + + fmt = npcm_video_find_format(f); + + /* If format not found or HEXTILE not supported, use RGB565 as default */ + if (!fmt || (fmt->fourcc == V4L2_PIX_FMT_HEXTILE && !video->ece.enable)) + f->fmt.pix.pixelformat = npcm_fmt_list[0].fourcc; + + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + f->fmt.pix.quantization = V4L2_QUANTIZATION_FULL_RANGE; + f->fmt.pix.width = video->pix_fmt.width; + f->fmt.pix.height = video->pix_fmt.height; + f->fmt.pix.bytesperline = video->bytesperline; + f->fmt.pix.sizeimage = video->pix_fmt.sizeimage; + + return 0; +} + +static int npcm_video_get_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct npcm_video *video = video_drvdata(file); + + f->fmt.pix = video->pix_fmt; + return 0; +} + +static int npcm_video_set_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct npcm_video *video = video_drvdata(file); + int ret; + + ret = npcm_video_try_format(file, fh, f); + if (ret) + return ret; + + if (vb2_is_busy(&video->queue)) { + dev_err(video->dev, "%s device busy\n", __func__); + return -EBUSY; + } + + video->pix_fmt.pixelformat = f->fmt.pix.pixelformat; + return 0; +} + +static int npcm_video_enum_input(struct file *file, void *fh, + struct v4l2_input *inp) +{ + struct npcm_video *video = video_drvdata(file); + + if (inp->index) + return -EINVAL; + + strscpy(inp->name, "Host VGA capture", sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->capabilities = V4L2_IN_CAP_DV_TIMINGS; + inp->status = video->v4l2_input_status; + + return 0; +} + +static int npcm_video_get_input(struct file *file, void *fh, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int npcm_video_set_input(struct file *file, void *fh, unsigned int i) +{ + if (i) + return -EINVAL; + + return 0; +} + +static int npcm_video_set_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct npcm_video *video = video_drvdata(file); + int rc; + + if (timings->bt.width == video->active_timings.width && + timings->bt.height == video->active_timings.height) + return 0; + + if (vb2_is_busy(&video->queue)) { + dev_err(video->dev, "%s device busy\n", __func__); + return -EBUSY; + } + + rc = npcm_video_set_resolution(video, &timings->bt); + if (rc) + return rc; + + timings->type = V4L2_DV_BT_656_1120; + + return 0; +} + +static int npcm_video_get_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct npcm_video *video = video_drvdata(file); + + timings->type = V4L2_DV_BT_656_1120; + timings->bt = video->active_timings; + + return 0; +} + +static int npcm_video_query_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct npcm_video *video = video_drvdata(file); + + npcm_video_detect_resolution(video); + timings->type = V4L2_DV_BT_656_1120; + timings->bt = video->detected_timings; + + return video->v4l2_input_status ? -ENOLINK : 0; +} + +static int npcm_video_enum_dv_timings(struct file *file, void *fh, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &npcm_video_timings_cap, + NULL, NULL); +} + +static int npcm_video_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap) +{ + *cap = npcm_video_timings_cap; + + return 0; +} + +static int npcm_video_sub_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + } + + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static const struct v4l2_ioctl_ops npcm_video_ioctls = { + .vidioc_querycap = npcm_video_querycap, + + .vidioc_enum_fmt_vid_cap = npcm_video_enum_format, + .vidioc_g_fmt_vid_cap = npcm_video_get_format, + .vidioc_s_fmt_vid_cap = npcm_video_set_format, + .vidioc_try_fmt_vid_cap = npcm_video_try_format, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_enum_input = npcm_video_enum_input, + .vidioc_g_input = npcm_video_get_input, + .vidioc_s_input = npcm_video_set_input, + + .vidioc_s_dv_timings = npcm_video_set_dv_timings, + .vidioc_g_dv_timings = npcm_video_get_dv_timings, + .vidioc_query_dv_timings = npcm_video_query_dv_timings, + .vidioc_enum_dv_timings = npcm_video_enum_dv_timings, + .vidioc_dv_timings_cap = npcm_video_dv_timings_cap, + + .vidioc_subscribe_event = npcm_video_sub_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int npcm_video_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct npcm_video *video = container_of(ctrl->handler, struct npcm_video, + ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_NPCM_CAPTURE_MODE: + if (ctrl->val == V4L2_NPCM_CAPTURE_MODE_COMPLETE) + video->ctrl_cmd = VCD_CMD_OPERATION_CAPTURE; + else if (ctrl->val == V4L2_NPCM_CAPTURE_MODE_DIFF) + video->ctrl_cmd = VCD_CMD_OPERATION_COMPARE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops npcm_video_ctrl_ops = { + .s_ctrl = npcm_video_set_ctrl, +}; + +static const char * const npcm_ctrl_capture_mode_menu[] = { + "COMPLETE", + "DIFF", + NULL, +}; + +static const struct v4l2_ctrl_config npcm_ctrl_capture_mode = { + .ops = &npcm_video_ctrl_ops, + .id = V4L2_CID_NPCM_CAPTURE_MODE, + .name = "NPCM Video Capture Mode", + .type = V4L2_CTRL_TYPE_MENU, + .min = 0, + .max = V4L2_NPCM_CAPTURE_MODE_DIFF, + .def = 0, + .qmenu = npcm_ctrl_capture_mode_menu, +}; + +/* + * This control value is set when a buffer is dequeued by userspace, i.e. in + * npcm_video_buf_finish function. + */ +static const struct v4l2_ctrl_config npcm_ctrl_rect_count = { + .id = V4L2_CID_NPCM_RECT_COUNT, + .name = "NPCM Hextile Rectangle Count", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (MAX_WIDTH / RECT_W) * (MAX_HEIGHT / RECT_H), + .step = 1, + .def = 0, +}; + +static int npcm_video_open(struct file *file) +{ + struct npcm_video *video = video_drvdata(file); + int rc; + + mutex_lock(&video->video_lock); + rc = v4l2_fh_open(file); + if (rc) { + mutex_unlock(&video->video_lock); + return rc; + } + + if (v4l2_fh_is_singular_file(file)) + npcm_video_start(video); + + mutex_unlock(&video->video_lock); + return 0; +} + +static int npcm_video_release(struct file *file) +{ + struct npcm_video *video = video_drvdata(file); + int rc; + + mutex_lock(&video->video_lock); + if (v4l2_fh_is_singular_file(file)) + npcm_video_stop(video); + + rc = _vb2_fop_release(file, NULL); + + mutex_unlock(&video->video_lock); + return rc; +} + +static const struct v4l2_file_operations npcm_video_v4l2_fops = { + .owner = THIS_MODULE, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .open = npcm_video_open, + .release = npcm_video_release, +}; + +static int npcm_video_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct npcm_video *video = vb2_get_drv_priv(q); + unsigned int i; + + if (*num_planes) { + if (sizes[0] < video->pix_fmt.sizeimage) + return -EINVAL; + + return 0; + } + + *num_planes = 1; + sizes[0] = video->pix_fmt.sizeimage; + + for (i = 0; i < VIDEO_MAX_FRAME; i++) + INIT_LIST_HEAD(&video->list[i]); + + return 0; +} + +static int npcm_video_buf_prepare(struct vb2_buffer *vb) +{ + struct npcm_video *video = vb2_get_drv_priv(vb->vb2_queue); + + if (vb2_plane_size(vb, 0) < video->pix_fmt.sizeimage) + return -EINVAL; + + return 0; +} + +static int npcm_video_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct npcm_video *video = vb2_get_drv_priv(q); + int rc; + + video->sequence = 0; + rc = npcm_video_start_frame(video); + if (rc) { + npcm_video_bufs_done(video, VB2_BUF_STATE_QUEUED); + return rc; + } + + set_bit(VIDEO_STREAMING, &video->flags); + return 0; +} + +static void npcm_video_stop_streaming(struct vb2_queue *q) +{ + struct npcm_video *video = vb2_get_drv_priv(q); + struct regmap *vcd = video->vcd_regmap; + + clear_bit(VIDEO_STREAMING, &video->flags); + regmap_write(vcd, VCD_INTE, 0); + regmap_write(vcd, VCD_STAT, VCD_STAT_CLEAR); + npcm_video_gfx_reset(video); + npcm_video_bufs_done(video, VB2_BUF_STATE_ERROR); + video->ctrl_cmd = VCD_CMD_OPERATION_CAPTURE; + v4l2_ctrl_s_ctrl(video->rect_cnt_ctrl, 0); +} + +static void npcm_video_buf_queue(struct vb2_buffer *vb) +{ + struct npcm_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct npcm_video_buffer *nvb = to_npcm_video_buffer(vbuf); + unsigned long flags; + bool empty; + + spin_lock_irqsave(&video->lock, flags); + empty = list_empty(&video->buffers); + list_add_tail(&nvb->link, &video->buffers); + spin_unlock_irqrestore(&video->lock, flags); + + if (test_bit(VIDEO_STREAMING, &video->flags) && + !test_bit(VIDEO_CAPTURING, &video->flags) && empty) { + if (npcm_video_start_frame(video)) + dev_err(video->dev, "Failed to capture next frame\n"); + } +} + +static void npcm_video_buf_finish(struct vb2_buffer *vb) +{ + struct npcm_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct list_head *head, *pos, *nx; + struct rect_list *tmp; + + /* + * This callback is called when the buffer is dequeued, so update + * V4L2_CID_NPCM_RECT_COUNT control value with the number of rectangles + * in this buffer and free associated rect_list. + */ + if (test_bit(VIDEO_STREAMING, &video->flags)) { + v4l2_ctrl_s_ctrl(video->rect_cnt_ctrl, video->rect[vb->index]); + + head = &video->list[vb->index]; + list_for_each_safe(pos, nx, head) { + tmp = list_entry(pos, struct rect_list, list); + list_del(&tmp->list); + kfree(tmp); + } + } +} + +static const struct regmap_config npcm_video_regmap_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = VCD_FIFO, +}; + +static const struct regmap_config npcm_video_ece_regmap_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ECE_HEX_RECT_OFFSET, +}; + +static const struct vb2_ops npcm_video_vb2_ops = { + .queue_setup = npcm_video_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = npcm_video_buf_prepare, + .buf_finish = npcm_video_buf_finish, + .start_streaming = npcm_video_start_streaming, + .stop_streaming = npcm_video_stop_streaming, + .buf_queue = npcm_video_buf_queue, +}; + +static int npcm_video_setup_video(struct npcm_video *video) +{ + struct v4l2_device *v4l2_dev = &video->v4l2_dev; + struct video_device *vdev = &video->vdev; + struct vb2_queue *vbq = &video->queue; + int rc; + + if (video->ece.enable) + video->pix_fmt.pixelformat = V4L2_PIX_FMT_HEXTILE; + else + video->pix_fmt.pixelformat = V4L2_PIX_FMT_RGB565; + + video->pix_fmt.field = V4L2_FIELD_NONE; + video->pix_fmt.colorspace = V4L2_COLORSPACE_SRGB; + video->pix_fmt.quantization = V4L2_QUANTIZATION_FULL_RANGE; + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + + rc = v4l2_device_register(video->dev, v4l2_dev); + if (rc) { + dev_err(video->dev, "Failed to register v4l2 device\n"); + return rc; + } + + v4l2_ctrl_handler_init(&video->ctrl_handler, 2); + v4l2_ctrl_new_custom(&video->ctrl_handler, &npcm_ctrl_capture_mode, NULL); + video->rect_cnt_ctrl = v4l2_ctrl_new_custom(&video->ctrl_handler, + &npcm_ctrl_rect_count, NULL); + if (video->ctrl_handler.error) { + dev_err(video->dev, "Failed to init controls: %d\n", + video->ctrl_handler.error); + + rc = video->ctrl_handler.error; + goto rel_ctrl_handler; + } + v4l2_dev->ctrl_handler = &video->ctrl_handler; + + vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vbq->io_modes = VB2_MMAP | VB2_DMABUF; + vbq->dev = v4l2_dev->dev; + vbq->lock = &video->video_lock; + vbq->ops = &npcm_video_vb2_ops; + vbq->mem_ops = &vb2_dma_contig_memops; + vbq->drv_priv = video; + vbq->buf_struct_size = sizeof(struct npcm_video_buffer); + vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vbq->min_buffers_needed = 3; + + rc = vb2_queue_init(vbq); + if (rc) { + dev_err(video->dev, "Failed to init vb2 queue\n"); + goto rel_ctrl_handler; + } + vdev->queue = vbq; + vdev->fops = &npcm_video_v4l2_fops; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + vdev->v4l2_dev = v4l2_dev; + strscpy(vdev->name, DEVICE_NAME, sizeof(vdev->name)); + vdev->vfl_type = VFL_TYPE_VIDEO; + vdev->vfl_dir = VFL_DIR_RX; + vdev->release = video_device_release_empty; + vdev->ioctl_ops = &npcm_video_ioctls; + vdev->lock = &video->video_lock; + + video_set_drvdata(vdev, video); + rc = video_register_device(vdev, VFL_TYPE_VIDEO, 0); + if (rc) { + dev_err(video->dev, "Failed to register video device\n"); + goto rel_vb_queue; + } + + return 0; + +rel_vb_queue: + vb2_queue_release(vbq); +rel_ctrl_handler: + v4l2_ctrl_handler_free(&video->ctrl_handler); + v4l2_device_unregister(v4l2_dev); + + return rc; +} + +static int npcm_video_ece_init(struct npcm_video *video) +{ + struct device *dev = video->dev; + struct device_node *ece_node; + struct platform_device *ece_pdev; + void __iomem *regs; + + ece_node = of_parse_phandle(video->dev->of_node, "nuvoton,ece", 0); + if (IS_ERR(ece_node)) { + dev_err(dev, "Failed to get ECE phandle in DTS\n"); + return PTR_ERR(ece_node); + } + + video->ece.enable = of_device_is_available(ece_node); + + if (video->ece.enable) { + dev_info(dev, "Support HEXTILE pixel format\n"); + + ece_pdev = of_find_device_by_node(ece_node); + if (IS_ERR(ece_pdev)) { + dev_err(dev, "Failed to find ECE device\n"); + return PTR_ERR(ece_pdev); + } + of_node_put(ece_node); + + regs = devm_platform_ioremap_resource(ece_pdev, 0); + if (IS_ERR(regs)) { + dev_err(dev, "Failed to parse ECE reg in DTS\n"); + return PTR_ERR(regs); + } + + video->ece.regmap = devm_regmap_init_mmio(dev, regs, + &npcm_video_ece_regmap_cfg); + if (IS_ERR(video->ece.regmap)) { + dev_err(dev, "Failed to initialize ECE regmap\n"); + return PTR_ERR(video->ece.regmap); + } + + video->ece.reset = devm_reset_control_get(&ece_pdev->dev, NULL); + if (IS_ERR(video->ece.reset)) { + dev_err(dev, "Failed to get ECE reset control in DTS\n"); + return PTR_ERR(video->ece.reset); + } + } + + return 0; +} + +static int npcm_video_init(struct npcm_video *video) +{ + struct device *dev = video->dev; + int irq, rc; + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (!irq) { + dev_err(dev, "Failed to find VCD IRQ\n"); + return -ENODEV; + } + + rc = devm_request_threaded_irq(dev, irq, NULL, npcm_video_irq, + IRQF_ONESHOT, DEVICE_NAME, video); + if (rc < 0) { + dev_err(dev, "Failed to request IRQ %d\n", irq); + return rc; + } + + of_reserved_mem_device_init(dev); + rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (rc) { + dev_err(dev, "Failed to set DMA mask\n"); + of_reserved_mem_device_release(dev); + } + + rc = npcm_video_ece_init(video); + if (rc) { + dev_err(dev, "Failed to initialize ECE\n"); + return rc; + } + + return 0; +} + +static int npcm_video_probe(struct platform_device *pdev) +{ + struct npcm_video *video = kzalloc(sizeof(*video), GFP_KERNEL); + int rc; + void __iomem *regs; + + if (!video) + return -ENOMEM; + + video->dev = &pdev->dev; + spin_lock_init(&video->lock); + mutex_init(&video->video_lock); + INIT_LIST_HEAD(&video->buffers); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { + dev_err(&pdev->dev, "Failed to parse VCD reg in DTS\n"); + return PTR_ERR(regs); + } + + video->vcd_regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &npcm_video_regmap_cfg); + if (IS_ERR(video->vcd_regmap)) { + dev_err(&pdev->dev, "Failed to initialize VCD regmap\n"); + return PTR_ERR(video->vcd_regmap); + } + + video->reset = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(video->reset)) { + dev_err(&pdev->dev, "Failed to get VCD reset control in DTS\n"); + return PTR_ERR(video->reset); + } + + video->gcr_regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "nuvoton,sysgcr"); + if (IS_ERR(video->gcr_regmap)) + return PTR_ERR(video->gcr_regmap); + + video->gfx_regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "nuvoton,sysgfxi"); + if (IS_ERR(video->gfx_regmap)) + return PTR_ERR(video->gfx_regmap); + + rc = npcm_video_init(video); + if (rc) + return rc; + + rc = npcm_video_setup_video(video); + if (rc) + return rc; + + dev_info(video->dev, "NPCM video driver probed\n"); + return 0; +} + +static int npcm_video_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct npcm_video *video = to_npcm_video(v4l2_dev); + + video_unregister_device(&video->vdev); + vb2_queue_release(&video->queue); + v4l2_ctrl_handler_free(&video->ctrl_handler); + v4l2_device_unregister(v4l2_dev); + if (video->ece.enable) + npcm_video_ece_stop(video); + of_reserved_mem_device_release(dev); + + return 0; +} + +static const struct of_device_id npcm_video_match[] = { + { .compatible = "nuvoton,npcm750-vcd" }, + { .compatible = "nuvoton,npcm845-vcd" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, npcm_video_match); + +static struct platform_driver npcm_video_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = npcm_video_match, + }, + .probe = npcm_video_probe, + .remove = npcm_video_remove, +}; + +module_platform_driver(npcm_video_driver); + +MODULE_AUTHOR("Joseph Liu "); +MODULE_AUTHOR("Marvin Lin "); +MODULE_DESCRIPTION("Driver for Nuvoton NPCM Video Capture/Encode Engine"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 0ab13674a9bd10514486cf1670d71dbd8afec421 Mon Sep 17 00:00:00 2001 From: Martin Tůma Date: Mon, 25 Sep 2023 16:36:04 +0200 Subject: media: pci: mgb4: Added Digiteq Automotive MGB4 driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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ůma Signed-off-by: Hans Verkuil --- MAINTAINERS | 7 + 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 | 244 +++++++++ drivers/media/pci/mgb4/mgb4_cmt.h | 17 + drivers/media/pci/mgb4/mgb4_core.c | 686 +++++++++++++++++++++++ drivers/media/pci/mgb4/mgb4_core.h | 74 +++ drivers/media/pci/mgb4/mgb4_dma.c | 123 +++++ drivers/media/pci/mgb4/mgb4_dma.h | 18 + drivers/media/pci/mgb4/mgb4_i2c.c | 140 +++++ drivers/media/pci/mgb4/mgb4_i2c.h | 35 ++ drivers/media/pci/mgb4/mgb4_io.h | 33 ++ 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 | 772 ++++++++++++++++++++++++++ drivers/media/pci/mgb4/mgb4_sysfs_out.c | 737 +++++++++++++++++++++++++ drivers/media/pci/mgb4/mgb4_sysfs_pci.c | 71 +++ drivers/media/pci/mgb4/mgb4_trigger.c | 208 +++++++ drivers/media/pci/mgb4/mgb4_trigger.h | 8 + drivers/media/pci/mgb4/mgb4_vin.c | 939 ++++++++++++++++++++++++++++++++ drivers/media/pci/mgb4/mgb4_vin.h | 69 +++ drivers/media/pci/mgb4/mgb4_vout.c | 602 ++++++++++++++++++++ drivers/media/pci/mgb4/mgb4_vout.h | 65 +++ 26 files changed, 4956 insertions(+) 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_dma.c create mode 100644 drivers/media/pci/mgb4/mgb4_dma.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 (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 03dae6ab6a88..bba09f9d0141 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6087,6 +6087,13 @@ L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-gpio-mm.c +DIGITEQ AUTOMOTIVE MGB4 V4L2 DRIVER +M: Martin Tuma +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/admin-guide/media/mgb4.rst +F: drivers/media/pci/mgb4/ + DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck L: linux-i2c@vger.kernel.org diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig index ee095bde0b68..7f65aa609388 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" +source "drivers/media/pci/mgb4/Kconfig" source "drivers/media/pci/solo6x10/Kconfig" source "drivers/media/pci/sta2x11/Kconfig" source "drivers/media/pci/tw5864/Kconfig" diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile index 8bed619b7130..f18c7e15abe3 100644 --- a/drivers/media/pci/Makefile +++ b/drivers/media/pci/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_VIDEO_CX25821) += cx25821/ obj-$(CONFIG_VIDEO_CX88) += cx88/ obj-$(CONFIG_VIDEO_DT3155) += dt3155/ obj-$(CONFIG_VIDEO_IVTV) += ivtv/ +obj-$(CONFIG_VIDEO_MGB4) += mgb4/ obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ obj-$(CONFIG_VIDEO_SAA7164) += saa7164/ obj-$(CONFIG_VIDEO_SOLO6X10) += 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/Makefile new file mode 100644 index 000000000000..e92ead18bed0 --- /dev/null +++ b/drivers/media/pci/mgb4/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +mgb4-objs := 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 mgb4_dma.o + +obj-$(CONFIG_VIDEO_MGB4) += mgb4.o diff --git a/drivers/media/pci/mgb4/mgb4_cmt.c b/drivers/media/pci/mgb4/mgb4_cmt.c new file mode 100644 index 000000000000..70dc78ef193c --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_cmt.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * The CMT module configures the FPGA Clock Management Tile (CMT) registers. For + * different video signal frequencies (FPGA input signal frequencies), the FPGA + * CMT registers need to be adjusted for the FPGA to work properly. The values + * are precomputed based on formulas given by Xilinx in their FPGA documentation + * (which are in turn full of some magic values/tables...). + */ + +#include +#include +#include "mgb4_core.h" +#include "mgb4_cmt.h" + +static const u16 cmt_vals_out[][15] = { + {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 u16 cmt_vals_in[][13] = { + {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 u32 cmt_addrs_out[][15] = { + {0x420, 0x424, 0x428, 0x42C, 0x430, 0x434, 0x450, 0x454, 0x458, 0x460, 0x464, 0x468, 0x4A0, 0x538, 0x53C}, + {0x620, 0x624, 0x628, 0x62C, 0x630, 0x634, 0x650, 0x654, 0x658, 0x660, 0x664, 0x668, 0x6A0, 0x738, 0x73C}, +}; + +static const u32 cmt_addrs_in[][13] = { + {0x020, 0x024, 0x028, 0x02C, 0x050, 0x054, 0x058, 0x060, 0x064, 0x068, 0x0A0, 0x138, 0x13C}, + {0x220, 0x224, 0x228, 0x22C, 0x250, 0x254, 0x258, 0x260, 0x264, 0x268, 0x2A0, 0x338, 0x33C}, +}; + +static const u32 cmt_freq[] = { + 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 = 0; + int r = size - 1; + int m = 0; + + while (l <= r) { + m = (l + r) / 2; + if (array[m] < key) + l = m + 1; + else if (array[m] > key) + r = 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_freq(struct mgb4_vout_dev *voutdev, unsigned int freq) +{ + struct mgb4_regs *video = &voutdev->mgbdev->video; + const struct mgb4_vout_regs *regs = &voutdev->config->regs; + const u16 *reg_set; + const u32 *addr; + u32 config; + size_t i, index; + + index = freq_srch(freq, cmt_freq, ARRAY_SIZE(cmt_freq)); + addr = cmt_addrs_out[voutdev->config->id]; + reg_set = cmt_vals_out[index]; + + config = mgb4_read_reg(video, regs->config); + + mgb4_write_reg(video, regs->config, 0x1 | (config & ~0x3)); + + for (i = 0; i < ARRAY_SIZE(cmt_addrs_out[0]); i++) + mgb4_write_reg(&voutdev->mgbdev->cmt, addr[i], reg_set[i]); + + mgb4_mask_reg(video, regs->config, 0x100, 0x100); + mgb4_mask_reg(video, regs->config, 0x100, 0x0); + + mgb4_write_reg(video, regs->config, config & ~0x1); + + return cmt_freq[index]; +} + +void mgb4_cmt_set_vin_freq_range(struct mgb4_vin_dev *vindev, + unsigned int freq_range) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_regs *regs = &vindev->config->regs; + const u16 *reg_set; + const u32 *addr; + u32 config; + size_t i; + + addr = cmt_addrs_in[vindev->config->id]; + reg_set = cmt_vals_in[freq_range]; + + config = mgb4_read_reg(video, regs->config); + + mgb4_write_reg(video, regs->config, 0x1 | (config & ~0x3)); + + for (i = 0; i < ARRAY_SIZE(cmt_addrs_in[0]); i++) + mgb4_write_reg(&vindev->mgbdev->cmt, addr[i], reg_set[i]); + + mgb4_mask_reg(video, regs->config, 0x1000, 0x1000); + mgb4_mask_reg(video, regs->config, 0x1000, 0x0); + + mgb4_write_reg(video, regs->config, config & ~0x1); +} diff --git a/drivers/media/pci/mgb4/mgb4_cmt.h b/drivers/media/pci/mgb4/mgb4_cmt.h new file mode 100644 index 000000000000..b15df56ca059 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_cmt.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 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_freq(struct mgb4_vout_dev *voutdev, unsigned int freq); +void mgb4_cmt_set_vin_freq_range(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/mgb4_core.c new file mode 100644 index 000000000000..3efb33fbf40c --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_core.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This is the driver for the MGB4 video grabber card by Digiteq Automotive. + * + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This is the main driver module. The DMA, I2C and SPI sub-drivers are + * initialized here and the input/output v4l2 devices are created. + * + * The mgb4 card uses different expansion modules for different video sources + * (GMSL and FPDL3 for now) so in probe() we detect the module type based on + * what we see on the I2C bus and check if it matches the FPGA bitstream (there + * are different bitstreams for different expansion modules). When no expansion + * module is present, we still let the driver initialize to allow flashing of + * the FPGA firmware using the SPI FLASH device. No v4l2 video devices are + * created in this case. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mgb4_dma.h" +#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 + +ATTRIBUTE_GROUPS(mgb4_pci); + +static int flashid; + +static struct xdma_chan_info h2c_chan_info = { + .dir = DMA_MEM_TO_DEV, +}; + +static struct xdma_chan_info c2h_chan_info = { + .dir = DMA_DEV_TO_MEM, +}; + +static struct xspi_platform_data spi_platform_data = { + .num_chipselect = 1, + .bits_per_word = 8 +}; + +static const struct i2c_board_info extender_info = { + I2C_BOARD_INFO("extender", 0x21) +}; + +#if IS_REACHABLE(CONFIG_HWMON) +static umode_t temp_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_temp && + (attr == hwmon_temp_input || attr == hwmon_temp_label)) + return 0444; + else + return 0; +} + +static int temp_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + u32 val10, raw; + + if (type != hwmon_temp || attr != hwmon_temp_input) + return -EOPNOTSUPP; + + raw = mgb4_read_reg(&mgbdev->video, 0xD0); + /* register value -> Celsius degrees formula given by Xilinx */ + val10 = ((((raw >> 20) & 0xFFF) * 503975) - 1118822400) / 409600; + *val = val10 * 100; + + return 0; +} + +static int temp_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + if (type != hwmon_temp || attr != hwmon_temp_label) + return -EOPNOTSUPP; + + *str = "FPGA Temperature"; + + return 0; +} + +static const struct hwmon_ops temp_ops = { + .is_visible = temp_is_visible, + .read = temp_read, + .read_string = temp_read_string +}; + +static const struct hwmon_channel_info *temp_channel_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_chip_info temp_chip_info = { + .ops = &temp_ops, + .info = temp_channel_info, +}; +#endif + +static int match_i2c_adap(struct device *dev, void *data) +{ + return i2c_verify_adapter(dev) ? 1 : 0; +} + +static struct i2c_adapter *get_i2c_adap(struct platform_device *pdev) +{ + struct device *dev; + + mutex_lock(&pdev->dev.mutex); + dev = device_find_child(&pdev->dev, NULL, match_i2c_adap); + mutex_unlock(&pdev->dev.mutex); + + return dev ? to_i2c_adapter(dev) : NULL; +} + +static int match_spi_adap(struct device *dev, void *data) +{ + return to_spi_device(dev) ? 1 : 0; +} + +static struct spi_master *get_spi_adap(struct platform_device *pdev) +{ + struct device *dev; + + mutex_lock(&pdev->dev.mutex); + dev = device_find_child(&pdev->dev, NULL, match_spi_adap); + mutex_unlock(&pdev->dev.mutex); + + return dev ? container_of(dev, struct spi_master, dev) : NULL; +} + +static int init_spi(struct mgb4_dev *mgbdev) +{ + struct resource spi_resources[] = { + { + .start = 0x400, + .end = 0x47f, + .flags = IORESOURCE_MEM, + .name = "io-memory", + }, + { + .start = 14, + .end = 14, + .flags = IORESOURCE_IRQ, + .name = "irq", + }, + }; + struct spi_board_info spi_info = { + .max_speed_hz = 10000000, + .modalias = "m25p80", + .chip_select = 0, + .mode = SPI_MODE_3, + }; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + struct spi_master *master; + struct spi_device *spi_dev; + u32 irq; + int rv, id; + resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + request_module("platform:xilinx_spi"); + + irq = xdma_get_user_irq(mgbdev->xdev, 14); + xdma_enable_user_irq(mgbdev->xdev, irq); + + spi_resources[0].parent = &pdev->resource[MGB4_MGB4_BAR_ID]; + spi_resources[0].start += mapbase; + spi_resources[0].end += mapbase; + spi_resources[1].start = irq; + spi_resources[1].end = irq; + + id = pci_dev_id(pdev); + mgbdev->spi_pdev = 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 = get_spi_adap(mgbdev->spi_pdev); + if (!master) { + dev_err(dev, "failed to get SPI adapter\n"); + rv = -EINVAL; + goto err_pdev; + } + + snprintf(mgbdev->fw_part_name, sizeof(mgbdev->fw_part_name), + "mgb4-fw.%d", flashid); + mgbdev->partitions[0].name = mgbdev->fw_part_name; + mgbdev->partitions[0].size = 0x400000; + mgbdev->partitions[0].offset = 0x400000; + mgbdev->partitions[0].mask_flags = 0; + + snprintf(mgbdev->data_part_name, sizeof(mgbdev->data_part_name), + "mgb4-data.%d", flashid); + mgbdev->partitions[1].name = mgbdev->data_part_name; + mgbdev->partitions[1].size = 0x10000; + mgbdev->partitions[1].offset = 0xFF0000; + mgbdev->partitions[1].mask_flags = MTD_CAP_NORFLASH; + + snprintf(mgbdev->flash_name, sizeof(mgbdev->flash_name), + "mgb4-flash.%d", flashid); + mgbdev->flash_data.name = mgbdev->flash_name; + mgbdev->flash_data.parts = mgbdev->partitions; + mgbdev->flash_data.nr_parts = ARRAY_SIZE(mgbdev->partitions); + mgbdev->flash_data.type = "spi-nor"; + + spi_info.platform_data = &mgbdev->flash_data; + + spi_dev = spi_new_device(master, &spi_info); + put_device(&master->dev); + if (!spi_dev) { + dev_err(dev, "failed to create MTD device\n"); + rv = -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[] = { + { + .start = 0x200, + .end = 0x3ff, + .flags = IORESOURCE_MEM, + .name = "io-memory", + }, + { + .start = 15, + .end = 15, + .flags = IORESOURCE_IRQ, + .name = "irq", + }, + }; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + char clk_name[16]; + u32 irq; + int rv, id; + resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + request_module("platform:xiic-i2c"); + + irq = xdma_get_user_irq(mgbdev->xdev, 15); + xdma_enable_user_irq(mgbdev->xdev, irq); + + i2c_resources[0].parent = &pdev->resource[MGB4_MGB4_BAR_ID]; + i2c_resources[0].start += mapbase; + i2c_resources[0].end += mapbase; + i2c_resources[1].start = irq; + i2c_resources[1].end = irq; + + id = 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 = clk_hw_register_fixed_rate(NULL, clk_name, NULL, + 0, 125000000); + if (IS_ERR(mgbdev->i2c_clk)) { + dev_err(dev, "failed to register I2C clock\n"); + return PTR_ERR(mgbdev->i2c_clk); + } + mgbdev->i2c_cl = 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 = -ENOMEM; + goto err_clk; + } + + mgbdev->i2c_pdev = 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 = PTR_ERR(mgbdev->i2c_pdev); + goto err_clkdev; + } + + mgbdev->i2c_adap = get_i2c_adap(mgbdev->i2c_pdev); + if (!mgbdev->i2c_adap) { + dev_err(dev, "failed to get I2C adapter\n"); + rv = -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 get_serial_number(struct mgb4_dev *mgbdev) +{ + struct device *dev = &mgbdev->pdev->dev; + struct mtd_info *mtd; + size_t rs; + int rv; + + mgbdev->serial_number = 0; + + mtd = 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 = mtd_read(mtd, 0, sizeof(mgbdev->serial_number), &rs, + (u_char *)&mgbdev->serial_number); + put_mtd_device(mtd); + if (rv < 0 || rs != 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 = &mgbdev->pdev->dev; + struct mgb4_i2c_client extender; + s32 version; + u32 fw_version; + int rv; + + rv = 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 = 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 = ~((u32)version) & 0xff; + if (!(MGB4_IS_FPDL3(mgbdev) || MGB4_IS_GMSL(mgbdev))) { + dev_err(dev, "unknown module type\n"); + return -EINVAL; + } + fw_version = mgb4_read_reg(&mgbdev->video, 0xC4); + if (fw_version >> 24 != mgbdev->module_version >> 4) { + dev_err(dev, "module/firmware type mismatch\n"); + return -EINVAL; + } + + dev_info(dev, "%s module detected\n", + MGB4_IS_FPDL3(mgbdev) ? "FPDL3" : "GMSL"); + + return 0; +} + +static int map_regs(struct pci_dev *pdev, struct resource *res, + struct mgb4_regs *regs) +{ + int rv; + resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + res->start += mapbase; + res->end += mapbase; + + rv = 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 init_xdma(struct mgb4_dev *mgbdev) +{ + struct xdma_platdata data; + struct resource res[2] = { 0 }; + struct dma_slave_map *map; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + int i; + + res[0].start = pci_resource_start(pdev, MGB4_XDMA_BAR_ID); + res[0].end = pci_resource_end(pdev, MGB4_XDMA_BAR_ID); + res[0].flags = IORESOURCE_MEM; + res[0].parent = &pdev->resource[MGB4_XDMA_BAR_ID]; + res[1].start = pci_irq_vector(pdev, 0); + res[1].end = res[1].start + MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES + + MGB4_USER_IRQS - 1; + res[1].flags = IORESOURCE_IRQ; + + data.max_dma_channels = MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES; + data.device_map = mgbdev->slave_map; + data.device_map_cnt = MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES; + + for (i = 0; i < MGB4_VIN_DEVICES; i++) { + sprintf(mgbdev->channel_names[i], "c2h%d", i); + map = &data.device_map[i]; + map->slave = mgbdev->channel_names[i]; + map->devname = dev_name(dev); + map->param = XDMA_FILTER_PARAM(&c2h_chan_info); + } + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + sprintf(mgbdev->channel_names[i + MGB4_VIN_DEVICES], "h2c%d", i); + map = &data.device_map[i + MGB4_VIN_DEVICES]; + map->slave = mgbdev->channel_names[i + MGB4_VIN_DEVICES]; + map->devname = dev_name(dev); + map->param = XDMA_FILTER_PARAM(&h2c_chan_info); + } + + mgbdev->xdev = platform_device_register_resndata(dev, "xdma", + PLATFORM_DEVID_AUTO, res, + 2, &data, sizeof(data)); + if (IS_ERR(mgbdev->xdev)) { + dev_err(dev, "failed to register XDMA device\n"); + return PTR_ERR(mgbdev->xdev); + } + + return 0; +} + +static void free_xdma(struct mgb4_dev *mgbdev) +{ + platform_device_unregister(mgbdev->xdev); +} + +static int mgb4_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int i, rv; + struct mgb4_dev *mgbdev; + struct resource video = { + .start = 0x0, + .end = 0x100, + .flags = IORESOURCE_MEM, + .name = "mgb4-video", + }; + struct resource cmt = { + .start = 0x1000, + .end = 0x1800, + .flags = IORESOURCE_MEM, + .name = "mgb4-cmt", + }; + int irqs = pci_msix_vec_count(pdev); + + mgbdev = kzalloc(sizeof(*mgbdev), GFP_KERNEL); + if (!mgbdev) + return -ENOMEM; + + mgbdev->pdev = pdev; + pci_set_drvdata(pdev, mgbdev); + + /* PCIe related stuff */ + rv = pci_enable_device(pdev); + if (rv) { + dev_err(&pdev->dev, "error enabling PCI device\n"); + goto err_mgbdev; + } + + rv = pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN); + if (rv) + dev_warn(&pdev->dev, "error enabling PCIe relaxed ordering\n"); + rv = 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 = pcie_set_readrq(pdev, 512); + if (rv) + dev_warn(&pdev->dev, "error setting PCIe max. memory read size\n"); + pci_set_master(pdev); + + rv = 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; + } + + rv = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (rv) { + dev_err(&pdev->dev, "error setting DMA mask\n"); + goto err_enable_pci; + } + + /* DMA + IRQ engine */ + rv = init_xdma(mgbdev); + if (rv) + goto err_alloc_irq; + rv = mgb4_dma_channel_init(mgbdev); + if (rv) + goto err_dma_chan; + + /* mgb4 video registers */ + rv = map_regs(pdev, &video, &mgbdev->video); + if (rv < 0) + goto err_dma_chan; + /* mgb4 cmt registers */ + rv = map_regs(pdev, &cmt, &mgbdev->cmt); + if (rv < 0) + goto err_video_regs; + + /* SPI FLASH */ + rv = init_spi(mgbdev); + if (rv < 0) + goto err_cmt_regs; + + /* I2C controller */ + rv = init_i2c(mgbdev); + if (rv < 0) + goto err_spi; + + /* PCI card related sysfs attributes */ + rv = device_add_groups(&pdev->dev, mgb4_pci_groups); + if (rv < 0) + goto err_i2c; + +#if IS_REACHABLE(CONFIG_HWMON) + /* HWmon (card temperature) */ + mgbdev->hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "mgb4", + mgbdev, + &temp_chip_info, + NULL); +#endif + +#ifdef CONFIG_DEBUG_FS + mgbdev->debugfs = debugfs_create_dir(dev_name(&pdev->dev), NULL); +#endif + + /* 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 = get_module_version(mgbdev); + if (rv < 0) + goto exit; + + /* Video input v4l2 devices */ + for (i = 0; i < MGB4_VIN_DEVICES; i++) + mgbdev->vin[i] = mgb4_vin_create(mgbdev, i); + + /* Video output v4l2 devices */ + for (i = 0; i < MGB4_VOUT_DEVICES; i++) + mgbdev->vout[i] = mgb4_vout_create(mgbdev, i); + + /* Triggers */ + mgbdev->indio_dev = 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_dma_chan: + mgb4_dma_channel_free(mgbdev); + free_xdma(mgbdev); +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 = pci_get_drvdata(pdev); + int i; + +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(mgbdev->debugfs); +#endif +#if IS_REACHABLE(CONFIG_HWMON) + hwmon_device_unregister(mgbdev->hwmon_dev); +#endif + + if (mgbdev->indio_dev) + mgb4_trigger_free(mgbdev->indio_dev); + + for (i = 0; i < MGB4_VOUT_DEVICES; i++) + if (mgbdev->vout[i]) + mgb4_vout_free(mgbdev->vout[i]); + for (i = 0; i < MGB4_VIN_DEVICES; i++) + if (mgbdev->vin[i]) + mgb4_vin_free(mgbdev->vin[i]); + + device_remove_groups(&mgbdev->pdev->dev, mgb4_pci_groups); + free_spi(mgbdev); + free_i2c(mgbdev); + mgb4_regs_free(&mgbdev->video); + mgb4_regs_free(&mgbdev->cmt); + + mgb4_dma_channel_free(mgbdev); + free_xdma(mgbdev); + + pci_disable_msix(mgbdev->pdev); + pci_disable_device(mgbdev->pdev); + + kfree(mgbdev); +} + +static const struct pci_device_id mgb4_pci_ids[] = { + { PCI_DEVICE(0x1ed8, 0x0101), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, mgb4_pci_ids); + +static struct pci_driver mgb4_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = mgb4_pci_ids, + .probe = mgb4_probe, + .remove = 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/mgb4_core.h new file mode 100644 index 000000000000..2a946e46aec1 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_core.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 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_IS_GMSL(mgbdev) \ + ((mgbdev)->module_version >> 4 == 2) +#define MGB4_IS_FPDL3(mgbdev) \ + ((mgbdev)->module_version >> 4 == 1) + +struct mgb4_dma_channel { + struct dma_chan *chan; + struct completion req_compl; +}; + +struct mgb4_dev { + struct pci_dev *pdev; + struct platform_device *xdev; + struct mgb4_vin_dev *vin[MGB4_VIN_DEVICES]; + struct mgb4_vout_dev *vout[MGB4_VOUT_DEVICES]; + + struct mgb4_dma_channel c2h_chan[MGB4_VIN_DEVICES]; + struct mgb4_dma_channel h2c_chan[MGB4_VOUT_DEVICES]; + struct dma_slave_map slave_map[MGB4_VIN_DEVICES + 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; /* I2C bus access lock */ + + struct platform_device *spi_pdev; + struct flash_platform_data flash_data; + struct mtd_partition partitions[2]; + char flash_name[16]; + char fw_part_name[16]; + char data_part_name[16]; + char channel_names[MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES][16]; + + struct iio_dev *indio_dev; +#if IS_REACHABLE(CONFIG_HWMON) + struct device *hwmon_dev; +#endif + + unsigned long io_reconfig; + + u8 module_version; + u32 serial_number; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif +}; + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_dma.c b/drivers/media/pci/mgb4/mgb4_dma.c new file mode 100644 index 000000000000..cae888e6504b --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_dma.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + * + * This module handles the DMA transfers. A standard dmaengine API as provided + * by the XDMA module is used. + */ + +#include +#include +#include "mgb4_core.h" +#include "mgb4_dma.h" + +static void chan_irq(void *param) +{ + struct mgb4_dma_channel *chan = param; + + complete(&chan->req_compl); +} + +int mgb4_dma_transfer(struct mgb4_dev *mgbdev, u32 channel, bool write, + u64 paddr, struct sg_table *sgt) +{ + struct dma_slave_config cfg; + struct mgb4_dma_channel *chan; + struct dma_async_tx_descriptor *tx; + struct pci_dev *pdev = mgbdev->pdev; + int ret; + + memset(&cfg, 0, sizeof(cfg)); + + if (write) { + cfg.direction = DMA_MEM_TO_DEV; + cfg.dst_addr = paddr; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + chan = &mgbdev->h2c_chan[channel]; + } else { + cfg.direction = DMA_DEV_TO_MEM; + cfg.src_addr = paddr; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + chan = &mgbdev->c2h_chan[channel]; + } + + ret = dmaengine_slave_config(chan->chan, &cfg); + if (ret) { + dev_err(&pdev->dev, "failed to config dma: %d\n", ret); + return ret; + } + + tx = dmaengine_prep_slave_sg(chan->chan, sgt->sgl, sgt->nents, + cfg.direction, 0); + if (!tx) { + dev_err(&pdev->dev, "failed to prep slave sg\n"); + return -EIO; + } + + tx->callback = chan_irq; + tx->callback_param = chan; + + ret = dma_submit_error(dmaengine_submit(tx)); + if (ret) { + dev_err(&pdev->dev, "failed to submit sg\n"); + return -EIO; + } + + dma_async_issue_pending(chan->chan); + + if (!wait_for_completion_timeout(&chan->req_compl, + msecs_to_jiffies(10000))) { + dev_err(&pdev->dev, "dma timeout\n"); + dmaengine_terminate_sync(chan->chan); + return -EIO; + } + + return 0; +} + +int mgb4_dma_channel_init(struct mgb4_dev *mgbdev) +{ + int i, ret; + char name[16]; + struct pci_dev *pdev = mgbdev->pdev; + + for (i = 0; i < MGB4_VIN_DEVICES; i++) { + sprintf(name, "c2h%d", i); + mgbdev->c2h_chan[i].chan = dma_request_chan(&pdev->dev, name); + if (IS_ERR(mgbdev->c2h_chan[i].chan)) { + dev_err(&pdev->dev, "failed to initialize %s", name); + ret = PTR_ERR(mgbdev->c2h_chan[i].chan); + mgbdev->c2h_chan[i].chan = NULL; + return ret; + } + init_completion(&mgbdev->c2h_chan[i].req_compl); + } + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + sprintf(name, "h2c%d", i); + mgbdev->h2c_chan[i].chan = dma_request_chan(&pdev->dev, name); + if (IS_ERR(mgbdev->h2c_chan[i].chan)) { + dev_err(&pdev->dev, "failed to initialize %s", name); + ret = PTR_ERR(mgbdev->h2c_chan[i].chan); + mgbdev->h2c_chan[i].chan = NULL; + return ret; + } + init_completion(&mgbdev->h2c_chan[i].req_compl); + } + + return 0; +} + +void mgb4_dma_channel_free(struct mgb4_dev *mgbdev) +{ + int i; + + for (i = 0; i < MGB4_VIN_DEVICES; i++) { + if (mgbdev->c2h_chan[i].chan) + dma_release_channel(mgbdev->c2h_chan[i].chan); + } + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + if (mgbdev->h2c_chan[i].chan) + dma_release_channel(mgbdev->h2c_chan[i].chan); + } +} diff --git a/drivers/media/pci/mgb4/mgb4_dma.h b/drivers/media/pci/mgb4/mgb4_dma.h new file mode 100644 index 000000000000..4ebc2b1ce9b7 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_dma.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_DMA_H__ +#define __MGB4_DMA_H__ + +#include "mgb4_core.h" + +int mgb4_dma_channel_init(struct mgb4_dev *mgbdev); +void mgb4_dma_channel_free(struct mgb4_dev *mgbdev); + +int mgb4_dma_transfer(struct mgb4_dev *mgbdev, u32 channel, bool write, + u64 paddr, struct sg_table *sgt); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_i2c.c b/drivers/media/pci/mgb4/mgb4_i2c.c new file mode 100644 index 000000000000..2697b67e290e --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_i2c.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * The i2c module unifies the I2C access to the serializes/deserializes. The I2C + * chips on the GMSL module use 16b addressing, the FPDL3 chips use standard + * 8b addressing. + */ + +#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] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = buf, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + buf[0] = (reg >> 8) & 0xff; + buf[1] = (reg >> 0) & 0xff; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EREMOTEIO; + else + return 0; +} + +static int write_r16(struct i2c_client *client, u16 reg, const u8 *val, int len) +{ + int ret; + u8 buf[4]; + struct i2c_msg msg[1] = { + { + .addr = client->addr, + .flags = 0, + .len = 2 + len, + .buf = buf, + } + }; + + if (2 + len > sizeof(buf)) + return -EINVAL; + + buf[0] = (reg >> 8) & 0xff; + buf[1] = (reg >> 0) & 0xff; + memcpy(&buf[2], val, len); + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + else if (ret != 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 = i2c_new_client_device(adap, info); + if (IS_ERR(client->client)) + return PTR_ERR(client->client); + + client->addr_size = 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 == 8) + return i2c_smbus_read_byte_data(client->client, reg); + + ret = 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 == 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, u8 val) +{ + s32 ret; + + if (mask != 0xFF) { + ret = mgb4_i2c_read_byte(client, reg); + if (ret < 0) + return ret; + val |= (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 = 0; i < count; i++) { + res = 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/mgb4_i2c.h new file mode 100644 index 000000000000..fac6a1634474 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_i2c.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 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; +}; + +int mgb4_i2c_init(struct mgb4_i2c_client *client, struct i2c_adapter *adap, + struct i2c_board_info const *info, int addr_size); +void mgb4_i2c_free(struct mgb4_i2c_client *client); + +s32 mgb4_i2c_read_byte(struct mgb4_i2c_client *client, u16 reg); +s32 mgb4_i2c_write_byte(struct mgb4_i2c_client *client, u16 reg, u8 val); +s32 mgb4_i2c_mask_byte(struct mgb4_i2c_client *client, u16 reg, u8 mask, + u8 val); + +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..8698db1be4a9 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_io.h @@ -0,0 +1,33 @@ +/* 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 + +#define MGB4_DEFAULT_WIDTH 1280 +#define MGB4_DEFAULT_HEIGHT 640 +#define MGB4_DEFAULT_PERIOD (125000000 / 60) + +/* Register access error indication */ +#define MGB4_ERR_NO_REG 0xFFFFFFFE +/* Frame buffer addresses greater than 0xFFFFFFFA indicate HW errors */ +#define MGB4_ERR_QUEUE_TIMEOUT 0xFFFFFFFD +#define MGB4_ERR_QUEUE_EMPTY 0xFFFFFFFC +#define MGB4_ERR_QUEUE_FULL 0xFFFFFFFB + +struct mgb4_frame_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +static inline struct mgb4_frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *vbuf) +{ + return container_of(vbuf, struct mgb4_frame_buffer, vb); +} + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_regs.c b/drivers/media/pci/mgb4/mgb4_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 = res->start; + regs->mapsize = res->end - res->start; + + if (!request_mem_region(regs->mapbase, regs->mapsize, res->name)) + return -EINVAL; + regs->membase = 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/mgb4_regs.h new file mode 100644 index 000000000000..c45180890730 --- /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 = mgb4_read_reg(regs, reg); + + val |= ret & ~mask; + mgb4_write_reg(regs, reg, val); +} + +int mgb4_regs_map(struct resource *res, struct mgb4_regs *regs); +void mgb4_regs_free(struct mgb4_regs *regs); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_sysfs.h b/drivers/media/pci/mgb4/mgb4_sysfs.h new file mode 100644 index 000000000000..017d82c0624e --- /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 attribute *mgb4_pci_attrs[]; +extern struct attribute *mgb4_fpdl3_in_attrs[]; +extern struct attribute *mgb4_gmsl_in_attrs[]; +extern struct attribute *mgb4_fpdl3_out_attrs[]; +extern struct attribute *mgb4_gmsl_out_attrs[]; + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_in.c b/drivers/media/pci/mgb4/mgb4_sysfs_in.c new file mode 100644 index 000000000000..0ba66a2cf145 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_in.c @@ -0,0 +1,772 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This module handles all the sysfs info/configuration that is related to the + * v4l2 input devices. + */ + +#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 input_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", vindev->config->id); +} + +static ssize_t oldi_lane_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + struct mgb4_dev *mgbdev = vindev->mgbdev; + u16 i2c_reg; + u8 i2c_mask, i2c_single_val, i2c_dual_val; + u32 config; + int ret; + + i2c_reg = MGB4_IS_GMSL(mgbdev) ? 0x1CE : 0x49; + i2c_mask = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x03; + i2c_single_val = MGB4_IS_GMSL(mgbdev) ? 0x00 : 0x02; + i2c_dual_val = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x00; + + mutex_lock(&mgbdev->i2c_lock); + ret = mgb4_i2c_read_byte(&vindev->deser, i2c_reg); + mutex_unlock(&mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + config = mgb4_read_reg(&mgbdev->video, vindev->config->regs.config); + + if (((config & (1U << 9)) && ((ret & i2c_mask) != i2c_dual_val)) || + (!(config & (1U << 9)) && ((ret & i2c_mask) != i2c_single_val))) { + dev_err(dev, "I2C/FPGA register value mismatch\n"); + return -EINVAL; + } + + return sprintf(buf, "%s\n", config & (1U << 9) ? "1" : "0"); +} + +/* + * OLDI lane width change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t oldi_lane_width_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + struct mgb4_dev *mgbdev = vindev->mgbdev; + u32 fpga_data; + u16 i2c_reg; + u8 i2c_mask, i2c_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* single */ + fpga_data = 0; + i2c_data = MGB4_IS_GMSL(mgbdev) ? 0x00 : 0x02; + break; + case 1: /* dual */ + fpga_data = 1U << 9; + i2c_data = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x00; + break; + default: + return -EINVAL; + } + + i2c_reg = MGB4_IS_GMSL(mgbdev) ? 0x1CE : 0x49; + i2c_mask = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x03; + + mutex_lock(&mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&vindev->deser, i2c_reg, i2c_mask, i2c_data); + mutex_unlock(&mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + mgb4_mask_reg(&mgbdev->video, vindev->config->regs.config, 1U << 9, + fpga_data); + if (MGB4_IS_GMSL(mgbdev)) { + /* reset input link */ + mutex_lock(&mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&vindev->deser, 0x10, 1U << 5, 1U << 5); + mutex_unlock(&mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + } + + return count; +} + +static ssize_t color_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.config); + + return sprintf(buf, "%s\n", config & (1U << 8) ? "0" : "1"); +} + +/* + * Color mapping change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t color_mapping_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 fpga_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* OLDI/JEIDA */ + fpga_data = (1U << 8); + break; + case 1: /* SPWG/VESA */ + fpga_data = 0; + break; + default: + return -EINVAL; + } + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, + 1U << 8, fpga_data); + + return count; +} + +static ssize_t link_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 status = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.status); + + return sprintf(buf, "%s\n", status & (1U << 2) ? "1" : "0"); +} + +static ssize_t stream_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 status = 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 video_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.resolution); + + return sprintf(buf, "%u\n", config >> 16); +} + +static ssize_t video_height_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.resolution); + + return sprintf(buf, "%u\n", config & 0xFFFF); +} + +static ssize_t hsync_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 status = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.status); + u32 res; + + if (!(status & (1U << 11))) + res = 0x02; // not available + else if (status & (1U << 12)) + res = 0x01; // active high + else + res = 0x00; // active low + + return sprintf(buf, "%u\n", res); +} + +static ssize_t vsync_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 status = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.status); + u32 res; + + if (!(status & (1U << 11))) + res = 0x02; // not available + else if (status & (1U << 13)) + res = 0x01; // active high + else + res = 0x00; // active low + + return sprintf(buf, "%u\n", res); +} + +static ssize_t hsync_gap_length_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sync = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.sync); + + return sprintf(buf, "%u\n", sync >> 16); +} + +/* + * HSYNC gap length change is expected to be called on live streams. Video + * device locking/queue check is not needed. + */ +static ssize_t hsync_gap_length_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync, + 0xFFFF0000, val << 16); + + return count; +} + +static ssize_t vsync_gap_length_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sync = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.sync); + + return sprintf(buf, "%u\n", sync & 0xFFFF); +} + +/* + * VSYNC gap length change is expected to be called on live streams. Video + * device locking/queue check is not needed. + */ +static ssize_t vsync_gap_length_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = 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 pclk_frequency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 freq = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.pclk); + + return sprintf(buf, "%u\n", freq); +} + +static ssize_t hsync_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.signal); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +static ssize_t vsync_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.signal2); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +static ssize_t hback_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.signal); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +static ssize_t hfront_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.signal); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +static ssize_t vback_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.signal2); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +static ssize_t vfront_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.signal2); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +static ssize_t frequency_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", vindev->freq_range); +} + +static ssize_t frequency_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mutex_lock(vindev->vdev.lock); + if (vb2_is_busy(vindev->vdev.queue)) { + mutex_unlock(vindev->vdev.lock); + return -EBUSY; + } + + mgb4_cmt_set_vin_freq_range(vindev, val); + vindev->freq_range = val; + + mutex_unlock(vindev->vdev.lock); + + return count; +} + +/* FPDL3 only */ + +static ssize_t fpdl3_input_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = 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; + } +} + +/* + * FPD-Link width change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t fpdl3_input_width_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u8 i2c_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* auto */ + i2c_data = 0x00; + break; + case 1: /* single */ + i2c_data = 0x10; + break; + case 2: /* dual */ + i2c_data = 0x08; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = 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 gmsl_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + s32 r1, r300, r3; + + mutex_lock(&vindev->mgbdev->i2c_lock); + r1 = mgb4_i2c_read_byte(&vindev->deser, 0x01); + r300 = mgb4_i2c_read_byte(&vindev->deser, 0x300); + r3 = 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) == 0x03 && (r300 & 0x0C) == 0x0C && (r3 & 0xC0) == 0xC0) + return sprintf(buf, "0\n"); + else if ((r1 & 0x03) == 0x02 && (r300 & 0x0C) == 0x08 && (r3 & 0xC0) == 0x00) + return sprintf(buf, "1\n"); + else if ((r1 & 0x03) == 0x01 && (r300 & 0x0C) == 0x04 && (r3 & 0xC0) == 0x00) + return sprintf(buf, "2\n"); + else if ((r1 & 0x03) == 0x00 && (r300 & 0x0C) == 0x00 && (r3 & 0xC0) == 0x00) + return sprintf(buf, "3\n"); + else + return -EINVAL; +} + +/* + * GMSL mode change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t gmsl_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + static const struct mgb4_i2c_kv G12[] = { + {0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0}}; + static const struct mgb4_i2c_kv G6[] = { + {0x01, 0x03, 0x02}, {0x300, 0x0C, 0x08}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv G3[] = { + {0x01, 0x03, 0x01}, {0x300, 0x0C, 0x04}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv G1[] = { + {0x01, 0x03, 0x00}, {0x300, 0x0C, 0x00}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv reset[] = { + {0x10, 1U << 5, 1U << 5}, {0x300, 1U << 6, 1U << 6}}; + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + const struct mgb4_i2c_kv *values; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* 12Gb/s */ + values = G12; + break; + case 1: /* 6Gb/s */ + values = G6; + break; + case 2: /* 3Gb/s */ + values = G3; + break; + case 3: /* 1.5Gb/s */ + values = G1; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_configure(&vindev->deser, values, 3); + ret |= mgb4_i2c_configure(&vindev->deser, reset, 2); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static ssize_t gmsl_stream_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = 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 gmsl_stream_id_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 3) + return -EINVAL; + + mutex_lock(vindev->vdev.lock); + if (vb2_is_busy(vindev->vdev.queue)) { + mutex_unlock(vindev->vdev.lock); + return -EBUSY; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&vindev->deser, 0xA0, 0x03, (u8)val); + mutex_unlock(&vindev->mgbdev->i2c_lock); + + mutex_unlock(vindev->vdev.lock); + + return (ret < 0) ? -EIO : count; +} + +static ssize_t gmsl_fec_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + s32 r3e0, r308; + + mutex_lock(&vindev->mgbdev->i2c_lock); + r3e0 = mgb4_i2c_read_byte(&vindev->deser, 0x3E0); + r308 = mgb4_i2c_read_byte(&vindev->deser, 0x308); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (r3e0 < 0 || r308 < 0) + return -EIO; + + if ((r3e0 & 0x07) == 0x00 && (r308 & 0x01) == 0x00) + return sprintf(buf, "0\n"); + else if ((r3e0 & 0x07) == 0x07 && (r308 & 0x01) == 0x01) + return sprintf(buf, "1\n"); + else + return -EINVAL; +} + +/* + * GMSL FEC change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t gmsl_fec_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + static const struct mgb4_i2c_kv enable[] = { + {0x3E0, 0x07, 0x07}, {0x308, 0x01, 0x01}}; + static const struct mgb4_i2c_kv disable[] = { + {0x3E0, 0x07, 0x00}, {0x308, 0x01, 0x00}}; + static const struct mgb4_i2c_kv reset[] = { + {0x10, 1U << 5, 1U << 5}, {0x300, 1U << 6, 1U << 6}}; + const struct mgb4_i2c_kv *values; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* disabled */ + values = disable; + break; + case 1: /* enabled */ + values = enable; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_configure(&vindev->deser, values, 2); + ret |= mgb4_i2c_configure(&vindev->deser, reset, 2); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static DEVICE_ATTR_RO(input_id); +static DEVICE_ATTR_RW(oldi_lane_width); +static DEVICE_ATTR_RW(color_mapping); +static DEVICE_ATTR_RO(link_status); +static DEVICE_ATTR_RO(stream_status); +static DEVICE_ATTR_RO(video_width); +static DEVICE_ATTR_RO(video_height); +static DEVICE_ATTR_RO(hsync_status); +static DEVICE_ATTR_RO(vsync_status); +static DEVICE_ATTR_RW(hsync_gap_length); +static DEVICE_ATTR_RW(vsync_gap_length); +static DEVICE_ATTR_RO(pclk_frequency); +static DEVICE_ATTR_RO(hsync_width); +static DEVICE_ATTR_RO(vsync_width); +static DEVICE_ATTR_RO(hback_porch); +static DEVICE_ATTR_RO(hfront_porch); +static DEVICE_ATTR_RO(vback_porch); +static DEVICE_ATTR_RO(vfront_porch); +static DEVICE_ATTR_RW(frequency_range); + +static DEVICE_ATTR_RW(fpdl3_input_width); + +static DEVICE_ATTR_RW(gmsl_mode); +static DEVICE_ATTR_RW(gmsl_stream_id); +static DEVICE_ATTR_RW(gmsl_fec); + +struct attribute *mgb4_fpdl3_in_attrs[] = { + &dev_attr_input_id.attr, + &dev_attr_link_status.attr, + &dev_attr_stream_status.attr, + &dev_attr_video_width.attr, + &dev_attr_video_height.attr, + &dev_attr_hsync_status.attr, + &dev_attr_vsync_status.attr, + &dev_attr_oldi_lane_width.attr, + &dev_attr_color_mapping.attr, + &dev_attr_hsync_gap_length.attr, + &dev_attr_vsync_gap_length.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_frequency_range.attr, + &dev_attr_fpdl3_input_width.attr, + NULL +}; + +struct attribute *mgb4_gmsl_in_attrs[] = { + &dev_attr_input_id.attr, + &dev_attr_link_status.attr, + &dev_attr_stream_status.attr, + &dev_attr_video_width.attr, + &dev_attr_video_height.attr, + &dev_attr_hsync_status.attr, + &dev_attr_vsync_status.attr, + &dev_attr_oldi_lane_width.attr, + &dev_attr_color_mapping.attr, + &dev_attr_hsync_gap_length.attr, + &dev_attr_vsync_gap_length.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_frequency_range.attr, + &dev_attr_gmsl_mode.attr, + &dev_attr_gmsl_stream_id.attr, + &dev_attr_gmsl_fec.attr, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c new file mode 100644 index 000000000000..23a9aabf3915 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This module handles all the sysfs info/configuration that is related to the + * v4l2 output devices. + */ + +#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 = 0; + + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + voutdev = vindev->mgbdev->vout[i]; + if (!voutdev) + continue; + + config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + if ((config & 0xc) >> 2 == vindev->config->id) + cnt++; + } + + return cnt; +} + +static bool is_busy(struct video_device *dev) +{ + bool ret; + + mutex_lock(dev->lock); + ret = vb2_is_busy(dev->queue); + mutex_unlock(dev->lock); + + return ret; +} + +/* Common for both FPDL3 and GMSL */ + +static ssize_t output_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", voutdev->config->id); +} + +static ssize_t video_source_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + + return sprintf(buf, "%u\n", (config & 0xc) >> 2); +} + +/* + * Video source change may affect the buffer queue of ANY video input/output on + * the card thus if any of the inputs/outputs is in use, we do not allow + * the change. + * + * As we do not want to lock all the video devices at the same time, a two-stage + * locking strategy is used. In addition to the video device locking there is + * a global (PCI device) variable "io_reconfig" atomically checked/set when + * the reconfiguration is running. All the video devices check the variable in + * their queue_setup() functions and do not allow to start the queue when + * the reconfiguration has started. + */ +static ssize_t video_source_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + struct mgb4_dev *mgbdev = voutdev->mgbdev; + struct mgb4_vin_dev *loopin_new = NULL, *loopin_old = NULL; + unsigned long val; + ssize_t ret; + u32 config; + int i; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 3) + return -EINVAL; + + if (test_and_set_bit(0, &mgbdev->io_reconfig)) + return -EBUSY; + + ret = -EBUSY; + for (i = 0; i < MGB4_VIN_DEVICES; i++) + if (mgbdev->vin[i] && is_busy(&mgbdev->vin[i]->vdev)) + goto end; + for (i = 0; i < MGB4_VOUT_DEVICES; i++) + if (mgbdev->vout[i] && is_busy(&mgbdev->vout[i]->vdev)) + goto end; + + config = mgb4_read_reg(&mgbdev->video, voutdev->config->regs.config); + + if (((config & 0xc) >> 2) < MGB4_VIN_DEVICES) + loopin_old = mgbdev->vin[(config & 0xc) >> 2]; + if (val < MGB4_VIN_DEVICES) + loopin_new = mgbdev->vin[val]; + if (loopin_old && loopin_cnt(loopin_old) == 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 == 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 = count; +end: + clear_bit(0, &mgbdev->io_reconfig); + + return ret; +} + +static ssize_t display_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.resolution); + + return sprintf(buf, "%u\n", config >> 16); +} + +static ssize_t display_width_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mutex_lock(voutdev->vdev.lock); + if (vb2_is_busy(voutdev->vdev.queue)) { + mutex_unlock(voutdev->vdev.lock); + return -EBUSY; + } + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.resolution, + 0xFFFF0000, val << 16); + + mutex_unlock(voutdev->vdev.lock); + + return count; +} + +static ssize_t display_height_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.resolution); + + return sprintf(buf, "%u\n", config & 0xFFFF); +} + +static ssize_t display_height_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mutex_lock(voutdev->vdev.lock); + if (vb2_is_busy(voutdev->vdev.queue)) { + mutex_unlock(voutdev->vdev.lock); + return -EBUSY; + } + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.resolution, + 0xFFFF, val); + + mutex_unlock(voutdev->vdev.lock); + + return count; +} + +static ssize_t frame_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 period = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.frame_period); + + return sprintf(buf, "%u\n", 125000000 / period); +} + +/* + * Frame rate change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t frame_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mgb4_write_reg(&voutdev->mgbdev->video, + voutdev->config->regs.frame_period, 125000000 / val); + + return count; +} + +static ssize_t hsync_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +/* + * HSYNC width change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t hsync_width_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = 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 vsync_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +/* + * VSYNC vidth change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t vsync_width_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = 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 hback_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +/* + * hback porch change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t hback_porch_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = 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 vback_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +/* + * vback porch change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t vback_porch_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = 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 hfront_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +/* + * hfront porch change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t hfront_porch_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = 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 vfront_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +/* + * vfront porch change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t vfront_porch_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = 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; +} + +/* FPDL3 only */ + +static ssize_t hsync_polarity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (config & (1U << 31)) >> 31); +} + +/* + * HSYNC polarity change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t hsync_polarity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, + (1U << 31), val << 31); + + return count; +} + +static ssize_t vsync_polarity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (config & (1U << 31)) >> 31); +} + +/* + * VSYNC polarity change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t vsync_polarity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + (1U << 31), val << 31); + + return count; +} + +static ssize_t de_polarity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (config & (1U << 30)) >> 30); +} + +/* + * DE polarity change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t de_polarity_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + (1U << 30), val << 30); + + return count; +} + +static ssize_t fpdl3_output_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&voutdev->mgbdev->i2c_lock); + ret = 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; + } +} + +/* + * FPD-Link width change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t fpdl3_output_width_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u8 i2c_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* auto */ + i2c_data = 0x00; + break; + case 1: /* single */ + i2c_data = 0x01; + break; + case 2: /* dual */ + i2c_data = 0x03; + break; + default: + return -EINVAL; + } + + mutex_lock(&voutdev->mgbdev->i2c_lock); + ret = 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 pclk_frequency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + + return sprintf(buf, "%u\n", voutdev->freq); +} + +static ssize_t pclk_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + unsigned int dp; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(voutdev->vdev.lock); + if (vb2_is_busy(voutdev->vdev.queue)) { + mutex_unlock(voutdev->vdev.lock); + return -EBUSY; + } + + dp = (val > 50000) ? 1 : 0; + voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, val >> dp) << dp; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, + 0x10, dp << 4); + mutex_lock(&voutdev->mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&voutdev->ser, 0x4F, 1 << 6, ((~dp) & 1) << 6); + mutex_unlock(&voutdev->mgbdev->i2c_lock); + + mutex_unlock(voutdev->vdev.lock); + + return (ret < 0) ? -EIO : count; +} + +static DEVICE_ATTR_RO(output_id); +static DEVICE_ATTR_RW(video_source); +static DEVICE_ATTR_RW(display_width); +static DEVICE_ATTR_RW(display_height); +static DEVICE_ATTR_RW(frame_rate); +static DEVICE_ATTR_RW(hsync_polarity); +static DEVICE_ATTR_RW(vsync_polarity); +static DEVICE_ATTR_RW(de_polarity); +static DEVICE_ATTR_RW(pclk_frequency); +static DEVICE_ATTR_RW(hsync_width); +static DEVICE_ATTR_RW(vsync_width); +static DEVICE_ATTR_RW(hback_porch); +static DEVICE_ATTR_RW(hfront_porch); +static DEVICE_ATTR_RW(vback_porch); +static DEVICE_ATTR_RW(vfront_porch); + +static DEVICE_ATTR_RW(fpdl3_output_width); + +struct attribute *mgb4_fpdl3_out_attrs[] = { + &dev_attr_output_id.attr, + &dev_attr_video_source.attr, + &dev_attr_display_width.attr, + &dev_attr_display_height.attr, + &dev_attr_frame_rate.attr, + &dev_attr_hsync_polarity.attr, + &dev_attr_vsync_polarity.attr, + &dev_attr_de_polarity.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_fpdl3_output_width.attr, + NULL +}; + +struct attribute *mgb4_gmsl_out_attrs[] = { + &dev_attr_output_id.attr, + &dev_attr_video_source.attr, + &dev_attr_display_width.attr, + &dev_attr_display_height.attr, + &dev_attr_frame_rate.attr, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_pci.c b/drivers/media/pci/mgb4/mgb4_sysfs_pci.c new file mode 100644 index 000000000000..d26935ff956b --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_pci.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + * + * This module handles all the sysfs info/configuration that is related to the + * PCI card device. + */ + +#include +#include "mgb4_core.h" +#include "mgb4_sysfs.h" + +static ssize_t module_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", mgbdev->module_version & 0x0F); +} + +static ssize_t module_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", mgbdev->module_version >> 4); +} + +static ssize_t fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + u32 config = mgb4_read_reg(&mgbdev->video, 0xC4); + + return sprintf(buf, "%u\n", config & 0xFFFF); +} + +static ssize_t fw_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + u32 config = mgb4_read_reg(&mgbdev->video, 0xC4); + + return sprintf(buf, "%u\n", config >> 24); +} + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + u32 sn = mgbdev->serial_number; + + return sprintf(buf, "%03d-%03d-%03d-%03d\n", sn >> 24, (sn >> 16) & 0xFF, + (sn >> 8) & 0xFF, sn & 0xFF); +} + +static DEVICE_ATTR_RO(module_version); +static DEVICE_ATTR_RO(module_type); +static DEVICE_ATTR_RO(fw_version); +static DEVICE_ATTR_RO(fw_type); +static DEVICE_ATTR_RO(serial_number); + +struct attribute *mgb4_pci_attrs[] = { + &dev_attr_module_type.attr, + &dev_attr_module_version.attr, + &dev_attr_fw_type.attr, + &dev_attr_fw_version.attr, + &dev_attr_serial_number.attr, + 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..923650d53d4c --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_trigger.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This module handles the IIO trigger device. The card has two signal inputs + * for event triggers that can be used to record events related to the video + * stream. A standard linux IIO device with triggered buffer capability is + * created and configured that can be used to fetch the events with the same + * clock source as the video frames. + */ + +#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 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + *val = 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 = iio_trigger_get_drvdata(trig); + struct trigger_data *st = iio_priv(indio_dev); + int irq = xdma_get_user_irq(st->mgbdev->xdev, 11); + + if (state) + xdma_enable_user_irq(st->mgbdev->xdev, irq); + else + xdma_disable_user_irq(st->mgbdev->xdev, irq); + + return 0; +} + +static const struct iio_trigger_ops trigger_ops = { + .set_trigger_state = &trigger_set_state, +}; + +static const struct iio_info trigger_info = { + .read_raw = trigger_read_raw, +}; + +#define TRIGGER_CHANNEL(_si) { \ + .type = IIO_ACTIVITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + .shift = 0, \ + .endianness = IIO_CPU \ + }, \ +} + +static const struct iio_chan_spec trigger_channels[] = { + TRIGGER_CHANNEL(0), + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static irqreturn_t trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct trigger_data *st = iio_priv(indio_dev); + struct { + u32 data; + s64 ts __aligned(8); + } scan; + + scan.data = 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 = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc(&st->mgbdev->pdev->dev, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + ret = request_irq(irq, &iio_trigger_generic_data_rdy_poll, 0, + "mgb4-trigger", st->trig); + if (ret) + goto error_free_trig; + + st->trig->ops = &trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_irq; + + indio_dev->trig = 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 = 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 = mgbdev->pdev; + struct device *dev = &pdev->dev; + int rv, irq; + + indio_dev = iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return NULL; + + indio_dev->info = &trigger_info; + indio_dev->name = "mgb4"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = trigger_channels; + indio_dev->num_channels = ARRAY_SIZE(trigger_channels); + + data = iio_priv(indio_dev); + data->mgbdev = mgbdev; + + irq = xdma_get_user_irq(mgbdev->xdev, 11); + rv = probe_trigger(indio_dev, irq); + if (rv < 0) { + dev_err(dev, "iio triggered setup failed\n"); + goto error_alloc; + } + rv = 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 = 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, irq); +error_alloc: + iio_device_free(indio_dev); + + return NULL; +} + +void mgb4_trigger_free(struct iio_dev *indio_dev) +{ + struct trigger_data *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + remove_trigger(indio_dev, xdma_get_user_irq(st->mgbdev->xdev, 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..6c25bc4576f6 --- /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 + */ + +struct iio_dev *mgb4_trigger_create(struct mgb4_dev *mgbdev); +void mgb4_trigger_free(struct iio_dev *indio_dev); diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c new file mode 100644 index 000000000000..d72b07b87cd1 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vin.c @@ -0,0 +1,939 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This is the v4l2 input device module. It initializes the signal deserializers + * and creates the v4l2 video devices. The input signal can change at any time + * which is handled by the "timings" callbacks and an IRQ based watcher, that + * emits the V4L2_EVENT_SOURCE_CHANGE event in case of a signal source change. + * + * When the device is in loopback mode (a direct, in HW, in->out frame passing + * mode) the card's frame queue must be running regardless of whether a v4l2 + * stream is running and the output parameters like frame buffers padding must + * be in sync with the input parameters. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mgb4_core.h" +#include "mgb4_dma.h" +#include "mgb4_sysfs.h" +#include "mgb4_io.h" +#include "mgb4_vout.h" +#include "mgb4_vin.h" + +ATTRIBUTE_GROUPS(mgb4_fpdl3_in); +ATTRIBUTE_GROUPS(mgb4_gmsl_in); + +static const struct mgb4_vin_config vin_cfg[] = { + {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[] = { + {I2C_BOARD_INFO("deserializer1", 0x38)}, + {I2C_BOARD_INFO("deserializer2", 0x36)}, +}; + +static const struct i2c_board_info gmsl_deser_info[] = { + {I2C_BOARD_INFO("deserializer1", 0x4C)}, + {I2C_BOARD_INFO("deserializer2", 0x2A)}, +}; + +static const struct mgb4_i2c_kv fpdl3_i2c[] = { + {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[] = { + {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 const struct v4l2_dv_timings_cap video_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .min_width = 320, + .max_width = 4096, + .min_height = 240, + .max_height = 2160, + .min_pixelclock = 1843200, /* 320 x 240 x 24Hz */ + .max_pixelclock = 530841600, /* 4096 x 2160 x 60Hz */ + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +/* + * Returns the video output connected with the given video input if the input + * is in loopback mode. + */ +static struct mgb4_vout_dev *loopback_dev(struct mgb4_vin_dev *vindev, int i) +{ + struct mgb4_vout_dev *voutdev; + u32 config; + + voutdev = vindev->mgbdev->vout[i]; + if (!voutdev) + return NULL; + + config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + if ((config & 0xc) >> 2 == vindev->config->id) + return voutdev; + + return NULL; +} + +/* + * Check, whether the loopback mode - a HW INPUT->OUTPUT transmission - is + * enabled on the given input. + */ +static int loopback_active(struct mgb4_vin_dev *vindev) +{ + int i; + + for (i = 0; i < MGB4_VOUT_DEVICES; i++) + if (loopback_dev(vindev, i)) + return 1; + + return 0; +} + +/* + * Set the output frame buffer padding of all outputs connected with the given + * input when the video input is set to loopback mode. The paddings must be + * the same for the loopback to work properly. + */ +static void set_loopback_padding(struct mgb4_vin_dev *vindev, u32 padding) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + struct mgb4_vout_dev *voutdev; + int i; + + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + voutdev = loopback_dev(vindev, i); + if (voutdev) + mgb4_write_reg(video, voutdev->config->regs.padding, + padding); + } +} + +static int get_timings(struct mgb4_vin_dev *vindev, + struct v4l2_dv_timings *timings) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_regs *regs = &vindev->config->regs; + + u32 status = mgb4_read_reg(video, regs->status); + u32 pclk = mgb4_read_reg(video, regs->pclk); + u32 signal = mgb4_read_reg(video, regs->signal); + u32 signal2 = mgb4_read_reg(video, regs->signal2); + u32 resolution = mgb4_read_reg(video, regs->resolution); + + if (!(status & (1U << 2))) + return -ENOLCK; + if (!(status & (3 << 9))) + return -ENOLINK; + + memset(timings, 0, sizeof(*timings)); + timings->type = V4L2_DV_BT_656_1120; + timings->bt.width = resolution >> 16; + timings->bt.height = resolution & 0xFFFF; + if (status & (1U << 12)) + timings->bt.polarities |= V4L2_DV_HSYNC_POS_POL; + if (status & (1U << 13)) + timings->bt.polarities |= V4L2_DV_VSYNC_POS_POL; + timings->bt.pixelclock = pclk * 1000; + timings->bt.hsync = (signal & 0x00FF0000) >> 16; + timings->bt.vsync = (signal2 & 0x00FF0000) >> 16; + timings->bt.hbackporch = (signal & 0x0000FF00) >> 8; + timings->bt.hfrontporch = signal & 0x000000FF; + timings->bt.vbackporch = (signal2 & 0x0000FF00) >> 8; + timings->bt.vfrontporch = signal2 & 0x000000FF; + + return 0; +} + +static void return_all_buffers(struct mgb4_vin_dev *vindev, + enum vb2_buffer_state state) +{ + struct mgb4_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 = vb2_get_drv_priv(q); + unsigned int size = (vindev->timings.bt.width + vindev->padding) + * vindev->timings.bt.height * 4; + + /* + * If I/O reconfiguration is in process, do not allow to start + * the queue. See video_source_store() in mgb4_sysfs_out.c for + * details. + */ + if (test_bit(0, &vindev->mgbdev->io_reconfig)) + return -EBUSY; + + if (!size) + return -EINVAL; + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int buffer_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mgb4_frame_buffer *buf = to_frame_buffer(vbuf); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vb->vb2_queue); + struct device *dev = &vindev->mgbdev->pdev->dev; + unsigned int size = (vindev->timings.bt.width + vindev->padding) + * vindev->timings.bt.height * 4; + + 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 = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mgb4_frame_buffer *buf = 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 = vb2_get_drv_priv(vq); + const struct mgb4_vin_config *config = vindev->config; + int irq = xdma_get_user_irq(vindev->mgbdev->xdev, config->vin_irq); + + xdma_disable_user_irq(vindev->mgbdev->xdev, irq); + + /* + * In loopback mode, the HW frame queue must be left running for + * the IN->OUT transmission to work! + */ + if (!loopback_active(vindev)) + mgb4_mask_reg(&vindev->mgbdev->video, 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 = vb2_get_drv_priv(vq); + const struct mgb4_vin_config *config = vindev->config; + int irq = xdma_get_user_irq(vindev->mgbdev->xdev, config->vin_irq); + + vindev->sequence = 0; + + /* + * In loopback mode, the HW frame queue is already running. + */ + if (!loopback_active(vindev)) + mgb4_mask_reg(&vindev->mgbdev->video, config->regs.config, 0x2, + 0x2); + + xdma_enable_user_irq(vindev->mgbdev->xdev, irq); + + return 0; +} + +static const struct vb2_ops queue_ops = { + .queue_setup = queue_setup, + .buf_init = buffer_init, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish +}; + +static int fh_open(struct file *file) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + int rv; + + mutex_lock(&vindev->lock); + + rv = v4l2_fh_open(file); + if (rv) + goto out; + + if (!v4l2_fh_is_singular_file(file)) + goto out; + + get_timings(vindev, &vindev->timings); + set_loopback_padding(vindev, vindev->padding); + +out: + mutex_unlock(&vindev->lock); + return rv; +} + +static int fh_release(struct file *file) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + int rv; + + mutex_lock(&vindev->lock); + + if (v4l2_fh_is_singular_file(file)) + set_loopback_padding(vindev, 0); + + rv = _vb2_fop_release(file, NULL); + + mutex_unlock(&vindev->lock); + + return rv; +} + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = fh_open, + .release = fh_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "MGB4 PCIe Card", sizeof(cap->card)); + + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + f->pixelformat = 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 = video_drvdata(file); + + if (ival->index != 0) + return -EINVAL; + if (ival->pixel_format != V4L2_PIX_FMT_ABGR32) + return -EINVAL; + if (ival->width != vindev->timings.bt.width || + ival->height != vindev->timings.bt.height) + return -EINVAL; + + ival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + ival->stepwise.min.denominator = 60; + ival->stepwise.min.numerator = 1; + ival->stepwise.max.denominator = 1; + ival->stepwise.max.numerator = 1; + ival->stepwise.step = ival->stepwise.max; + + return 0; +} + +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32; + f->fmt.pix.width = vindev->timings.bt.width; + f->fmt.pix.height = vindev->timings.bt.height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 4; + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32; + f->fmt.pix.width = vindev->timings.bt.width; + f->fmt.pix.height = vindev->timings.bt.height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.bytesperline = max(f->fmt.pix.width * 4, + ALIGN_DOWN(f->fmt.pix.bytesperline, 4)); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + + if (vb2_is_busy(&vindev->queue)) + return -EBUSY; + + vidioc_try_fmt(file, priv, f); + + vindev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width * 4)) / 4; + mgb4_write_reg(video, vindev->config->regs.padding, vindev->padding); + set_loopback_padding(vindev, vindev->padding); + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + u32 status; + + if (i->index != 0) + return -EINVAL; + + strscpy(i->name, "MGB4", sizeof(i->name)); + i->type = V4L2_INPUT_TYPE_CAMERA; + i->capabilities = V4L2_IN_CAP_DV_TIMINGS; + i->status = 0; + + status = mgb4_read_reg(video, vindev->config->regs.status); + if (!(status & (1U << 2))) + i->status |= V4L2_IN_ST_NO_SYNC; + if (!(status & (3 << 9))) + i->status |= V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + if (fsize->index != 0 || fsize->pixel_format != V4L2_PIX_FMT_ABGR32) + return -EINVAL; + + fsize->discrete.width = vindev->timings.bt.width; + fsize->discrete.height = vindev->timings.bt.height; + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return (i == 0) ? 0 : -EINVAL; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int vidioc_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_regs *regs = &vindev->config->regs; + struct v4l2_fract timeperframe = { + .numerator = mgb4_read_reg(video, regs->frame_period), + .denominator = 125000000, + }; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + parm->parm.capture.readbuffers = 2; + parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parm->parm.capture.timeperframe = timeperframe; + + return 0; +} + +static int vidioc_s_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + if (timings->bt.width < video_timings_cap.bt.min_width || + timings->bt.width > video_timings_cap.bt.max_width || + timings->bt.height < video_timings_cap.bt.min_height || + timings->bt.height > video_timings_cap.bt.max_height) + return -EINVAL; + if (timings->bt.width == vindev->timings.bt.width && + timings->bt.height == vindev->timings.bt.height) + return 0; + if (vb2_is_busy(&vindev->queue)) + return -EBUSY; + + vindev->timings = *timings; + + return 0; +} + +static int vidioc_g_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + *timings = vindev->timings; + + return 0; +} + +static int vidioc_query_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + return get_timings(vindev, timings); +} + +static int vidioc_enum_dv_timings(struct file *file, void *fh, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &video_timings_cap, NULL, NULL); +} + +static int vidioc_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap) +{ + *cap = video_timings_cap; + + return 0; +} + +static int vidioc_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + } + + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_parm = vidioc_parm, + .vidioc_s_parm = vidioc_parm, + .vidioc_dv_timings_cap = vidioc_dv_timings_cap, + .vidioc_enum_dv_timings = vidioc_enum_dv_timings, + .vidioc_g_dv_timings = vidioc_g_dv_timings, + .vidioc_s_dv_timings = vidioc_s_dv_timings, + .vidioc_query_dv_timings = vidioc_query_dv_timings, + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void dma_transfer(struct work_struct *work) +{ + struct mgb4_vin_dev *vindev = container_of(work, struct mgb4_vin_dev, + dma_work); + struct mgb4_regs *video = &vindev->mgbdev->video; + struct device *dev = &vindev->mgbdev->pdev->dev; + struct mgb4_frame_buffer *buf = NULL; + unsigned long flags; + u32 addr; + int rv; + + spin_lock_irqsave(&vindev->qlock, flags); + if (!list_empty(&vindev->buf_list)) { + buf = list_first_entry(&vindev->buf_list, + struct mgb4_frame_buffer, list); + list_del_init(vindev->buf_list.next); + } + spin_unlock_irqrestore(&vindev->qlock, flags); + + if (!buf) + return; + + addr = mgb4_read_reg(video, vindev->config->regs.address); + if (addr >= MGB4_ERR_QUEUE_FULL) { + dev_dbg(dev, "frame queue error (%d)\n", (int)addr); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + rv = mgb4_dma_transfer(vindev->mgbdev, vindev->config->dma_channel, + false, addr, + vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0)); + if (rv < 0) { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } else { + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = vindev->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } +} + +static void signal_change(struct work_struct *work) +{ + struct mgb4_vin_dev *vindev = container_of(work, struct mgb4_vin_dev, + err_work); + struct mgb4_regs *video = &vindev->mgbdev->video; + struct v4l2_bt_timings *timings = &vindev->timings.bt; + struct device *dev = &vindev->mgbdev->pdev->dev; + + u32 resolution = mgb4_read_reg(video, vindev->config->regs.resolution); + u32 width = resolution >> 16; + u32 height = resolution & 0xFFFF; + + if (timings->width != width || timings->height != height) { + static const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_event_queue(&vindev->vdev, &ev); + + if (vb2_is_streaming(&vindev->queue)) + vb2_queue_error(&vindev->queue); + } + + dev_dbg(dev, "stream changed to %ux%u\n", width, height); +} + +static irqreturn_t vin_handler(int irq, void *ctx) +{ + struct mgb4_vin_dev *vindev = (struct mgb4_vin_dev *)ctx; + struct mgb4_regs *video = &vindev->mgbdev->video; + + schedule_work(&vindev->dma_work); + + mgb4_write_reg(video, 0xB4, 1U << vindev->config->vin_irq); + + return IRQ_HANDLED; +} + +static irqreturn_t err_handler(int irq, void *ctx) +{ + struct mgb4_vin_dev *vindev = (struct mgb4_vin_dev *)ctx; + struct mgb4_regs *video = &vindev->mgbdev->video; + + schedule_work(&vindev->err_work); + + mgb4_write_reg(video, 0xB4, 1U << vindev->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 = &vindev->mgbdev->pdev->dev; + + if (MGB4_IS_GMSL(vindev->mgbdev)) { + info = &gmsl_deser_info[id]; + addr_size = 16; + values = gmsl_i2c; + values_count = ARRAY_SIZE(gmsl_i2c); + } else { + info = &fpdl3_deser_info[id]; + addr_size = 8; + values = fpdl3_i2c; + values_count = ARRAY_SIZE(fpdl3_i2c); + } + + rv = 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 = 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; +} + +static void fpga_init(struct mgb4_vin_dev *vindev) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_regs *regs = &vindev->config->regs; + + mgb4_write_reg(video, regs->config, 0x00000001); + mgb4_write_reg(video, regs->sync, 0x03E80002); + mgb4_write_reg(video, regs->padding, 0x00000000); + mgb4_write_reg(video, regs->config, 1U << 9); +} + +#ifdef CONFIG_DEBUG_FS +static void debugfs_init(struct mgb4_vin_dev *vindev) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + + vindev->debugfs = debugfs_create_dir(vindev->vdev.name, + vindev->mgbdev->debugfs); + if (!vindev->debugfs) + return; + + vindev->regs[0].name = "CONFIG"; + vindev->regs[0].offset = vindev->config->regs.config; + vindev->regs[1].name = "STATUS"; + vindev->regs[1].offset = vindev->config->regs.status; + vindev->regs[2].name = "RESOLUTION"; + vindev->regs[2].offset = vindev->config->regs.resolution; + vindev->regs[3].name = "FRAME_PERIOD"; + vindev->regs[3].offset = vindev->config->regs.frame_period; + vindev->regs[4].name = "HS_VS_GENER_SETTINGS"; + vindev->regs[4].offset = vindev->config->regs.sync; + vindev->regs[5].name = "PCLK_FREQUENCY"; + vindev->regs[5].offset = vindev->config->regs.pclk; + vindev->regs[6].name = "VIDEO_PARAMS_1"; + vindev->regs[6].offset = vindev->config->regs.signal; + vindev->regs[7].name = "VIDEO_PARAMS_2"; + vindev->regs[7].offset = vindev->config->regs.signal2; + vindev->regs[8].name = "PADDING_PIXELS"; + vindev->regs[8].offset = vindev->config->regs.padding; + + vindev->regset.base = video->membase; + vindev->regset.regs = vindev->regs; + vindev->regset.nregs = ARRAY_SIZE(vindev->regs); + + debugfs_create_regset32("registers", 0444, vindev->debugfs, + &vindev->regset); +} +#endif + +struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id) +{ + int rv; + const struct attribute_group **groups; + struct mgb4_vin_dev *vindev; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + int vin_irq, err_irq; + + vindev = kzalloc(sizeof(*vindev), GFP_KERNEL); + if (!vindev) + return NULL; + + vindev->mgbdev = mgbdev; + vindev->config = &vin_cfg[id]; + + /* Frame queue*/ + INIT_LIST_HEAD(&vindev->buf_list); + spin_lock_init(&vindev->qlock); + + /* Work queues */ + INIT_WORK(&vindev->dma_work, dma_transfer); + INIT_WORK(&vindev->err_work, signal_change); + + /* IRQ callback */ + vin_irq = xdma_get_user_irq(mgbdev->xdev, vindev->config->vin_irq); + rv = request_irq(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 */ + err_irq = xdma_get_user_irq(mgbdev->xdev, vindev->config->err_irq); + rv = request_irq(err_irq, err_handler, 0, "mgb4-err", vindev); + if (rv) { + dev_err(dev, "failed to register err irq handler\n"); + goto err_vin_irq; + } + + /* Set the FPGA registers default values */ + fpga_init(vindev); + + /* Set the deserializer default values */ + rv = deser_init(vindev, id); + if (rv) + goto err_err_irq; + + /* V4L2 stuff init */ + rv = 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 = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vindev->queue.io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + vindev->queue.buf_struct_size = sizeof(struct mgb4_frame_buffer); + vindev->queue.ops = &queue_ops; + vindev->queue.mem_ops = &vb2_dma_sg_memops; + vindev->queue.gfp_flags = GFP_DMA32; + vindev->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vindev->queue.min_buffers_needed = 2; + vindev->queue.drv_priv = vindev; + vindev->queue.lock = &vindev->lock; + vindev->queue.dev = dev; + rv = 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 = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE + | V4L2_CAP_STREAMING; + vindev->vdev.fops = &video_fops; + vindev->vdev.ioctl_ops = &video_ioctl_ops; + vindev->vdev.release = video_device_release_empty; + vindev->vdev.v4l2_dev = &vindev->v4l2dev; + vindev->vdev.lock = &vindev->lock; + vindev->vdev.queue = &vindev->queue; + video_set_drvdata(&vindev->vdev, vindev); + + /* Enable the video signal change watcher */ + xdma_enable_user_irq(vindev->mgbdev->xdev, err_irq); + + /* Register the video device */ + rv = video_register_device(&vindev->vdev, VFL_TYPE_VIDEO, -1); + if (rv) { + dev_err(dev, "failed to register video device\n"); + goto err_v4l2_dev; + } + + /* Module sysfs attributes */ + groups = MGB4_IS_GMSL(mgbdev) + ? mgb4_gmsl_in_groups : mgb4_fpdl3_in_groups; + rv = device_add_groups(&vindev->vdev.dev, groups); + if (rv) { + dev_err(dev, "failed to create sysfs attributes\n"); + goto err_video_dev; + } + +#ifdef CONFIG_DEBUG_FS + debugfs_init(vindev); +#endif + + return vindev; + +err_video_dev: + video_unregister_device(&vindev->vdev); +err_v4l2_dev: + v4l2_device_unregister(&vindev->v4l2dev); +err_err_irq: + free_irq(err_irq, vindev); +err_vin_irq: + free_irq(vin_irq, vindev); +err_alloc: + kfree(vindev); + + return NULL; +} + +void mgb4_vin_free(struct mgb4_vin_dev *vindev) +{ + const struct attribute_group **groups; + int vin_irq = xdma_get_user_irq(vindev->mgbdev->xdev, + vindev->config->vin_irq); + int err_irq = xdma_get_user_irq(vindev->mgbdev->xdev, + vindev->config->err_irq); + + xdma_disable_user_irq(vindev->mgbdev->xdev, err_irq); + + free_irq(vin_irq, vindev); + free_irq(err_irq, vindev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(vindev->debugfs); +#endif + + groups = MGB4_IS_GMSL(vindev->mgbdev) + ? mgb4_gmsl_in_groups : mgb4_fpdl3_in_groups; + device_remove_groups(&vindev->vdev.dev, groups); + + mgb4_i2c_free(&vindev->deser); + video_unregister_device(&vindev->vdev); + v4l2_device_unregister(&vindev->v4l2dev); + + kfree(vindev); +} diff --git a/drivers/media/pci/mgb4/mgb4_vin.h b/drivers/media/pci/mgb4/mgb4_vin.h new file mode 100644 index 000000000000..0249b400ad4d --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vin.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_VIN_H__ +#define __MGB4_VIN_H__ + +#include +#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; /* vdev lock */ + + spinlock_t qlock; /* video buffer queue lock */ + struct list_head buf_list; + struct work_struct dma_work, err_work; + + unsigned int sequence; + + struct v4l2_dv_timings timings; + u32 freq_range; + u32 padding; + + struct mgb4_i2c_client deser; + + const struct mgb4_vin_config *config; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; + struct debugfs_regset32 regset; + struct debugfs_reg32 regs[9]; +#endif +}; + +struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id); +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/mgb4_vout.c new file mode 100644 index 000000000000..857fc7bbd21a --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vout.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This is the v4l2 output device module. It initializes the signal serializers + * and creates the v4l2 video devices. + * + * When the device is in loopback mode (a direct, in HW, in->out frame passing + * mode) we disable the v4l2 output by returning EBUSY in the open() syscall. + */ + +#include +#include +#include +#include +#include +#include +#include "mgb4_core.h" +#include "mgb4_dma.h" +#include "mgb4_sysfs.h" +#include "mgb4_io.h" +#include "mgb4_cmt.h" +#include "mgb4_vout.h" + +ATTRIBUTE_GROUPS(mgb4_fpdl3_out); +ATTRIBUTE_GROUPS(mgb4_gmsl_out); + +static const struct mgb4_vout_config vout_cfg[] = { + {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[] = { + {I2C_BOARD_INFO("serializer1", 0x14)}, + {I2C_BOARD_INFO("serializer2", 0x16)}, +}; + +static const struct mgb4_i2c_kv fpdl3_i2c[] = { + {0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80} +}; + +static void return_all_buffers(struct mgb4_vout_dev *voutdev, + enum vb2_buffer_state state) +{ + struct mgb4_frame_buffer *buf, *node; + unsigned long flags; + + spin_lock_irqsave(&voutdev->qlock, flags); + list_for_each_entry_safe(buf, node, &voutdev->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + spin_unlock_irqrestore(&voutdev->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_vout_dev *voutdev = vb2_get_drv_priv(q); + unsigned int size; + + /* + * If I/O reconfiguration is in process, do not allow to start + * the queue. See video_source_store() in mgb4_sysfs_out.c for + * details. + */ + if (test_bit(0, &voutdev->mgbdev->io_reconfig)) + return -EBUSY; + + size = (voutdev->width + voutdev->padding) * voutdev->height * 4; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int buffer_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mgb4_frame_buffer *buf = to_frame_buffer(vbuf); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vb->vb2_queue); + struct device *dev = &voutdev->mgbdev->pdev->dev; + unsigned int size; + + size = (voutdev->width + voutdev->padding) * voutdev->height * 4; + + 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_vout_dev *vindev = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mgb4_frame_buffer *buf = 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 = vb2_get_drv_priv(vq); + struct mgb4_dev *mgbdev = voutdev->mgbdev; + int irq = xdma_get_user_irq(mgbdev->xdev, voutdev->config->irq); + + xdma_disable_user_irq(mgbdev->xdev, irq); + cancel_work_sync(&voutdev->dma_work); + mgb4_mask_reg(&mgbdev->video, voutdev->config->regs.config, 0x2, 0x0); + return_all_buffers(voutdev, VB2_BUF_STATE_ERROR); +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vq); + struct mgb4_dev *mgbdev = voutdev->mgbdev; + struct device *dev = &mgbdev->pdev->dev; + struct mgb4_frame_buffer *buf; + struct mgb4_regs *video = &mgbdev->video; + const struct mgb4_vout_config *config = voutdev->config; + int irq = xdma_get_user_irq(mgbdev->xdev, config->irq); + int rv; + u32 addr; + + mgb4_mask_reg(video, config->regs.config, 0x2, 0x2); + + addr = mgb4_read_reg(video, config->regs.address); + if (addr >= MGB4_ERR_QUEUE_FULL) { + dev_dbg(dev, "frame queue error (%d)\n", (int)addr); + return_all_buffers(voutdev, VB2_BUF_STATE_QUEUED); + return -EBUSY; + } + + buf = list_first_entry(&voutdev->buf_list, struct mgb4_frame_buffer, + list); + list_del_init(voutdev->buf_list.next); + + rv = mgb4_dma_transfer(mgbdev, config->dma_channel, true, addr, + vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0)); + if (rv < 0) { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } else { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + + xdma_enable_user_irq(mgbdev->xdev, irq); + + return 0; +} + +static const struct vb2_ops queue_ops = { + .queue_setup = queue_setup, + .buf_init = buffer_init, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish +}; + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "MGB4 PCIe Card", sizeof(cap->card)); + + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_ABGR32; + + return 0; +} + +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32; + f->fmt.pix.width = voutdev->width; + f->fmt.pix.height = voutdev->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 4; + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32; + f->fmt.pix.width = voutdev->width; + f->fmt.pix.height = voutdev->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.bytesperline = max(f->fmt.pix.width * 4, + ALIGN_DOWN(f->fmt.pix.bytesperline, 4)); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + + if (vb2_is_busy(&voutdev->queue)) + return -EBUSY; + + vidioc_try_fmt(file, priv, f); + + voutdev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width * 4)) / 4; + mgb4_write_reg(video, voutdev->config->regs.padding, voutdev->padding); + + return 0; +} + +static int vidioc_g_output(struct file *file, void *priv, unsigned int *i) +{ + *i = 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_output *out) +{ + if (out->index != 0) + return -EINVAL; + + out->type = V4L2_OUTPUT_TYPE_ANALOG; + strscpy(out->name, "MGB4", sizeof(out->name)); + + return 0; +} + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt, + .vidioc_try_fmt_vid_out = vidioc_try_fmt, + .vidioc_s_fmt_vid_out = vidioc_s_fmt, + .vidioc_g_fmt_vid_out = vidioc_g_fmt, + .vidioc_enum_output = vidioc_enum_output, + .vidioc_g_output = vidioc_g_output, + .vidioc_s_output = vidioc_s_output, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static int fh_open(struct file *file) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + struct device *dev = &voutdev->mgbdev->pdev->dev; + u32 config, resolution; + int rv; + + /* Return EBUSY when the device is in loopback mode */ + config = mgb4_read_reg(video, voutdev->config->regs.config); + if ((config & 0xc) >> 2 != voutdev->config->id + MGB4_VIN_DEVICES) { + dev_dbg(dev, "can not open - device in loopback mode"); + return -EBUSY; + } + + mutex_lock(&voutdev->lock); + + rv = v4l2_fh_open(file); + if (rv) + goto out; + + if (!v4l2_fh_is_singular_file(file)) + goto out; + + resolution = mgb4_read_reg(video, voutdev->config->regs.resolution); + voutdev->width = resolution >> 16; + voutdev->height = resolution & 0xFFFF; + +out: + mutex_unlock(&voutdev->lock); + return rv; +} + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .write = vb2_fop_write, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static void dma_transfer(struct work_struct *work) +{ + struct mgb4_vout_dev *voutdev = container_of(work, struct mgb4_vout_dev, + dma_work); + struct device *dev = &voutdev->mgbdev->pdev->dev; + struct mgb4_regs *video = &voutdev->mgbdev->video; + struct mgb4_frame_buffer *buf = NULL; + unsigned long flags; + u32 addr; + int rv; + + spin_lock_irqsave(&voutdev->qlock, flags); + if (!list_empty(&voutdev->buf_list)) { + buf = list_first_entry(&voutdev->buf_list, + struct mgb4_frame_buffer, list); + list_del_init(voutdev->buf_list.next); + } + spin_unlock_irqrestore(&voutdev->qlock, flags); + + if (!buf) + return; + + addr = mgb4_read_reg(video, voutdev->config->regs.address); + if (addr >= MGB4_ERR_QUEUE_FULL) { + dev_dbg(dev, "frame queue error (%d)\n", (int)addr); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + rv = mgb4_dma_transfer(voutdev->mgbdev, voutdev->config->dma_channel, + true, addr, + vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0)); + if (rv < 0) { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } else { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } +} + +static irqreturn_t handler(int irq, void *ctx) +{ + struct mgb4_vout_dev *voutdev = (struct mgb4_vout_dev *)ctx; + struct mgb4_regs *video = &voutdev->mgbdev->video; + + schedule_work(&voutdev->dma_work); + + mgb4_write_reg(video, 0xB4, 1U << voutdev->config->irq); + + return IRQ_HANDLED; +} + +static int ser_init(struct mgb4_vout_dev *voutdev, int id) +{ + int rv; + const struct i2c_board_info *info = &fpdl3_ser_info[id]; + struct mgb4_i2c_client *ser = &voutdev->ser; + struct device *dev = &voutdev->mgbdev->pdev->dev; + + if (MGB4_IS_GMSL(voutdev->mgbdev)) + return 0; + + rv = mgb4_i2c_init(ser, voutdev->mgbdev->i2c_adap, info, 8); + if (rv < 0) { + dev_err(dev, "failed to create serializer\n"); + return rv; + } + rv = mgb4_i2c_configure(ser, fpdl3_i2c, ARRAY_SIZE(fpdl3_i2c)); + if (rv < 0) { + dev_err(dev, "failed to configure serializer\n"); + goto err_i2c_dev; + } + + return 0; + +err_i2c_dev: + mgb4_i2c_free(ser); + + return rv; +} + +static void fpga_init(struct mgb4_vout_dev *voutdev) +{ + struct mgb4_regs *video = &voutdev->mgbdev->video; + const struct mgb4_vout_regs *regs = &voutdev->config->regs; + + mgb4_write_reg(video, regs->config, 0x00000011); + mgb4_write_reg(video, regs->resolution, + (MGB4_DEFAULT_WIDTH << 16) | MGB4_DEFAULT_HEIGHT); + mgb4_write_reg(video, regs->hsync, 0x00102020); + mgb4_write_reg(video, regs->vsync, 0x40020202); + mgb4_write_reg(video, regs->frame_period, MGB4_DEFAULT_PERIOD); + mgb4_write_reg(video, regs->padding, 0x00000000); + + voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 70000 >> 1) << 1; + + mgb4_write_reg(video, regs->config, + (voutdev->config->id + MGB4_VIN_DEVICES) << 2 | 1 << 4); +} + +#ifdef CONFIG_DEBUG_FS +static void debugfs_init(struct mgb4_vout_dev *voutdev) +{ + struct mgb4_regs *video = &voutdev->mgbdev->video; + + voutdev->debugfs = debugfs_create_dir(voutdev->vdev.name, + voutdev->mgbdev->debugfs); + if (!voutdev->debugfs) + return; + + voutdev->regs[0].name = "CONFIG"; + voutdev->regs[0].offset = voutdev->config->regs.config; + voutdev->regs[1].name = "STATUS"; + voutdev->regs[1].offset = voutdev->config->regs.status; + voutdev->regs[2].name = "RESOLUTION"; + voutdev->regs[2].offset = voutdev->config->regs.resolution; + voutdev->regs[3].name = "VIDEO_PARAMS_1"; + voutdev->regs[3].offset = voutdev->config->regs.hsync; + voutdev->regs[4].name = "VIDEO_PARAMS_2"; + voutdev->regs[4].offset = voutdev->config->regs.vsync; + voutdev->regs[5].name = "FRAME_PERIOD"; + voutdev->regs[5].offset = voutdev->config->regs.frame_period; + voutdev->regs[6].name = "PADDING"; + voutdev->regs[6].offset = voutdev->config->regs.padding; + + voutdev->regset.base = video->membase; + voutdev->regset.regs = voutdev->regs; + voutdev->regset.nregs = ARRAY_SIZE(voutdev->regs); + + debugfs_create_regset32("registers", 0444, voutdev->debugfs, + &voutdev->regset); +} +#endif + +struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id) +{ + int rv, irq; + const struct attribute_group **groups; + struct mgb4_vout_dev *voutdev; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + + voutdev = kzalloc(sizeof(*voutdev), GFP_KERNEL); + if (!voutdev) + return NULL; + + voutdev->mgbdev = mgbdev; + voutdev->config = &vout_cfg[id]; + + /* 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 */ + irq = xdma_get_user_irq(mgbdev->xdev, voutdev->config->irq); + rv = request_irq(irq, handler, 0, "mgb4-vout", voutdev); + if (rv) { + dev_err(dev, "failed to register irq handler\n"); + goto err_alloc; + } + + /* Set the FPGA registers default values */ + fpga_init(voutdev); + + /* Set the serializer default values */ + rv = ser_init(voutdev, id); + if (rv) + goto err_irq; + + /* V4L2 stuff init */ + rv = 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 = V4L2_BUF_TYPE_VIDEO_OUTPUT; + voutdev->queue.io_modes = VB2_MMAP | VB2_DMABUF | VB2_WRITE; + voutdev->queue.buf_struct_size = sizeof(struct mgb4_frame_buffer); + voutdev->queue.ops = &queue_ops; + voutdev->queue.mem_ops = &vb2_dma_sg_memops; + voutdev->queue.gfp_flags = GFP_DMA32; + voutdev->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + voutdev->queue.min_buffers_needed = 2; + voutdev->queue.drv_priv = voutdev; + voutdev->queue.lock = &voutdev->lock; + voutdev->queue.dev = dev; + rv = 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 = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE + | V4L2_CAP_STREAMING; + voutdev->vdev.vfl_dir = VFL_DIR_TX; + voutdev->vdev.fops = &video_fops; + voutdev->vdev.ioctl_ops = &video_ioctl_ops; + voutdev->vdev.release = video_device_release_empty; + voutdev->vdev.v4l2_dev = &voutdev->v4l2dev; + voutdev->vdev.lock = &voutdev->lock; + voutdev->vdev.queue = &voutdev->queue; + video_set_drvdata(&voutdev->vdev, voutdev); + + rv = video_register_device(&voutdev->vdev, VFL_TYPE_VIDEO, -1); + if (rv) { + dev_err(dev, "failed to register video device\n"); + goto err_v4l2_dev; + } + + /* Module sysfs attributes */ + groups = MGB4_IS_GMSL(mgbdev) + ? mgb4_gmsl_out_groups : mgb4_fpdl3_out_groups; + rv = device_add_groups(&voutdev->vdev.dev, groups); + if (rv) { + dev_err(dev, "failed to create sysfs attributes\n"); + goto err_video_dev; + } + +#ifdef CONFIG_DEBUG_FS + debugfs_init(voutdev); +#endif + + return voutdev; + +err_video_dev: + video_unregister_device(&voutdev->vdev); +err_v4l2_dev: + v4l2_device_unregister(&voutdev->v4l2dev); +err_irq: + free_irq(irq, voutdev); +err_alloc: + kfree(voutdev); + + return NULL; +} + +void mgb4_vout_free(struct mgb4_vout_dev *voutdev) +{ + const struct attribute_group **groups; + int irq = xdma_get_user_irq(voutdev->mgbdev->xdev, voutdev->config->irq); + + free_irq(irq, voutdev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(voutdev->debugfs); +#endif + + groups = MGB4_IS_GMSL(voutdev->mgbdev) + ? mgb4_gmsl_out_groups : mgb4_fpdl3_out_groups; + device_remove_groups(&voutdev->vdev.dev, groups); + + mgb4_i2c_free(&voutdev->ser); + video_unregister_device(&voutdev->vdev); + v4l2_device_unregister(&voutdev->v4l2dev); + + kfree(voutdev); +} diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h new file mode 100644 index 000000000000..b163dee711fd --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vout.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_VOUT_H__ +#define __MGB4_VOUT_H__ + +#include +#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; /* vdev lock */ + + spinlock_t qlock; /* buffer queue lock */ + struct list_head buf_list; + struct work_struct dma_work; + + u32 width; + u32 height; + u32 freq; + u32 padding; + + struct mgb4_i2c_client ser; + + const struct mgb4_vout_config *config; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; + struct debugfs_regset32 regset; + struct debugfs_reg32 regs[7]; +#endif +}; + +struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id); +void mgb4_vout_free(struct mgb4_vout_dev *voutdev); + +#endif -- cgit v1.2.3 From 0308483f6674d5d4e31deb8892d393b0aecd1ed3 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 19 Sep 2023 14:04:47 +0300 Subject: media: ov9282: Assign maintenance to Dave The current maintainers won't be looking after this driver anymore. Dave offered to take over the driver, assign maintenance to him. Signed-off-by: Sakari Ailus Acked-by: Dave Stevenson Signed-off-by: Hans Verkuil --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index bba09f9d0141..bcbc7c2b9c3f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15881,8 +15881,7 @@ F: Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml F: drivers/media/i2c/ov8858.c OMNIVISION OV9282 SENSOR DRIVER -M: Paul J. Murphy -M: Daniele Alessandrelli +M: Dave Stevenson L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git -- cgit v1.2.3 From 59ac78dcd17ccfe362229d5538ac9c1d00456e15 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 19 Sep 2023 14:06:06 +0300 Subject: media: imx412: Orphan the driver The current maintainers won't be looking after this driver anymore. Mark it orphan. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index bcbc7c2b9c3f..a9d4f734702b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20045,10 +20045,8 @@ T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/imx355.c SONY IMX412 SENSOR DRIVER -M: Paul J. Murphy -M: Daniele Alessandrelli L: linux-media@vger.kernel.org -S: Maintained +S: Orphan T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/sony,imx412.yaml F: drivers/media/i2c/imx412.c -- cgit v1.2.3 From 5b0e91fd477d0fbfac6989cbf26071c28f21bfe4 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 19 Sep 2023 14:09:32 +0300 Subject: media: imx335: Orphan the driver The current maintainers won't be looking after this driver anymore. Mark it orphan. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index a9d4f734702b..85c817eae9ae 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20029,10 +20029,8 @@ F: Documentation/devicetree/bindings/media/i2c/sony,imx334.yaml F: drivers/media/i2c/imx334.c SONY IMX335 SENSOR DRIVER -M: Paul J. Murphy -M: Daniele Alessandrelli L: linux-media@vger.kernel.org -S: Maintained +S: Orphan T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/sony,imx335.yaml F: drivers/media/i2c/imx335.c -- cgit v1.2.3 From 17c74cb89706654713bfa817820b2b981771c104 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 19 Sep 2023 14:09:57 +0300 Subject: media: imx334: Orphan the driver The current maintainers won't be looking after this driver anymore. Mark it orphan. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 85c817eae9ae..985670c74cf0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20020,10 +20020,8 @@ T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/imx319.c SONY IMX334 SENSOR DRIVER -M: Paul J. Murphy -M: Daniele Alessandrelli L: linux-media@vger.kernel.org -S: Maintained +S: Orphan T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/sony,imx334.yaml F: drivers/media/i2c/imx334.c -- cgit v1.2.3 From e00d0d9e74fe2b1dccea3b8ee1c9593dba8f6117 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 20 Sep 2023 20:11:53 +0300 Subject: media: dt-bindings: media: i2c: Add MT9M114 camera sensor binding Add device tree binding for the onsemi MT9M114 CMOS camera sensor. Signed-off-by: Laurent Pinchart Reviewed-by: Rob Herring Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/onnn,mt9m114.yaml | 114 +++++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml b/Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml new file mode 100644 index 000000000000..f6b87892068a --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/onnn,mt9m114.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: onsemi 1/6-inch 720p CMOS Digital Image Sensor + +maintainers: + - Laurent Pinchart + +description: |- + The onsemi MT9M114 is a 1/6-inch 720p (1.26 Mp) CMOS digital image sensor + with an active pixel-array size of 1296H x 976V. It is programmable through + an I2C interface and outputs image data over a 8-bit parallel or 1-lane MIPI + CSI-2 connection. + +properties: + compatible: + const: onnn,mt9m114 + + reg: + description: I2C device address + enum: + - 0x48 + - 0x5d + + clocks: + description: EXTCLK clock signal + maxItems: 1 + + vdd-supply: + description: + Core digital voltage supply, 1.8V + + vddio-supply: + description: + I/O digital voltage supply, 1.8V or 2.8V + + vaa-supply: + description: + Analog voltage supply, 2.8V + + reset-gpios: + description: |- + Reference to the GPIO connected to the RESET_BAR pin, if any (active + low). + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + additionalProperties: false + + properties: + bus-type: + enum: [4, 5, 6] + + link-frequencies: true + remote-endpoint: true + + # The number and mapping of lanes (for CSI-2), and the bus width and + # signal polarities (for parallel and BT.656) are fixed and must not + # be specified. + + required: + - bus-type + - link-frequencies + +required: + - compatible + - reg + - clocks + - vdd-supply + - vddio-supply + - vaa-supply + - port + +additionalProperties: false + +examples: + - | + #include + #include + + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + sensor@48 { + compatible = "onnn,mt9m114"; + reg = <0x48>; + + clocks = <&clk24m 0>; + + reset-gpios = <&gpio5 21 GPIO_ACTIVE_LOW>; + + vddio-supply = <®_cam_1v8>; + vdd-supply = <®_cam_1v8>; + vaa-supply = <®_2p8v>; + + port { + endpoint { + bus-type = ; + link-frequencies = /bits/ 64 <384000000>; + remote-endpoint = <&mipi_csi_in>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 985670c74cf0..c55e13029e2d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14558,6 +14558,13 @@ L: linux-mtd@lists.infradead.org S: Maintained F: drivers/mtd/devices/docg3* +MT9M114 ONSEMI SENSOR DRIVER +M: Laurent Pinchart +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml + MT9P031 APTINA CAMERA SENSOR M: Laurent Pinchart L: linux-media@vger.kernel.org -- cgit v1.2.3 From 24d756e914fc3418bad7897b0657aefa9ef848e8 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 20 Sep 2023 20:11:54 +0300 Subject: media: i2c: Add driver for onsemi MT9M114 camera sensor The MT9M114 is a CMOS camera sensor that combines a 1296x976 pixel array with a 10-bit dynamic range together with an internal ISP. The driver exposes two subdevs, one for the pixel array and one for the ISP (named IFP for Image Flow Processor). Major supported features are - Full configuration of analog crop and binning in the pixel array - Full configuration of scaling in the ISP - Automatic exposure and white balance - Manual exposure and analog gain - Horizontal and vertical flip Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/mt9m114.c | 2481 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2493 insertions(+) create mode 100644 drivers/media/i2c/mt9m114.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c55e13029e2d..83a0c7f3826b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14564,6 +14564,7 @@ L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml +F: drivers/media/i2c/mt9m114.c MT9P031 APTINA CAMERA SENSOR M: Laurent Pinchart diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 74ff833ff48c..897d02f0b552 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -215,6 +215,16 @@ config VIDEO_MT9M111 This driver supports MT9M111, MT9M112 and MT9M131 cameras from Micron/Aptina +config VIDEO_MT9M114 + tristate "onsemi MT9M114 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor-level driver for the onsemi MT9M114 + camera. + + To compile this driver as a module, choose M here: the + module will be called mt9m114. + config VIDEO_MT9P031 tristate "Aptina MT9P031 support" select VIDEO_APTINA_PLL diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 80b00d39b48f..f5010f80a21f 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o obj-$(CONFIG_VIDEO_MT9M111) += mt9m111.o +obj-$(CONFIG_VIDEO_MT9M114) += mt9m114.o obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o obj-$(CONFIG_VIDEO_MT9T112) += mt9t112.o obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c new file mode 100644 index 000000000000..dae675e52390 --- /dev/null +++ b/drivers/media/i2c/mt9m114.c @@ -0,0 +1,2481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mt9m114.c onsemi MT9M114 sensor driver + * + * Copyright (c) 2020-2023 Laurent Pinchart + * Copyright (c) 2012 Analog Devices Inc. + * + * Almost complete rewrite of work by Scott Jiang + * itself based on work from Andrew Chew . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Sysctl registers */ +#define MT9M114_CHIP_ID CCI_REG16(0x0000) +#define MT9M114_COMMAND_REGISTER CCI_REG16(0x0080) +#define MT9M114_COMMAND_REGISTER_APPLY_PATCH BIT(0) +#define MT9M114_COMMAND_REGISTER_SET_STATE BIT(1) +#define MT9M114_COMMAND_REGISTER_REFRESH BIT(2) +#define MT9M114_COMMAND_REGISTER_WAIT_FOR_EVENT BIT(3) +#define MT9M114_COMMAND_REGISTER_OK BIT(15) +#define MT9M114_RESET_AND_MISC_CONTROL CCI_REG16(0x001a) +#define MT9M114_RESET_SOC BIT(0) +#define MT9M114_PAD_SLEW CCI_REG16(0x001e) +#define MT9M114_PAD_CONTROL CCI_REG16(0x0032) + +/* XDMA registers */ +#define MT9M114_ACCESS_CTL_STAT CCI_REG16(0x0982) +#define MT9M114_PHYSICAL_ADDRESS_ACCESS CCI_REG16(0x098a) +#define MT9M114_LOGICAL_ADDRESS_ACCESS CCI_REG16(0x098e) + +/* Sensor Core registers */ +#define MT9M114_COARSE_INTEGRATION_TIME CCI_REG16(0x3012) +#define MT9M114_FINE_INTEGRATION_TIME CCI_REG16(0x3014) +#define MT9M114_RESET_REGISTER CCI_REG16(0x301a) +#define MT9M114_RESET_REGISTER_LOCK_REG BIT(3) +#define MT9M114_RESET_REGISTER_MASK_BAD BIT(9) +#define MT9M114_FLASH CCI_REG16(0x3046) +#define MT9M114_GREEN1_GAIN CCI_REG16(0x3056) +#define MT9M114_BLUE_GAIN CCI_REG16(0x3058) +#define MT9M114_RED_GAIN CCI_REG16(0x305a) +#define MT9M114_GREEN2_GAIN CCI_REG16(0x305c) +#define MT9M114_GLOBAL_GAIN CCI_REG16(0x305e) +#define MT9M114_GAIN_DIGITAL_GAIN(n) ((n) << 12) +#define MT9M114_GAIN_DIGITAL_GAIN_MASK (0xf << 12) +#define MT9M114_GAIN_ANALOG_GAIN(n) ((n) << 0) +#define MT9M114_GAIN_ANALOG_GAIN_MASK (0xff << 0) +#define MT9M114_CUSTOMER_REV CCI_REG16(0x31fe) + +/* Monitor registers */ +#define MT9M114_MON_MAJOR_VERSION CCI_REG16(0x8000) +#define MT9M114_MON_MINOR_VERSION CCI_REG16(0x8002) +#define MT9M114_MON_RELEASE_VERSION CCI_REG16(0x8004) + +/* Auto-Exposure Track registers */ +#define MT9M114_AE_TRACK_ALGO CCI_REG16(0xa804) +#define MT9M114_AE_TRACK_EXEC_AUTOMATIC_EXPOSURE BIT(0) +#define MT9M114_AE_TRACK_AE_TRACKING_DAMPENING_SPEED CCI_REG8(0xa80a) + +/* Color Correction Matrix registers */ +#define MT9M114_CCM_ALGO CCI_REG16(0xb404) +#define MT9M114_CCM_EXEC_CALC_CCM_MATRIX BIT(4) +#define MT9M114_CCM_DELTA_GAIN CCI_REG8(0xb42a) + +/* Camera Control registers */ +#define MT9M114_CAM_SENSOR_CFG_Y_ADDR_START CCI_REG16(0xc800) +#define MT9M114_CAM_SENSOR_CFG_X_ADDR_START CCI_REG16(0xc802) +#define MT9M114_CAM_SENSOR_CFG_Y_ADDR_END CCI_REG16(0xc804) +#define MT9M114_CAM_SENSOR_CFG_X_ADDR_END CCI_REG16(0xc806) +#define MT9M114_CAM_SENSOR_CFG_PIXCLK CCI_REG32(0xc808) +#define MT9M114_CAM_SENSOR_CFG_ROW_SPEED CCI_REG16(0xc80c) +#define MT9M114_CAM_SENSOR_CFG_FINE_INTEG_TIME_MIN CCI_REG16(0xc80e) +#define MT9M114_CAM_SENSOR_CFG_FINE_INTEG_TIME_MAX CCI_REG16(0xc810) +#define MT9M114_CAM_SENSOR_CFG_FRAME_LENGTH_LINES CCI_REG16(0xc812) +#define MT9M114_CAM_SENSOR_CFG_FRAME_LENGTH_LINES_MAX 65535 +#define MT9M114_CAM_SENSOR_CFG_LINE_LENGTH_PCK CCI_REG16(0xc814) +#define MT9M114_CAM_SENSOR_CFG_LINE_LENGTH_PCK_MAX 8191 +#define MT9M114_CAM_SENSOR_CFG_FINE_CORRECTION CCI_REG16(0xc816) +#define MT9M114_CAM_SENSOR_CFG_CPIPE_LAST_ROW CCI_REG16(0xc818) +#define MT9M114_CAM_SENSOR_CFG_REG_0_DATA CCI_REG16(0xc826) +#define MT9M114_CAM_SENSOR_CONTROL_READ_MODE CCI_REG16(0xc834) +#define MT9M114_CAM_SENSOR_CONTROL_HORZ_MIRROR_EN BIT(0) +#define MT9M114_CAM_SENSOR_CONTROL_VERT_FLIP_EN BIT(1) +#define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_NORMAL (0 << 4) +#define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_SKIPPING (1 << 4) +#define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_AVERAGE (2 << 4) +#define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_SUMMING (3 << 4) +#define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_MASK (3 << 4) +#define MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_NORMAL (0 << 8) +#define MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_SKIPPING (1 << 8) +#define MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_SUMMING (3 << 8) +#define MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_MASK (3 << 8) +#define MT9M114_CAM_SENSOR_CONTROL_ANALOG_GAIN CCI_REG16(0xc836) +#define MT9M114_CAM_SENSOR_CONTROL_COARSE_INTEGRATION_TIME CCI_REG16(0xc83c) +#define MT9M114_CAM_SENSOR_CONTROL_FINE_INTEGRATION_TIME CCI_REG16(0xc83e) +#define MT9M114_CAM_MODE_SELECT CCI_REG8(0xc84c) +#define MT9M114_CAM_MODE_SELECT_NORMAL (0 << 0) +#define MT9M114_CAM_MODE_SELECT_LENS_CALIBRATION (1 << 0) +#define MT9M114_CAM_MODE_SELECT_TEST_PATTERN (2 << 0) +#define MT9M114_CAM_MODE_TEST_PATTERN_SELECT CCI_REG8(0xc84d) +#define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_SOLID (1 << 0) +#define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_SOLID_BARS (4 << 0) +#define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_RANDOM (5 << 0) +#define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_FADING_BARS (8 << 0) +#define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_WALKING_1S_10B (10 << 0) +#define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_WALKING_1S_8B (11 << 0) +#define MT9M114_CAM_MODE_TEST_PATTERN_RED CCI_REG16(0xc84e) +#define MT9M114_CAM_MODE_TEST_PATTERN_GREEN CCI_REG16(0xc850) +#define MT9M114_CAM_MODE_TEST_PATTERN_BLUE CCI_REG16(0xc852) +#define MT9M114_CAM_CROP_WINDOW_XOFFSET CCI_REG16(0xc854) +#define MT9M114_CAM_CROP_WINDOW_YOFFSET CCI_REG16(0xc856) +#define MT9M114_CAM_CROP_WINDOW_WIDTH CCI_REG16(0xc858) +#define MT9M114_CAM_CROP_WINDOW_HEIGHT CCI_REG16(0xc85a) +#define MT9M114_CAM_CROP_CROPMODE CCI_REG8(0xc85c) +#define MT9M114_CAM_CROP_MODE_AE_AUTO_CROP_EN BIT(0) +#define MT9M114_CAM_CROP_MODE_AWB_AUTO_CROP_EN BIT(1) +#define MT9M114_CAM_OUTPUT_WIDTH CCI_REG16(0xc868) +#define MT9M114_CAM_OUTPUT_HEIGHT CCI_REG16(0xc86a) +#define MT9M114_CAM_OUTPUT_FORMAT CCI_REG16(0xc86c) +#define MT9M114_CAM_OUTPUT_FORMAT_SWAP_RED_BLUE BIT(0) +#define MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES BIT(1) +#define MT9M114_CAM_OUTPUT_FORMAT_MONO_ENABLE BIT(2) +#define MT9M114_CAM_OUTPUT_FORMAT_BT656_ENABLE BIT(3) +#define MT9M114_CAM_OUTPUT_FORMAT_BT656_CROP_SCALE_DISABLE BIT(4) +#define MT9M114_CAM_OUTPUT_FORMAT_FVLV_DISABLE BIT(5) +#define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV (0 << 8) +#define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB (1 << 8) +#define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_BAYER (2 << 8) +#define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_NONE (3 << 8) +#define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_MASK (3 << 8) +#define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_RAWR10 (0 << 10) +#define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_PRELSC_8_2 (1 << 10) +#define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_POSTLSC_8_2 (2 << 10) +#define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_PROCESSED8 (3 << 10) +#define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_MASK (3 << 10) +#define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_565RGB (0 << 12) +#define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_555RGB (1 << 12) +#define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_444xRGB (2 << 12) +#define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_444RGBx (3 << 12) +#define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_MASK (3 << 12) +#define MT9M114_CAM_OUTPUT_FORMAT_YUV CCI_REG16(0xc86e) +#define MT9M114_CAM_OUTPUT_FORMAT_YUV_CLIP BIT(5) +#define MT9M114_CAM_OUTPUT_FORMAT_YUV_AUV_OFFSET BIT(4) +#define MT9M114_CAM_OUTPUT_FORMAT_YUV_SELECT_601 BIT(3) +#define MT9M114_CAM_OUTPUT_FORMAT_YUV_NORMALISE BIT(2) +#define MT9M114_CAM_OUTPUT_FORMAT_YUV_SAMPLING_EVEN_UV (0 << 0) +#define MT9M114_CAM_OUTPUT_FORMAT_YUV_SAMPLING_ODD_UV (1 << 0) +#define MT9M114_CAM_OUTPUT_FORMAT_YUV_SAMPLING_EVENU_ODDV (2 << 0) +#define MT9M114_CAM_OUTPUT_Y_OFFSET CCI_REG8(0xc870) +#define MT9M114_CAM_AET_AEMODE CCI_REG8(0xc878) +#define MT9M114_CAM_AET_EXEC_SET_INDOOR BIT(0) +#define MT9M114_CAM_AET_DISCRETE_FRAMERATE BIT(1) +#define MT9M114_CAM_AET_ADAPTATIVE_TARGET_LUMA BIT(2) +#define MT9M114_CAM_AET_ADAPTATIVE_SKIP_FRAMES BIT(3) +#define MT9M114_CAM_AET_SKIP_FRAMES CCI_REG8(0xc879) +#define MT9M114_CAM_AET_TARGET_AVERAGE_LUMA CCI_REG8(0xc87a) +#define MT9M114_CAM_AET_TARGET_AVERAGE_LUMA_DARK CCI_REG8(0xc87b) +#define MT9M114_CAM_AET_BLACK_CLIPPING_TARGET CCI_REG16(0xc87c) +#define MT9M114_CAM_AET_AE_MIN_VIRT_INT_TIME_PCLK CCI_REG16(0xc87e) +#define MT9M114_CAM_AET_AE_MIN_VIRT_DGAIN CCI_REG16(0xc880) +#define MT9M114_CAM_AET_AE_MAX_VIRT_DGAIN CCI_REG16(0xc882) +#define MT9M114_CAM_AET_AE_MIN_VIRT_AGAIN CCI_REG16(0xc884) +#define MT9M114_CAM_AET_AE_MAX_VIRT_AGAIN CCI_REG16(0xc886) +#define MT9M114_CAM_AET_AE_VIRT_GAIN_TH_EG CCI_REG16(0xc888) +#define MT9M114_CAM_AET_AE_EG_GATE_PERCENTAGE CCI_REG8(0xc88a) +#define MT9M114_CAM_AET_FLICKER_FREQ_HZ CCI_REG8(0xc88b) +#define MT9M114_CAM_AET_MAX_FRAME_RATE CCI_REG16(0xc88c) +#define MT9M114_CAM_AET_MIN_FRAME_RATE CCI_REG16(0xc88e) +#define MT9M114_CAM_AET_TARGET_GAIN CCI_REG16(0xc890) +#define MT9M114_CAM_AWB_CCM_L(n) CCI_REG16(0xc892 + (n) * 2) +#define MT9M114_CAM_AWB_CCM_M(n) CCI_REG16(0xc8a4 + (n) * 2) +#define MT9M114_CAM_AWB_CCM_R(n) CCI_REG16(0xc8b6 + (n) * 2) +#define MT9M114_CAM_AWB_CCM_L_RG_GAIN CCI_REG16(0xc8c8) +#define MT9M114_CAM_AWB_CCM_L_BG_GAIN CCI_REG16(0xc8ca) +#define MT9M114_CAM_AWB_CCM_M_RG_GAIN CCI_REG16(0xc8cc) +#define MT9M114_CAM_AWB_CCM_M_BG_GAIN CCI_REG16(0xc8ce) +#define MT9M114_CAM_AWB_CCM_R_RG_GAIN CCI_REG16(0xc8d0) +#define MT9M114_CAM_AWB_CCM_R_BG_GAIN CCI_REG16(0xc8d2) +#define MT9M114_CAM_AWB_CCM_L_CTEMP CCI_REG16(0xc8d4) +#define MT9M114_CAM_AWB_CCM_M_CTEMP CCI_REG16(0xc8d6) +#define MT9M114_CAM_AWB_CCM_R_CTEMP CCI_REG16(0xc8d8) +#define MT9M114_CAM_AWB_AWB_XSCALE CCI_REG8(0xc8f2) +#define MT9M114_CAM_AWB_AWB_YSCALE CCI_REG8(0xc8f3) +#define MT9M114_CAM_AWB_AWB_WEIGHTS(n) CCI_REG16(0xc8f4 + (n) * 2) +#define MT9M114_CAM_AWB_AWB_XSHIFT_PRE_ADJ CCI_REG16(0xc904) +#define MT9M114_CAM_AWB_AWB_YSHIFT_PRE_ADJ CCI_REG16(0xc906) +#define MT9M114_CAM_AWB_AWBMODE CCI_REG8(0xc909) +#define MT9M114_CAM_AWB_MODE_AUTO BIT(1) +#define MT9M114_CAM_AWB_MODE_EXCLUSIVE_AE BIT(0) +#define MT9M114_CAM_AWB_K_R_L CCI_REG8(0xc90c) +#define MT9M114_CAM_AWB_K_G_L CCI_REG8(0xc90d) +#define MT9M114_CAM_AWB_K_B_L CCI_REG8(0xc90e) +#define MT9M114_CAM_AWB_K_R_R CCI_REG8(0xc90f) +#define MT9M114_CAM_AWB_K_G_R CCI_REG8(0xc910) +#define MT9M114_CAM_AWB_K_B_R CCI_REG8(0xc911) +#define MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XSTART CCI_REG16(0xc914) +#define MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YSTART CCI_REG16(0xc916) +#define MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XEND CCI_REG16(0xc918) +#define MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YEND CCI_REG16(0xc91a) +#define MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XSTART CCI_REG16(0xc91c) +#define MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YSTART CCI_REG16(0xc91e) +#define MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XEND CCI_REG16(0xc920) +#define MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YEND CCI_REG16(0xc922) +#define MT9M114_CAM_LL_LLMODE CCI_REG16(0xc924) +#define MT9M114_CAM_LL_START_BRIGHTNESS CCI_REG16(0xc926) +#define MT9M114_CAM_LL_STOP_BRIGHTNESS CCI_REG16(0xc928) +#define MT9M114_CAM_LL_START_SATURATION CCI_REG8(0xc92a) +#define MT9M114_CAM_LL_END_SATURATION CCI_REG8(0xc92b) +#define MT9M114_CAM_LL_START_DESATURATION CCI_REG8(0xc92c) +#define MT9M114_CAM_LL_END_DESATURATION CCI_REG8(0xc92d) +#define MT9M114_CAM_LL_START_DEMOSAICING CCI_REG8(0xc92e) +#define MT9M114_CAM_LL_START_AP_GAIN CCI_REG8(0xc92f) +#define MT9M114_CAM_LL_START_AP_THRESH CCI_REG8(0xc930) +#define MT9M114_CAM_LL_STOP_DEMOSAICING CCI_REG8(0xc931) +#define MT9M114_CAM_LL_STOP_AP_GAIN CCI_REG8(0xc932) +#define MT9M114_CAM_LL_STOP_AP_THRESH CCI_REG8(0xc933) +#define MT9M114_CAM_LL_START_NR_RED CCI_REG8(0xc934) +#define MT9M114_CAM_LL_START_NR_GREEN CCI_REG8(0xc935) +#define MT9M114_CAM_LL_START_NR_BLUE CCI_REG8(0xc936) +#define MT9M114_CAM_LL_START_NR_THRESH CCI_REG8(0xc937) +#define MT9M114_CAM_LL_STOP_NR_RED CCI_REG8(0xc938) +#define MT9M114_CAM_LL_STOP_NR_GREEN CCI_REG8(0xc939) +#define MT9M114_CAM_LL_STOP_NR_BLUE CCI_REG8(0xc93a) +#define MT9M114_CAM_LL_STOP_NR_THRESH CCI_REG8(0xc93b) +#define MT9M114_CAM_LL_START_CONTRAST_BM CCI_REG16(0xc93c) +#define MT9M114_CAM_LL_STOP_CONTRAST_BM CCI_REG16(0xc93e) +#define MT9M114_CAM_LL_GAMMA CCI_REG16(0xc940) +#define MT9M114_CAM_LL_START_CONTRAST_GRADIENT CCI_REG8(0xc942) +#define MT9M114_CAM_LL_STOP_CONTRAST_GRADIENT CCI_REG8(0xc943) +#define MT9M114_CAM_LL_START_CONTRAST_LUMA_PERCENTAGE CCI_REG8(0xc944) +#define MT9M114_CAM_LL_STOP_CONTRAST_LUMA_PERCENTAGE CCI_REG8(0xc945) +#define MT9M114_CAM_LL_START_GAIN_METRIC CCI_REG16(0xc946) +#define MT9M114_CAM_LL_STOP_GAIN_METRIC CCI_REG16(0xc948) +#define MT9M114_CAM_LL_START_FADE_TO_BLACK_LUMA CCI_REG16(0xc94a) +#define MT9M114_CAM_LL_STOP_FADE_TO_BLACK_LUMA CCI_REG16(0xc94c) +#define MT9M114_CAM_LL_CLUSTER_DC_TH_BM CCI_REG16(0xc94e) +#define MT9M114_CAM_LL_CLUSTER_DC_GATE_PERCENTAGE CCI_REG8(0xc950) +#define MT9M114_CAM_LL_SUMMING_SENSITIVITY_FACTOR CCI_REG8(0xc951) +#define MT9M114_CAM_LL_START_TARGET_LUMA_BM CCI_REG16(0xc952) +#define MT9M114_CAM_LL_STOP_TARGET_LUMA_BM CCI_REG16(0xc954) +#define MT9M114_CAM_PGA_PGA_CONTROL CCI_REG16(0xc95e) +#define MT9M114_CAM_SYSCTL_PLL_ENABLE CCI_REG8(0xc97e) +#define MT9M114_CAM_SYSCTL_PLL_ENABLE_VALUE BIT(0) +#define MT9M114_CAM_SYSCTL_PLL_DIVIDER_M_N CCI_REG16(0xc980) +#define MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(m, n) (((n) << 8) | (m)) +#define MT9M114_CAM_SYSCTL_PLL_DIVIDER_P CCI_REG16(0xc982) +#define MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(p) ((p) << 8) +#define MT9M114_CAM_PORT_OUTPUT_CONTROL CCI_REG16(0xc984) +#define MT9M114_CAM_PORT_PORT_SELECT_PARALLEL (0 << 0) +#define MT9M114_CAM_PORT_PORT_SELECT_MIPI (1 << 0) +#define MT9M114_CAM_PORT_CLOCK_SLOWDOWN BIT(3) +#define MT9M114_CAM_PORT_TRUNCATE_RAW_BAYER BIT(4) +#define MT9M114_CAM_PORT_PIXCLK_GATE BIT(5) +#define MT9M114_CAM_PORT_CONT_MIPI_CLK BIT(6) +#define MT9M114_CAM_PORT_CHAN_NUM(vc) ((vc) << 8) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_ZERO CCI_REG16(0xc988) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_ZERO_VALUE(n) ((n) << 8) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_EXIT_TRAIL CCI_REG16(0xc98a) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_EXIT_VALUE(n) ((n) << 8) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_TRAIL_VALUE(n) ((n) << 0) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_POST_PRE CCI_REG16(0xc98c) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_POST_VALUE(n) ((n) << 8) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_PRE_VALUE(n) ((n) << 0) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_TRAIL_ZERO CCI_REG16(0xc98e) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_TRAIL_VALUE(n) ((n) << 8) +#define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_ZERO_VALUE(n) ((n) << 0) + +/* System Manager registers */ +#define MT9M114_SYSMGR_NEXT_STATE CCI_REG8(0xdc00) +#define MT9M114_SYSMGR_CURRENT_STATE CCI_REG8(0xdc01) +#define MT9M114_SYSMGR_CMD_STATUS CCI_REG8(0xdc02) + +/* Patch Loader registers */ +#define MT9M114_PATCHLDR_LOADER_ADDRESS CCI_REG16(0xe000) +#define MT9M114_PATCHLDR_PATCH_ID CCI_REG16(0xe002) +#define MT9M114_PATCHLDR_FIRMWARE_ID CCI_REG32(0xe004) +#define MT9M114_PATCHLDR_APPLY_STATUS CCI_REG8(0xe008) +#define MT9M114_PATCHLDR_NUM_PATCHES CCI_REG8(0xe009) +#define MT9M114_PATCHLDR_PATCH_ID_0 CCI_REG16(0xe00a) +#define MT9M114_PATCHLDR_PATCH_ID_1 CCI_REG16(0xe00c) +#define MT9M114_PATCHLDR_PATCH_ID_2 CCI_REG16(0xe00e) +#define MT9M114_PATCHLDR_PATCH_ID_3 CCI_REG16(0xe010) +#define MT9M114_PATCHLDR_PATCH_ID_4 CCI_REG16(0xe012) +#define MT9M114_PATCHLDR_PATCH_ID_5 CCI_REG16(0xe014) +#define MT9M114_PATCHLDR_PATCH_ID_6 CCI_REG16(0xe016) +#define MT9M114_PATCHLDR_PATCH_ID_7 CCI_REG16(0xe018) + +/* SYS_STATE values (for SYSMGR_NEXT_STATE and SYSMGR_CURRENT_STATE) */ +#define MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE 0x28 +#define MT9M114_SYS_STATE_STREAMING 0x31 +#define MT9M114_SYS_STATE_START_STREAMING 0x34 +#define MT9M114_SYS_STATE_ENTER_SUSPEND 0x40 +#define MT9M114_SYS_STATE_SUSPENDED 0x41 +#define MT9M114_SYS_STATE_ENTER_STANDBY 0x50 +#define MT9M114_SYS_STATE_STANDBY 0x52 +#define MT9M114_SYS_STATE_LEAVE_STANDBY 0x54 + +/* Result status of last SET_STATE comamnd */ +#define MT9M114_SET_STATE_RESULT_ENOERR 0x00 +#define MT9M114_SET_STATE_RESULT_EINVAL 0x0c +#define MT9M114_SET_STATE_RESULT_ENOSPC 0x0d + +/* + * The minimum amount of horizontal and vertical blanking is undocumented. The + * minimum values that have been seen in register lists are 303 and 38, use + * them. + * + * Set the default to achieve 1280x960 at 30fps. + */ +#define MT9M114_MIN_HBLANK 303 +#define MT9M114_MIN_VBLANK 38 +#define MT9M114_DEF_HBLANK 323 +#define MT9M114_DEF_VBLANK 39 + +#define MT9M114_DEF_FRAME_RATE 30 +#define MT9M114_MAX_FRAME_RATE 120 + +#define MT9M114_PIXEL_ARRAY_WIDTH 1296U +#define MT9M114_PIXEL_ARRAY_HEIGHT 976U + +/* + * These values are not well documented and are semi-arbitrary. The pixel array + * minimum output size is 8 pixels larger than the minimum scaler cropped input + * width to account for the demosaicing. + */ +#define MT9M114_PIXEL_ARRAY_MIN_OUTPUT_WIDTH (32U + 8U) +#define MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT (32U + 8U) +#define MT9M114_SCALER_CROPPED_INPUT_WIDTH 32U +#define MT9M114_SCALER_CROPPED_INPUT_HEIGHT 32U + +/* Indices into the mt9m114.ifp.tpg array. */ +#define MT9M114_TPG_PATTERN 0 +#define MT9M114_TPG_RED 1 +#define MT9M114_TPG_GREEN 2 +#define MT9M114_TPG_BLUE 3 + +/* ----------------------------------------------------------------------------- + * Data Structures + */ + +enum mt9m114_format_flag { + MT9M114_FMT_FLAG_PARALLEL = BIT(0), + MT9M114_FMT_FLAG_CSI2 = BIT(1), +}; + +struct mt9m114_format_info { + u32 code; + u32 output_format; + u32 flags; +}; + +struct mt9m114 { + struct i2c_client *client; + struct regmap *regmap; + + struct clk *clk; + struct gpio_desc *reset; + struct regulator_bulk_data supplies[3]; + struct v4l2_fwnode_endpoint bus_cfg; + + struct { + unsigned int m; + unsigned int n; + unsigned int p; + } pll; + + unsigned int pixrate; + bool streaming; + + /* Pixel Array */ + struct { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + } pa; + + /* Image Flow Processor */ + struct { + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_ctrl_handler hdl; + unsigned int frame_rate; + + struct v4l2_ctrl *tpg[4]; + } ifp; +}; + +/* ----------------------------------------------------------------------------- + * Formats + */ + +static const struct mt9m114_format_info mt9m114_format_infos[] = { + { + /* + * The first two entries are used as defaults, for parallel and + * CSI-2 buses respectively. Keep them in that order. + */ + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .flags = MT9M114_FMT_FLAG_PARALLEL, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV, + }, { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .flags = MT9M114_FMT_FLAG_CSI2, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV, + }, { + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .flags = MT9M114_FMT_FLAG_PARALLEL, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV + | MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES, + }, { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .flags = MT9M114_FMT_FLAG_CSI2, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV + | MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES, + }, { + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .flags = MT9M114_FMT_FLAG_PARALLEL, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_565RGB + | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB + | MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES, + }, { + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .flags = MT9M114_FMT_FLAG_PARALLEL, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_565RGB + | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB, + }, { + .code = MEDIA_BUS_FMT_RGB565_1X16, + .flags = MT9M114_FMT_FLAG_CSI2, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_565RGB + | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB, + }, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_PROCESSED8 + | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_BAYER, + .flags = MT9M114_FMT_FLAG_PARALLEL | MT9M114_FMT_FLAG_CSI2, + }, { + /* Keep the format compatible with the IFP sink pad last. */ + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .output_format = MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_RAWR10 + | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_BAYER, + .flags = MT9M114_FMT_FLAG_PARALLEL | MT9M114_FMT_FLAG_CSI2, + } +}; + +static const struct mt9m114_format_info * +mt9m114_default_format_info(struct mt9m114 *sensor) +{ + if (sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY) + return &mt9m114_format_infos[1]; + else + return &mt9m114_format_infos[0]; +} + +static const struct mt9m114_format_info * +mt9m114_format_info(struct mt9m114 *sensor, unsigned int pad, u32 code) +{ + const unsigned int num_formats = ARRAY_SIZE(mt9m114_format_infos); + unsigned int flag; + unsigned int i; + + switch (pad) { + case 0: + return &mt9m114_format_infos[num_formats - 1]; + + case 1: + if (sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY) + flag = MT9M114_FMT_FLAG_CSI2; + else + flag = MT9M114_FMT_FLAG_PARALLEL; + + for (i = 0; i < num_formats; ++i) { + const struct mt9m114_format_info *info = + &mt9m114_format_infos[i]; + + if (info->code == code && info->flags & flag) + return info; + } + + return mt9m114_default_format_info(sensor); + + default: + return NULL; + } +} + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +static const struct cci_reg_sequence mt9m114_init[] = { + { MT9M114_RESET_REGISTER, MT9M114_RESET_REGISTER_MASK_BAD | + MT9M114_RESET_REGISTER_LOCK_REG | + 0x0010 }, + + /* Sensor optimization */ + { CCI_REG16(0x316a), 0x8270 }, + { CCI_REG16(0x316c), 0x8270 }, + { CCI_REG16(0x3ed0), 0x2305 }, + { CCI_REG16(0x3ed2), 0x77cf }, + { CCI_REG16(0x316e), 0x8202 }, + { CCI_REG16(0x3180), 0x87ff }, + { CCI_REG16(0x30d4), 0x6080 }, + { CCI_REG16(0xa802), 0x0008 }, + + { CCI_REG16(0x3e14), 0xff39 }, + + /* APGA */ + { MT9M114_CAM_PGA_PGA_CONTROL, 0x0000 }, + + /* Automatic White balance */ + { MT9M114_CAM_AWB_CCM_L(0), 0x0267 }, + { MT9M114_CAM_AWB_CCM_L(1), 0xff1a }, + { MT9M114_CAM_AWB_CCM_L(2), 0xffb3 }, + { MT9M114_CAM_AWB_CCM_L(3), 0xff80 }, + { MT9M114_CAM_AWB_CCM_L(4), 0x0166 }, + { MT9M114_CAM_AWB_CCM_L(5), 0x0003 }, + { MT9M114_CAM_AWB_CCM_L(6), 0xff9a }, + { MT9M114_CAM_AWB_CCM_L(7), 0xfeb4 }, + { MT9M114_CAM_AWB_CCM_L(8), 0x024d }, + { MT9M114_CAM_AWB_CCM_M(0), 0x01bf }, + { MT9M114_CAM_AWB_CCM_M(1), 0xff01 }, + { MT9M114_CAM_AWB_CCM_M(2), 0xfff3 }, + { MT9M114_CAM_AWB_CCM_M(3), 0xff75 }, + { MT9M114_CAM_AWB_CCM_M(4), 0x0198 }, + { MT9M114_CAM_AWB_CCM_M(5), 0xfffd }, + { MT9M114_CAM_AWB_CCM_M(6), 0xff9a }, + { MT9M114_CAM_AWB_CCM_M(7), 0xfee7 }, + { MT9M114_CAM_AWB_CCM_M(8), 0x02a8 }, + { MT9M114_CAM_AWB_CCM_R(0), 0x01d9 }, + { MT9M114_CAM_AWB_CCM_R(1), 0xff26 }, + { MT9M114_CAM_AWB_CCM_R(2), 0xfff3 }, + { MT9M114_CAM_AWB_CCM_R(3), 0xffb3 }, + { MT9M114_CAM_AWB_CCM_R(4), 0x0132 }, + { MT9M114_CAM_AWB_CCM_R(5), 0xffe8 }, + { MT9M114_CAM_AWB_CCM_R(6), 0xffda }, + { MT9M114_CAM_AWB_CCM_R(7), 0xfecd }, + { MT9M114_CAM_AWB_CCM_R(8), 0x02c2 }, + { MT9M114_CAM_AWB_CCM_L_RG_GAIN, 0x0075 }, + { MT9M114_CAM_AWB_CCM_L_BG_GAIN, 0x011c }, + { MT9M114_CAM_AWB_CCM_M_RG_GAIN, 0x009a }, + { MT9M114_CAM_AWB_CCM_M_BG_GAIN, 0x0105 }, + { MT9M114_CAM_AWB_CCM_R_RG_GAIN, 0x00a4 }, + { MT9M114_CAM_AWB_CCM_R_BG_GAIN, 0x00ac }, + { MT9M114_CAM_AWB_CCM_L_CTEMP, 0x0a8c }, + { MT9M114_CAM_AWB_CCM_M_CTEMP, 0x0f0a }, + { MT9M114_CAM_AWB_CCM_R_CTEMP, 0x1964 }, + { MT9M114_CAM_AWB_AWB_XSHIFT_PRE_ADJ, 51 }, + { MT9M114_CAM_AWB_AWB_YSHIFT_PRE_ADJ, 60 }, + { MT9M114_CAM_AWB_AWB_XSCALE, 3 }, + { MT9M114_CAM_AWB_AWB_YSCALE, 2 }, + { MT9M114_CAM_AWB_AWB_WEIGHTS(0), 0x0000 }, + { MT9M114_CAM_AWB_AWB_WEIGHTS(1), 0x0000 }, + { MT9M114_CAM_AWB_AWB_WEIGHTS(2), 0x0000 }, + { MT9M114_CAM_AWB_AWB_WEIGHTS(3), 0xe724 }, + { MT9M114_CAM_AWB_AWB_WEIGHTS(4), 0x1583 }, + { MT9M114_CAM_AWB_AWB_WEIGHTS(5), 0x2045 }, + { MT9M114_CAM_AWB_AWB_WEIGHTS(6), 0x03ff }, + { MT9M114_CAM_AWB_AWB_WEIGHTS(7), 0x007c }, + { MT9M114_CAM_AWB_K_R_L, 0x80 }, + { MT9M114_CAM_AWB_K_G_L, 0x80 }, + { MT9M114_CAM_AWB_K_B_L, 0x80 }, + { MT9M114_CAM_AWB_K_R_R, 0x88 }, + { MT9M114_CAM_AWB_K_G_R, 0x80 }, + { MT9M114_CAM_AWB_K_B_R, 0x80 }, + + /* Low-Light Image Enhancements */ + { MT9M114_CAM_LL_START_BRIGHTNESS, 0x0020 }, + { MT9M114_CAM_LL_STOP_BRIGHTNESS, 0x009a }, + { MT9M114_CAM_LL_START_GAIN_METRIC, 0x0070 }, + { MT9M114_CAM_LL_STOP_GAIN_METRIC, 0x00f3 }, + { MT9M114_CAM_LL_START_CONTRAST_LUMA_PERCENTAGE, 0x20 }, + { MT9M114_CAM_LL_STOP_CONTRAST_LUMA_PERCENTAGE, 0x9a }, + { MT9M114_CAM_LL_START_SATURATION, 0x80 }, + { MT9M114_CAM_LL_END_SATURATION, 0x4b }, + { MT9M114_CAM_LL_START_DESATURATION, 0x00 }, + { MT9M114_CAM_LL_END_DESATURATION, 0xff }, + { MT9M114_CAM_LL_START_DEMOSAICING, 0x3c }, + { MT9M114_CAM_LL_START_AP_GAIN, 0x02 }, + { MT9M114_CAM_LL_START_AP_THRESH, 0x06 }, + { MT9M114_CAM_LL_STOP_DEMOSAICING, 0x64 }, + { MT9M114_CAM_LL_STOP_AP_GAIN, 0x01 }, + { MT9M114_CAM_LL_STOP_AP_THRESH, 0x0c }, + { MT9M114_CAM_LL_START_NR_RED, 0x3c }, + { MT9M114_CAM_LL_START_NR_GREEN, 0x3c }, + { MT9M114_CAM_LL_START_NR_BLUE, 0x3c }, + { MT9M114_CAM_LL_START_NR_THRESH, 0x0f }, + { MT9M114_CAM_LL_STOP_NR_RED, 0x64 }, + { MT9M114_CAM_LL_STOP_NR_GREEN, 0x64 }, + { MT9M114_CAM_LL_STOP_NR_BLUE, 0x64 }, + { MT9M114_CAM_LL_STOP_NR_THRESH, 0x32 }, + { MT9M114_CAM_LL_START_CONTRAST_BM, 0x0020 }, + { MT9M114_CAM_LL_STOP_CONTRAST_BM, 0x009a }, + { MT9M114_CAM_LL_GAMMA, 0x00dc }, + { MT9M114_CAM_LL_START_CONTRAST_GRADIENT, 0x38 }, + { MT9M114_CAM_LL_STOP_CONTRAST_GRADIENT, 0x30 }, + { MT9M114_CAM_LL_START_CONTRAST_LUMA_PERCENTAGE, 0x50 }, + { MT9M114_CAM_LL_STOP_CONTRAST_LUMA_PERCENTAGE, 0x19 }, + { MT9M114_CAM_LL_START_FADE_TO_BLACK_LUMA, 0x0230 }, + { MT9M114_CAM_LL_STOP_FADE_TO_BLACK_LUMA, 0x0010 }, + { MT9M114_CAM_LL_CLUSTER_DC_TH_BM, 0x01cd }, + { MT9M114_CAM_LL_CLUSTER_DC_GATE_PERCENTAGE, 0x05 }, + { MT9M114_CAM_LL_SUMMING_SENSITIVITY_FACTOR, 0x40 }, + + /* Auto-Exposure */ + { MT9M114_CAM_AET_TARGET_AVERAGE_LUMA_DARK, 0x1b }, + { MT9M114_CAM_AET_AEMODE, 0x00 }, + { MT9M114_CAM_AET_TARGET_GAIN, 0x0080 }, + { MT9M114_CAM_AET_AE_MAX_VIRT_AGAIN, 0x0100 }, + { MT9M114_CAM_AET_BLACK_CLIPPING_TARGET, 0x005a }, + + { MT9M114_CCM_DELTA_GAIN, 0x05 }, + { MT9M114_AE_TRACK_AE_TRACKING_DAMPENING_SPEED, 0x20 }, + + /* Pixel array timings and integration time */ + { MT9M114_CAM_SENSOR_CFG_ROW_SPEED, 1 }, + { MT9M114_CAM_SENSOR_CFG_FINE_INTEG_TIME_MIN, 219 }, + { MT9M114_CAM_SENSOR_CFG_FINE_INTEG_TIME_MAX, 1459 }, + { MT9M114_CAM_SENSOR_CFG_FINE_CORRECTION, 96 }, + { MT9M114_CAM_SENSOR_CFG_REG_0_DATA, 32 }, + + /* Miscellaneous settings */ + { MT9M114_PAD_SLEW, 0x0777 }, +}; + +/* ----------------------------------------------------------------------------- + * Hardware Configuration + */ + +/* Wait for a command to complete. */ +static int mt9m114_poll_command(struct mt9m114 *sensor, u32 command) +{ + unsigned int i; + u64 value; + int ret; + + for (i = 0; i < 100; ++i) { + ret = cci_read(sensor->regmap, MT9M114_COMMAND_REGISTER, &value, + NULL); + if (ret < 0) + return ret; + + if (!(value & command)) + break; + + usleep_range(5000, 6000); + } + + if (value & command) { + dev_err(&sensor->client->dev, "Command %u completion timeout\n", + command); + return -ETIMEDOUT; + } + + if (!(value & MT9M114_COMMAND_REGISTER_OK)) { + dev_err(&sensor->client->dev, "Command %u failed\n", command); + return -EIO; + } + + return 0; +} + +/* Wait for a state to be entered. */ +static int mt9m114_poll_state(struct mt9m114 *sensor, u32 state) +{ + unsigned int i; + u64 value; + int ret; + + for (i = 0; i < 100; ++i) { + ret = cci_read(sensor->regmap, MT9M114_SYSMGR_CURRENT_STATE, + &value, NULL); + if (ret < 0) + return ret; + + if (value == state) + return 0; + + usleep_range(1000, 1500); + } + + dev_err(&sensor->client->dev, "Timeout waiting for state 0x%02x\n", + state); + return -ETIMEDOUT; +} + +static int mt9m114_set_state(struct mt9m114 *sensor, u8 next_state) +{ + int ret = 0; + + /* Set the next desired state and start the state transition. */ + cci_write(sensor->regmap, MT9M114_SYSMGR_NEXT_STATE, next_state, &ret); + cci_write(sensor->regmap, MT9M114_COMMAND_REGISTER, + MT9M114_COMMAND_REGISTER_OK | + MT9M114_COMMAND_REGISTER_SET_STATE, &ret); + if (ret < 0) + return ret; + + /* Wait for the state transition to complete. */ + ret = mt9m114_poll_command(sensor, MT9M114_COMMAND_REGISTER_SET_STATE); + if (ret < 0) + return ret; + + return 0; +} + +static int mt9m114_initialize(struct mt9m114 *sensor) +{ + u32 value; + int ret; + + ret = cci_multi_reg_write(sensor->regmap, mt9m114_init, + ARRAY_SIZE(mt9m114_init), NULL); + if (ret < 0) { + dev_err(&sensor->client->dev, + "Failed to initialize the sensor\n"); + return ret; + } + + /* Configure the PLL. */ + cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_ENABLE, + MT9M114_CAM_SYSCTL_PLL_ENABLE_VALUE, &ret); + cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_M_N, + MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(sensor->pll.m, + sensor->pll.n), + &ret); + cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_P, + MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(sensor->pll.p), &ret); + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_PIXCLK, + sensor->pixrate, &ret); + + /* Configure the output mode. */ + if (sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY) { + value = MT9M114_CAM_PORT_PORT_SELECT_MIPI + | MT9M114_CAM_PORT_CHAN_NUM(0) + | 0x8000; + if (!(sensor->bus_cfg.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) + value |= MT9M114_CAM_PORT_CONT_MIPI_CLK; + } else { + value = MT9M114_CAM_PORT_PORT_SELECT_PARALLEL + | 0x8000; + } + cci_write(sensor->regmap, MT9M114_CAM_PORT_OUTPUT_CONTROL, value, &ret); + if (ret < 0) + return ret; + + ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE); + if (ret < 0) + return ret; + + ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_SUSPEND); + if (ret < 0) + return ret; + + return 0; +} + +static int mt9m114_configure(struct mt9m114 *sensor, + struct v4l2_subdev_state *pa_state, + struct v4l2_subdev_state *ifp_state) +{ + const struct v4l2_mbus_framefmt *pa_format; + const struct v4l2_rect *pa_crop; + const struct mt9m114_format_info *ifp_info; + const struct v4l2_mbus_framefmt *ifp_format; + const struct v4l2_rect *ifp_crop; + const struct v4l2_rect *ifp_compose; + unsigned int hratio, vratio; + u64 output_format; + u64 read_mode; + int ret = 0; + + pa_format = v4l2_subdev_get_pad_format(&sensor->pa.sd, pa_state, 0); + pa_crop = v4l2_subdev_get_pad_crop(&sensor->pa.sd, pa_state, 0); + + ifp_format = v4l2_subdev_get_pad_format(&sensor->ifp.sd, ifp_state, 1); + ifp_info = mt9m114_format_info(sensor, 1, ifp_format->code); + ifp_crop = v4l2_subdev_get_pad_crop(&sensor->ifp.sd, ifp_state, 0); + ifp_compose = v4l2_subdev_get_pad_compose(&sensor->ifp.sd, ifp_state, 0); + + ret = cci_read(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_READ_MODE, + &read_mode, NULL); + if (ret < 0) + return ret; + + ret = cci_read(sensor->regmap, MT9M114_CAM_OUTPUT_FORMAT, + &output_format, NULL); + if (ret < 0) + return ret; + + hratio = pa_crop->width / pa_format->width; + vratio = pa_crop->height / pa_format->height; + + /* + * Pixel array crop and binning. The CAM_SENSOR_CFG_CPIPE_LAST_ROW + * register isn't clearly documented, but is always set to the number + * of active rows minus 4 divided by the vertical binning factor in all + * example sensor modes. + */ + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_X_ADDR_START, + pa_crop->left, &ret); + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_Y_ADDR_START, + pa_crop->top, &ret); + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_X_ADDR_END, + pa_crop->width + pa_crop->left - 1, &ret); + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_Y_ADDR_END, + pa_crop->height + pa_crop->top - 1, &ret); + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_CPIPE_LAST_ROW, + (pa_crop->height - 4) / vratio - 1, &ret); + + read_mode &= ~(MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_MASK | + MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_MASK); + + if (hratio > 1) + read_mode |= MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_SUMMING; + if (vratio > 1) + read_mode |= MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_SUMMING; + + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_READ_MODE, + read_mode, &ret); + + /* + * Color pipeline (IFP) cropping and scaling. Subtract 4 from the left + * and top coordinates to compensate for the lines and columns removed + * by demosaicing that are taken into account in the crop rectangle but + * not in the hardware. + */ + cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_XOFFSET, + ifp_crop->left - 4, &ret); + cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_YOFFSET, + ifp_crop->top - 4, &ret); + cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_WIDTH, + ifp_crop->width, &ret); + cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_HEIGHT, + ifp_crop->height, &ret); + + cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_WIDTH, + ifp_compose->width, &ret); + cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_HEIGHT, + ifp_compose->height, &ret); + + /* AWB and AE windows, use the full frame. */ + cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XSTART, + 0, &ret); + cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YSTART, + 0, &ret); + cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XEND, + ifp_compose->width - 1, &ret); + cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YEND, + ifp_compose->height - 1, &ret); + + cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XSTART, + 0, &ret); + cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YSTART, + 0, &ret); + cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XEND, + ifp_compose->width / 5 - 1, &ret); + cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YEND, + ifp_compose->height / 5 - 1, &ret); + + cci_write(sensor->regmap, MT9M114_CAM_CROP_CROPMODE, + MT9M114_CAM_CROP_MODE_AWB_AUTO_CROP_EN | + MT9M114_CAM_CROP_MODE_AE_AUTO_CROP_EN, &ret); + + /* Set the media bus code. */ + output_format &= ~(MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_MASK | + MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_MASK | + MT9M114_CAM_OUTPUT_FORMAT_FORMAT_MASK | + MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES | + MT9M114_CAM_OUTPUT_FORMAT_SWAP_RED_BLUE); + output_format |= ifp_info->output_format; + + cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_FORMAT, + output_format, &ret); + + return ret; +} + +static int mt9m114_set_frame_rate(struct mt9m114 *sensor) +{ + u16 frame_rate = sensor->ifp.frame_rate << 8; + int ret = 0; + + cci_write(sensor->regmap, MT9M114_CAM_AET_MIN_FRAME_RATE, + frame_rate, &ret); + cci_write(sensor->regmap, MT9M114_CAM_AET_MAX_FRAME_RATE, + frame_rate, &ret); + + return ret; +} + +static int mt9m114_start_streaming(struct mt9m114 *sensor, + struct v4l2_subdev_state *pa_state, + struct v4l2_subdev_state *ifp_state) +{ + int ret; + + ret = pm_runtime_resume_and_get(&sensor->client->dev); + if (ret) + return ret; + + ret = mt9m114_configure(sensor, pa_state, ifp_state); + if (ret) + goto error; + + ret = mt9m114_set_frame_rate(sensor); + if (ret) + goto error; + + ret = __v4l2_ctrl_handler_setup(&sensor->pa.hdl); + if (ret) + goto error; + + ret = __v4l2_ctrl_handler_setup(&sensor->ifp.hdl); + if (ret) + goto error; + + /* + * The Change-Config state is transient and moves to the streaming + * state automatically. + */ + ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE); + if (ret) + goto error; + + sensor->streaming = true; + + return 0; + +error: + pm_runtime_mark_last_busy(&sensor->client->dev); + pm_runtime_put_autosuspend(&sensor->client->dev); + + return ret; +} + +static int mt9m114_stop_streaming(struct mt9m114 *sensor) +{ + int ret; + + sensor->streaming = false; + + ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_SUSPEND); + + pm_runtime_mark_last_busy(&sensor->client->dev); + pm_runtime_put_autosuspend(&sensor->client->dev); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Common Subdev Operations + */ + +static const struct media_entity_operations mt9m114_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* ----------------------------------------------------------------------------- + * Pixel Array Control Operations + */ + +static inline struct mt9m114 *pa_ctrl_to_mt9m114(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mt9m114, pa.hdl); +} + +static int mt9m114_pa_g_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m114 *sensor = pa_ctrl_to_mt9m114(ctrl); + u64 value; + int ret; + + if (!pm_runtime_get_if_in_use(&sensor->client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = cci_read(sensor->regmap, + MT9M114_CAM_SENSOR_CONTROL_COARSE_INTEGRATION_TIME, + &value, NULL); + if (ret) + break; + + ctrl->val = value; + break; + + case V4L2_CID_ANALOGUE_GAIN: + ret = cci_read(sensor->regmap, + MT9M114_CAM_SENSOR_CONTROL_ANALOG_GAIN, + &value, NULL); + if (ret) + break; + + ctrl->val = value; + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(&sensor->client->dev); + pm_runtime_put_autosuspend(&sensor->client->dev); + + return ret; +} + +static int mt9m114_pa_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m114 *sensor = pa_ctrl_to_mt9m114(ctrl); + const struct v4l2_mbus_framefmt *format; + struct v4l2_subdev_state *state; + int ret = 0; + u64 mask; + + /* V4L2 controls values are applied only when power is up. */ + if (!pm_runtime_get_if_in_use(&sensor->client->dev)) + return 0; + + state = v4l2_subdev_get_locked_active_state(&sensor->pa.sd); + format = v4l2_subdev_get_pad_format(&sensor->pa.sd, state, 0); + + switch (ctrl->id) { + case V4L2_CID_HBLANK: + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_LINE_LENGTH_PCK, + ctrl->val + format->width, &ret); + break; + + case V4L2_CID_VBLANK: + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_FRAME_LENGTH_LINES, + ctrl->val + format->height, &ret); + break; + + case V4L2_CID_EXPOSURE: + cci_write(sensor->regmap, + MT9M114_CAM_SENSOR_CONTROL_COARSE_INTEGRATION_TIME, + ctrl->val, &ret); + break; + + case V4L2_CID_ANALOGUE_GAIN: + /* + * The CAM_SENSOR_CONTROL_ANALOG_GAIN contains linear analog + * gain values that are mapped to the GLOBAL_GAIN register + * values by the sensor firmware. + */ + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_ANALOG_GAIN, + ctrl->val, &ret); + break; + + case V4L2_CID_HFLIP: + mask = MT9M114_CAM_SENSOR_CONTROL_HORZ_MIRROR_EN; + ret = cci_update_bits(sensor->regmap, + MT9M114_CAM_SENSOR_CONTROL_READ_MODE, + mask, ctrl->val ? mask : 0, NULL); + break; + + case V4L2_CID_VFLIP: + mask = MT9M114_CAM_SENSOR_CONTROL_VERT_FLIP_EN; + ret = cci_update_bits(sensor->regmap, + MT9M114_CAM_SENSOR_CONTROL_READ_MODE, + mask, ctrl->val ? mask : 0, NULL); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(&sensor->client->dev); + pm_runtime_put_autosuspend(&sensor->client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops mt9m114_pa_ctrl_ops = { + .g_volatile_ctrl = mt9m114_pa_g_ctrl, + .s_ctrl = mt9m114_pa_s_ctrl, +}; + +static void mt9m114_pa_ctrl_update_exposure(struct mt9m114 *sensor, bool manual) +{ + /* + * Update the volatile flag on the manual exposure and gain controls. + * If the controls have switched to manual, read their current value + * from the hardware to ensure that control read and write operations + * will behave correctly + */ + if (manual) { + mt9m114_pa_g_ctrl(sensor->pa.exposure); + sensor->pa.exposure->cur.val = sensor->pa.exposure->val; + sensor->pa.exposure->flags &= ~V4L2_CTRL_FLAG_VOLATILE; + + mt9m114_pa_g_ctrl(sensor->pa.gain); + sensor->pa.gain->cur.val = sensor->pa.gain->val; + sensor->pa.gain->flags &= ~V4L2_CTRL_FLAG_VOLATILE; + } else { + sensor->pa.exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; + sensor->pa.gain->flags |= V4L2_CTRL_FLAG_VOLATILE; + } +} + +static void mt9m114_pa_ctrl_update_blanking(struct mt9m114 *sensor, + const struct v4l2_mbus_framefmt *format) +{ + unsigned int max_blank; + + /* Update the blanking controls ranges based on the output size. */ + max_blank = MT9M114_CAM_SENSOR_CFG_LINE_LENGTH_PCK_MAX + - format->width; + __v4l2_ctrl_modify_range(sensor->pa.hblank, MT9M114_MIN_HBLANK, + max_blank, 1, MT9M114_DEF_HBLANK); + + max_blank = MT9M114_CAM_SENSOR_CFG_FRAME_LENGTH_LINES_MAX + - format->height; + __v4l2_ctrl_modify_range(sensor->pa.vblank, MT9M114_MIN_VBLANK, + max_blank, 1, MT9M114_DEF_VBLANK); +} + +/* ----------------------------------------------------------------------------- + * Pixel Array Subdev Operations + */ + +static inline struct mt9m114 *pa_to_mt9m114(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9m114, pa.sd); +} + +static int mt9m114_pa_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + crop = v4l2_subdev_get_pad_crop(sd, state, 0); + + crop->left = 0; + crop->top = 0; + crop->width = MT9M114_PIXEL_ARRAY_WIDTH; + crop->height = MT9M114_PIXEL_ARRAY_HEIGHT; + + format = v4l2_subdev_get_pad_format(sd, state, 0); + + format->width = MT9M114_PIXEL_ARRAY_WIDTH; + format->height = MT9M114_PIXEL_ARRAY_HEIGHT; + format->code = MEDIA_BUS_FMT_SGRBG10_1X10; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_RAW; + format->ycbcr_enc = V4L2_YCBCR_ENC_601; + format->quantization = V4L2_QUANTIZATION_FULL_RANGE; + format->xfer_func = V4L2_XFER_FUNC_NONE; + + return 0; +} + +static int mt9m114_pa_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int mt9m114_pa_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index > 1) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + /* Report binning capability through frame size enumeration. */ + fse->min_width = MT9M114_PIXEL_ARRAY_WIDTH / (fse->index + 1); + fse->max_width = MT9M114_PIXEL_ARRAY_WIDTH / (fse->index + 1); + fse->min_height = MT9M114_PIXEL_ARRAY_HEIGHT / (fse->index + 1); + fse->max_height = MT9M114_PIXEL_ARRAY_HEIGHT / (fse->index + 1); + + return 0; +} + +static int mt9m114_pa_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct mt9m114 *sensor = pa_to_mt9m114(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + unsigned int hscale; + unsigned int vscale; + + crop = v4l2_subdev_get_pad_crop(sd, state, fmt->pad); + format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); + + /* The sensor can bin horizontally and vertically. */ + hscale = DIV_ROUND_CLOSEST(crop->width, fmt->format.width ? : 1); + vscale = DIV_ROUND_CLOSEST(crop->height, fmt->format.height ? : 1); + format->width = crop->width / clamp(hscale, 1U, 2U); + format->height = crop->height / clamp(vscale, 1U, 2U); + + fmt->format = *format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + mt9m114_pa_ctrl_update_blanking(sensor, format); + + return 0; +} + +static int mt9m114_pa_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_get_pad_crop(sd, state, sel->pad); + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = MT9M114_PIXEL_ARRAY_WIDTH; + sel->r.height = MT9M114_PIXEL_ARRAY_HEIGHT; + return 0; + + default: + return -EINVAL; + } +} + +static int mt9m114_pa_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct mt9m114 *sensor = pa_to_mt9m114(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + crop = v4l2_subdev_get_pad_crop(sd, state, sel->pad); + format = v4l2_subdev_get_pad_format(sd, state, sel->pad); + + /* + * Clamp the crop rectangle. The vertical coordinates must be even, and + * the horizontal coordinates must be a multiple of 4. + * + * FIXME: The horizontal coordinates must be a multiple of 8 when + * binning, but binning is configured after setting the selection, so + * we can't know tell here if it will be used. + */ + crop->left = ALIGN(sel->r.left, 4); + crop->top = ALIGN(sel->r.top, 2); + crop->width = clamp_t(unsigned int, ALIGN(sel->r.width, 4), + MT9M114_PIXEL_ARRAY_MIN_OUTPUT_WIDTH, + MT9M114_PIXEL_ARRAY_WIDTH - crop->left); + crop->height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT, + MT9M114_PIXEL_ARRAY_HEIGHT - crop->top); + + sel->r = *crop; + + /* Reset the format. */ + format->width = crop->width; + format->height = crop->height; + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) + mt9m114_pa_ctrl_update_blanking(sensor, format); + + return 0; +} + +static const struct v4l2_subdev_pad_ops mt9m114_pa_pad_ops = { + .init_cfg = mt9m114_pa_init_cfg, + .enum_mbus_code = mt9m114_pa_enum_mbus_code, + .enum_frame_size = mt9m114_pa_enum_framesizes, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mt9m114_pa_set_fmt, + .get_selection = mt9m114_pa_get_selection, + .set_selection = mt9m114_pa_set_selection, +}; + +static const struct v4l2_subdev_ops mt9m114_pa_ops = { + .pad = &mt9m114_pa_pad_ops, +}; + +static int mt9m114_pa_init(struct mt9m114 *sensor) +{ + struct v4l2_ctrl_handler *hdl = &sensor->pa.hdl; + struct v4l2_subdev *sd = &sensor->pa.sd; + struct media_pad *pads = &sensor->pa.pad; + const struct v4l2_mbus_framefmt *format; + struct v4l2_subdev_state *state; + unsigned int max_exposure; + int ret; + + /* Initialize the subdev. */ + v4l2_subdev_init(sd, &mt9m114_pa_ops); + v4l2_i2c_subdev_set_name(sd, sensor->client, NULL, " pixel array"); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->owner = THIS_MODULE; + sd->dev = &sensor->client->dev; + v4l2_set_subdevdata(sd, sensor->client); + + /* Initialize the media entity. */ + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + sd->entity.ops = &mt9m114_entity_ops; + pads[0].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, 1, pads); + if (ret < 0) + return ret; + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(hdl, 7); + + /* The range of the HBLANK and VBLANK controls will be updated below. */ + sensor->pa.hblank = v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, + V4L2_CID_HBLANK, + MT9M114_DEF_HBLANK, + MT9M114_DEF_HBLANK, 1, + MT9M114_DEF_HBLANK); + sensor->pa.vblank = v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, + V4L2_CID_VBLANK, + MT9M114_DEF_VBLANK, + MT9M114_DEF_VBLANK, 1, + MT9M114_DEF_VBLANK); + + /* + * The maximum coarse integration time is the frame length in lines + * minus two. The default is taken directly from the datasheet, but + * makes little sense as auto-exposure is enabled by default. + */ + max_exposure = MT9M114_PIXEL_ARRAY_HEIGHT + MT9M114_MIN_VBLANK - 2; + sensor->pa.exposure = v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, + V4L2_CID_EXPOSURE, 1, + max_exposure, 1, 16); + if (sensor->pa.exposure) + sensor->pa.exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; + + sensor->pa.gain = v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, 1, + 511, 1, 32); + if (sensor->pa.gain) + sensor->pa.gain->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, + V4L2_CID_PIXEL_RATE, + sensor->pixrate, sensor->pixrate, 1, + sensor->pixrate); + + v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, + V4L2_CID_HFLIP, + 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, + V4L2_CID_VFLIP, + 0, 1, 1, 0); + + if (hdl->error) { + ret = hdl->error; + goto error; + } + + sd->state_lock = hdl->lock; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto error; + + /* Update the range of the blanking controls based on the format. */ + state = v4l2_subdev_lock_and_get_active_state(sd); + format = v4l2_subdev_get_pad_format(sd, state, 0); + mt9m114_pa_ctrl_update_blanking(sensor, format); + v4l2_subdev_unlock_state(state); + + sd->ctrl_handler = hdl; + + return 0; + +error: + v4l2_ctrl_handler_free(&sensor->pa.hdl); + media_entity_cleanup(&sensor->pa.sd.entity); + return ret; +} + +static void mt9m114_pa_cleanup(struct mt9m114 *sensor) +{ + v4l2_ctrl_handler_free(&sensor->pa.hdl); + media_entity_cleanup(&sensor->pa.sd.entity); +} + +/* ----------------------------------------------------------------------------- + * Image Flow Processor Control Operations + */ + +static const char * const mt9m114_test_pattern_menu[] = { + "Disabled", + "Solid Color", + "100% Color Bars", + "Pseudo-Random", + "Fade-to-Gray Color Bars", + "Walking Ones 10-bit", + "Walking Ones 8-bit", +}; + +/* Keep in sync with mt9m114_test_pattern_menu */ +static const unsigned int mt9m114_test_pattern_value[] = { + MT9M114_CAM_MODE_TEST_PATTERN_SELECT_SOLID, + MT9M114_CAM_MODE_TEST_PATTERN_SELECT_SOLID_BARS, + MT9M114_CAM_MODE_TEST_PATTERN_SELECT_RANDOM, + MT9M114_CAM_MODE_TEST_PATTERN_SELECT_FADING_BARS, + MT9M114_CAM_MODE_TEST_PATTERN_SELECT_WALKING_1S_10B, + MT9M114_CAM_MODE_TEST_PATTERN_SELECT_WALKING_1S_8B, +}; + +static inline struct mt9m114 *ifp_ctrl_to_mt9m114(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mt9m114, ifp.hdl); +} + +static int mt9m114_ifp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m114 *sensor = ifp_ctrl_to_mt9m114(ctrl); + u32 value; + int ret = 0; + + if (ctrl->id == V4L2_CID_EXPOSURE_AUTO) + mt9m114_pa_ctrl_update_exposure(sensor, + ctrl->val != V4L2_EXPOSURE_AUTO); + + /* V4L2 controls values are applied only when power is up. */ + if (!pm_runtime_get_if_in_use(&sensor->client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + /* Control both the AWB mode and the CCM algorithm. */ + if (ctrl->val) + value = MT9M114_CAM_AWB_MODE_AUTO + | MT9M114_CAM_AWB_MODE_EXCLUSIVE_AE; + else + value = 0; + + cci_write(sensor->regmap, MT9M114_CAM_AWB_AWBMODE, value, &ret); + + if (ctrl->val) + value = MT9M114_CCM_EXEC_CALC_CCM_MATRIX | 0x22; + else + value = 0; + + cci_write(sensor->regmap, MT9M114_CCM_ALGO, value, &ret); + break; + + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->val == V4L2_EXPOSURE_AUTO) + value = MT9M114_AE_TRACK_EXEC_AUTOMATIC_EXPOSURE + | 0x00fe; + else + value = 0; + + cci_write(sensor->regmap, MT9M114_AE_TRACK_ALGO, value, &ret); + if (ret) + break; + + break; + + case V4L2_CID_TEST_PATTERN: + case V4L2_CID_TEST_PATTERN_RED: + case V4L2_CID_TEST_PATTERN_GREENR: + case V4L2_CID_TEST_PATTERN_BLUE: { + unsigned int pattern = sensor->ifp.tpg[MT9M114_TPG_PATTERN]->val; + + if (pattern) { + cci_write(sensor->regmap, MT9M114_CAM_MODE_SELECT, + MT9M114_CAM_MODE_SELECT_TEST_PATTERN, &ret); + cci_write(sensor->regmap, + MT9M114_CAM_MODE_TEST_PATTERN_SELECT, + mt9m114_test_pattern_value[pattern - 1], &ret); + cci_write(sensor->regmap, + MT9M114_CAM_MODE_TEST_PATTERN_RED, + sensor->ifp.tpg[MT9M114_TPG_RED]->val, &ret); + cci_write(sensor->regmap, + MT9M114_CAM_MODE_TEST_PATTERN_GREEN, + sensor->ifp.tpg[MT9M114_TPG_GREEN]->val, &ret); + cci_write(sensor->regmap, + MT9M114_CAM_MODE_TEST_PATTERN_BLUE, + sensor->ifp.tpg[MT9M114_TPG_BLUE]->val, &ret); + } else { + cci_write(sensor->regmap, MT9M114_CAM_MODE_SELECT, + MT9M114_CAM_MODE_SELECT_NORMAL, &ret); + } + + /* + * A Config-Change needs to be issued for the change to take + * effect. If we're not streaming ignore this, the change will + * be applied when the stream is started. + */ + if (ret || !sensor->streaming) + break; + + ret = mt9m114_set_state(sensor, + MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE); + break; + } + + default: + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(&sensor->client->dev); + pm_runtime_put_autosuspend(&sensor->client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops mt9m114_ifp_ctrl_ops = { + .s_ctrl = mt9m114_ifp_s_ctrl, +}; + +/* ----------------------------------------------------------------------------- + * Image Flow Processor Subdev Operations + */ + +static inline struct mt9m114 *ifp_to_mt9m114(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9m114, ifp.sd); +} + +static int mt9m114_ifp_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + struct v4l2_subdev_state *pa_state; + struct v4l2_subdev_state *ifp_state; + int ret; + + if (!enable) + return mt9m114_stop_streaming(sensor); + + ifp_state = v4l2_subdev_lock_and_get_active_state(&sensor->ifp.sd); + pa_state = v4l2_subdev_lock_and_get_active_state(&sensor->pa.sd); + + ret = mt9m114_start_streaming(sensor, pa_state, ifp_state); + + v4l2_subdev_unlock_state(pa_state); + v4l2_subdev_unlock_state(ifp_state); + + return ret; +} + +static int mt9m114_ifp_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *interval) +{ + struct v4l2_fract *ival = &interval->interval; + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + + mutex_lock(sensor->ifp.hdl.lock); + + ival->numerator = 1; + ival->denominator = sensor->ifp.frame_rate; + + mutex_unlock(sensor->ifp.hdl.lock); + + return 0; +} + +static int mt9m114_ifp_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *interval) +{ + struct v4l2_fract *ival = &interval->interval; + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + int ret = 0; + + mutex_lock(sensor->ifp.hdl.lock); + + if (ival->numerator != 0 && ival->denominator != 0) + sensor->ifp.frame_rate = min_t(unsigned int, + ival->denominator / ival->numerator, + MT9M114_MAX_FRAME_RATE); + else + sensor->ifp.frame_rate = MT9M114_MAX_FRAME_RATE; + + ival->numerator = 1; + ival->denominator = sensor->ifp.frame_rate; + + if (sensor->streaming) + ret = mt9m114_set_frame_rate(sensor); + + mutex_unlock(sensor->ifp.hdl.lock); + + return ret; +} + +static int mt9m114_ifp_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + struct v4l2_rect *compose; + + format = v4l2_subdev_get_pad_format(sd, state, 0); + + format->width = MT9M114_PIXEL_ARRAY_WIDTH; + format->height = MT9M114_PIXEL_ARRAY_HEIGHT; + format->code = MEDIA_BUS_FMT_SGRBG10_1X10; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_RAW; + format->ycbcr_enc = V4L2_YCBCR_ENC_601; + format->quantization = V4L2_QUANTIZATION_FULL_RANGE; + format->xfer_func = V4L2_XFER_FUNC_NONE; + + crop = v4l2_subdev_get_pad_crop(sd, state, 0); + + crop->left = 4; + crop->top = 4; + crop->width = format->width - 8; + crop->height = format->height - 8; + + compose = v4l2_subdev_get_pad_compose(sd, state, 0); + + compose->left = 0; + compose->top = 0; + compose->width = crop->width; + compose->height = crop->height; + + format = v4l2_subdev_get_pad_format(sd, state, 1); + + format->width = compose->width; + format->height = compose->height; + format->code = mt9m114_default_format_info(sensor)->code; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + format->quantization = V4L2_QUANTIZATION_DEFAULT; + format->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static int mt9m114_ifp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const unsigned int num_formats = ARRAY_SIZE(mt9m114_format_infos); + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + unsigned int index = 0; + unsigned int flag; + unsigned int i; + + switch (code->pad) { + case 0: + if (code->index != 0) + return -EINVAL; + + code->code = mt9m114_format_infos[num_formats - 1].code; + return 0; + + case 1: + if (sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY) + flag = MT9M114_FMT_FLAG_CSI2; + else + flag = MT9M114_FMT_FLAG_PARALLEL; + + for (i = 0; i < num_formats; ++i) { + const struct mt9m114_format_info *info = + &mt9m114_format_infos[i]; + + if (info->flags & flag) { + if (index == code->index) { + code->code = info->code; + return 0; + } + + index++; + } + } + + return -EINVAL; + + default: + return -EINVAL; + } +} + +static int mt9m114_ifp_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + const struct mt9m114_format_info *info; + + if (fse->index > 0) + return -EINVAL; + + info = mt9m114_format_info(sensor, fse->pad, fse->code); + if (!info || info->code != fse->code) + return -EINVAL; + + if (fse->pad == 0) { + fse->min_width = MT9M114_PIXEL_ARRAY_MIN_OUTPUT_WIDTH; + fse->max_width = MT9M114_PIXEL_ARRAY_WIDTH; + fse->min_height = MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT; + fse->max_height = MT9M114_PIXEL_ARRAY_HEIGHT; + } else { + const struct v4l2_rect *crop; + + crop = v4l2_subdev_get_pad_crop(sd, state, 0); + + fse->max_width = crop->width; + fse->max_height = crop->height; + + fse->min_width = fse->max_width / 4; + fse->min_height = fse->max_height / 4; + } + + return 0; +} + +static int mt9m114_ifp_enum_frameintervals(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + const struct mt9m114_format_info *info; + + if (fie->index > 0) + return -EINVAL; + + info = mt9m114_format_info(sensor, fie->pad, fie->code); + if (!info || info->code != fie->code) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = MT9M114_MAX_FRAME_RATE; + + return 0; +} + +static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); + + if (fmt->pad == 0) { + /* Only the size can be changed on the sink pad. */ + format->width = clamp(ALIGN(fmt->format.width, 8), + MT9M114_PIXEL_ARRAY_MIN_OUTPUT_WIDTH, + MT9M114_PIXEL_ARRAY_WIDTH); + format->height = clamp(ALIGN(fmt->format.height, 8), + MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT, + MT9M114_PIXEL_ARRAY_HEIGHT); + } else { + const struct mt9m114_format_info *info; + + /* Only the media bus code can be changed on the source pad. */ + info = mt9m114_format_info(sensor, 1, fmt->format.code); + + format->code = info->code; + + /* If the output format is RAW10, bypass the scaler. */ + if (format->code == MEDIA_BUS_FMT_SGRBG10_1X10) + *format = *v4l2_subdev_get_pad_format(sd, state, 0); + } + + fmt->format = *format; + + return 0; +} + +static int mt9m114_ifp_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + const struct v4l2_mbus_framefmt *format; + const struct v4l2_rect *crop; + int ret = 0; + + /* Crop and compose are only supported on the sink pad. */ + if (sel->pad != 0) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_get_pad_crop(sd, state, 0); + break; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + /* + * The crop default and bounds are equal to the sink + * format size minus 4 pixels on each side for demosaicing. + */ + format = v4l2_subdev_get_pad_format(sd, state, 0); + + sel->r.left = 4; + sel->r.top = 4; + sel->r.width = format->width - 8; + sel->r.height = format->height - 8; + break; + + case V4L2_SEL_TGT_COMPOSE: + sel->r = *v4l2_subdev_get_pad_compose(sd, state, 0); + break; + + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + /* + * The compose default and bounds sizes are equal to the sink + * crop rectangle size. + */ + crop = v4l2_subdev_get_pad_crop(sd, state, 0); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = crop->width; + sel->r.height = crop->height; + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + struct v4l2_rect *compose; + + if (sel->target != V4L2_SEL_TGT_CROP && + sel->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + + /* Crop and compose are only supported on the sink pad. */ + if (sel->pad != 0) + return -EINVAL; + + format = v4l2_subdev_get_pad_format(sd, state, 0); + crop = v4l2_subdev_get_pad_crop(sd, state, 0); + compose = v4l2_subdev_get_pad_compose(sd, state, 0); + + if (sel->target == V4L2_SEL_TGT_CROP) { + /* + * Clamp the crop rectangle. Demosaicing removes 4 pixels on + * each side of the image. + */ + crop->left = clamp_t(unsigned int, ALIGN(sel->r.left, 2), 4, + format->width - 4 - + MT9M114_SCALER_CROPPED_INPUT_WIDTH); + crop->top = clamp_t(unsigned int, ALIGN(sel->r.top, 2), 4, + format->height - 4 - + MT9M114_SCALER_CROPPED_INPUT_HEIGHT); + crop->width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), + MT9M114_SCALER_CROPPED_INPUT_WIDTH, + format->width - 4 - crop->left); + crop->height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + MT9M114_SCALER_CROPPED_INPUT_HEIGHT, + format->height - 4 - crop->top); + + sel->r = *crop; + + /* Propagate to the compose rectangle. */ + compose->width = crop->width; + compose->height = crop->height; + } else { + /* + * Clamp the compose rectangle. The scaler can only downscale. + */ + compose->left = 0; + compose->top = 0; + compose->width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), + MT9M114_SCALER_CROPPED_INPUT_WIDTH, + crop->width); + compose->height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + MT9M114_SCALER_CROPPED_INPUT_HEIGHT, + crop->height); + + sel->r = *compose; + } + + /* Propagate the compose rectangle to the source format. */ + format = v4l2_subdev_get_pad_format(sd, state, 1); + format->width = compose->width; + format->height = compose->height; + + return 0; +} + +static void mt9m114_ifp_unregistered(struct v4l2_subdev *sd) +{ + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + + v4l2_device_unregister_subdev(&sensor->pa.sd); +} + +static int mt9m114_ifp_registered(struct v4l2_subdev *sd) +{ + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + int ret; + + ret = v4l2_device_register_subdev(sd->v4l2_dev, &sensor->pa.sd); + if (ret < 0) { + dev_err(&sensor->client->dev, + "Failed to register pixel array subdev\n"); + return ret; + } + + ret = media_create_pad_link(&sensor->pa.sd.entity, 0, + &sensor->ifp.sd.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + dev_err(&sensor->client->dev, + "Failed to link pixel array to ifp\n"); + v4l2_device_unregister_subdev(&sensor->pa.sd); + return ret; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops mt9m114_ifp_video_ops = { + .s_stream = mt9m114_ifp_s_stream, + .g_frame_interval = mt9m114_ifp_g_frame_interval, + .s_frame_interval = mt9m114_ifp_s_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops mt9m114_ifp_pad_ops = { + .init_cfg = mt9m114_ifp_init_cfg, + .enum_mbus_code = mt9m114_ifp_enum_mbus_code, + .enum_frame_size = mt9m114_ifp_enum_framesizes, + .enum_frame_interval = mt9m114_ifp_enum_frameintervals, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mt9m114_ifp_set_fmt, + .get_selection = mt9m114_ifp_get_selection, + .set_selection = mt9m114_ifp_set_selection, +}; + +static const struct v4l2_subdev_ops mt9m114_ifp_ops = { + .video = &mt9m114_ifp_video_ops, + .pad = &mt9m114_ifp_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mt9m114_ifp_internal_ops = { + .registered = mt9m114_ifp_registered, + .unregistered = mt9m114_ifp_unregistered, +}; + +static int mt9m114_ifp_init(struct mt9m114 *sensor) +{ + struct v4l2_subdev *sd = &sensor->ifp.sd; + struct media_pad *pads = sensor->ifp.pads; + struct v4l2_ctrl_handler *hdl = &sensor->ifp.hdl; + struct v4l2_ctrl *link_freq; + int ret; + + /* Initialize the subdev. */ + v4l2_i2c_subdev_init(sd, sensor->client, &mt9m114_ifp_ops); + v4l2_i2c_subdev_set_name(sd, sensor->client, NULL, " ifp"); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->internal_ops = &mt9m114_ifp_internal_ops; + + /* Initialize the media entity. */ + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP; + sd->entity.ops = &mt9m114_entity_ops; + pads[0].flags = MEDIA_PAD_FL_SINK; + pads[1].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, 2, pads); + if (ret < 0) + return ret; + + sensor->ifp.frame_rate = MT9M114_DEF_FRAME_RATE; + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(hdl, 8); + v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + v4l2_ctrl_new_std_menu(hdl, &mt9m114_ifp_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + + link_freq = v4l2_ctrl_new_int_menu(hdl, &mt9m114_ifp_ctrl_ops, + V4L2_CID_LINK_FREQ, + sensor->bus_cfg.nr_of_link_frequencies - 1, + 0, sensor->bus_cfg.link_frequencies); + if (link_freq) + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, + V4L2_CID_PIXEL_RATE, + sensor->pixrate, sensor->pixrate, 1, + sensor->pixrate); + + sensor->ifp.tpg[MT9M114_TPG_PATTERN] = + v4l2_ctrl_new_std_menu_items(hdl, &mt9m114_ifp_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(mt9m114_test_pattern_menu) - 1, + 0, 0, mt9m114_test_pattern_menu); + sensor->ifp.tpg[MT9M114_TPG_RED] = + v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED, + 0, 1023, 1, 1023); + sensor->ifp.tpg[MT9M114_TPG_GREEN] = + v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, + V4L2_CID_TEST_PATTERN_GREENR, + 0, 1023, 1, 1023); + sensor->ifp.tpg[MT9M114_TPG_BLUE] = + v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, + V4L2_CID_TEST_PATTERN_BLUE, + 0, 1023, 1, 1023); + + v4l2_ctrl_cluster(ARRAY_SIZE(sensor->ifp.tpg), sensor->ifp.tpg); + + if (hdl->error) { + ret = hdl->error; + goto error; + } + + sd->ctrl_handler = hdl; + sd->state_lock = hdl->lock; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto error; + + return 0; + +error: + v4l2_ctrl_handler_free(&sensor->ifp.hdl); + media_entity_cleanup(&sensor->ifp.sd.entity); + return ret; +} + +static void mt9m114_ifp_cleanup(struct mt9m114 *sensor) +{ + v4l2_ctrl_handler_free(&sensor->ifp.hdl); + media_entity_cleanup(&sensor->ifp.sd.entity); +} + +/* ----------------------------------------------------------------------------- + * Power Management + */ + +static int mt9m114_power_on(struct mt9m114 *sensor) +{ + int ret; + + /* Enable power and clocks. */ + ret = regulator_bulk_enable(ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(sensor->clk); + if (ret < 0) + goto error_regulator; + + /* Perform a hard reset if available, or a soft reset otherwise. */ + if (sensor->reset) { + long freq = clk_get_rate(sensor->clk); + unsigned int duration; + + /* + * The minimum duration is 50 clock cycles, thus typically + * around 2µs. Double it to be safe. + */ + duration = DIV_ROUND_UP(2 * 50 * 1000000, freq); + + gpiod_set_value(sensor->reset, 1); + udelay(duration); + gpiod_set_value(sensor->reset, 0); + } else { + /* + * The power may have just been turned on, we need to wait for + * the sensor to be ready to accept I2C commands. + */ + usleep_range(44500, 50000); + + cci_write(sensor->regmap, MT9M114_RESET_AND_MISC_CONTROL, + MT9M114_RESET_SOC, &ret); + cci_write(sensor->regmap, MT9M114_RESET_AND_MISC_CONTROL, 0, + &ret); + + if (ret < 0) { + dev_err(&sensor->client->dev, "Soft reset failed\n"); + goto error_clock; + } + } + + /* + * Wait for the sensor to be ready to accept I2C commands by polling the + * command register to wait for initialization to complete. + */ + usleep_range(44500, 50000); + + ret = mt9m114_poll_command(sensor, MT9M114_COMMAND_REGISTER_SET_STATE); + if (ret < 0) + goto error_clock; + + if (sensor->bus_cfg.bus_type == V4L2_MBUS_PARALLEL) { + /* + * In parallel mode (OE set to low), the sensor will enter the + * streaming state after initialization. Enter the standby + * manually to stop streaming. + */ + ret = mt9m114_set_state(sensor, + MT9M114_SYS_STATE_ENTER_STANDBY); + if (ret < 0) + goto error_clock; + } + + /* + * Before issuing any Set-State command, we must ensure that the sensor + * reaches the standby mode (either initiated manually above in + * parallel mode, or automatically after reset in MIPI mode). + */ + ret = mt9m114_poll_state(sensor, MT9M114_SYS_STATE_STANDBY); + if (ret < 0) + goto error_clock; + + return 0; + +error_clock: + clk_disable_unprepare(sensor->clk); +error_regulator: + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); + return ret; +} + +static void mt9m114_power_off(struct mt9m114 *sensor) +{ + clk_disable_unprepare(sensor->clk); + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); +} + +static int __maybe_unused mt9m114_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + int ret; + + ret = mt9m114_power_on(sensor); + if (ret) + return ret; + + ret = mt9m114_initialize(sensor); + if (ret) { + mt9m114_power_off(sensor); + return ret; + } + + return 0; +} + +static int __maybe_unused mt9m114_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + + mt9m114_power_off(sensor); + + return 0; +} + +static const struct dev_pm_ops mt9m114_pm_ops = { + SET_RUNTIME_PM_OPS(mt9m114_runtime_suspend, mt9m114_runtime_resume, NULL) +}; + +/* ----------------------------------------------------------------------------- + * Probe & Remove + */ + +static int mt9m114_clk_init(struct mt9m114 *sensor) +{ + unsigned int link_freq; + + /* Hardcode the PLL multiplier and dividers to default settings. */ + sensor->pll.m = 32; + sensor->pll.n = 1; + sensor->pll.p = 7; + + /* + * Calculate the pixel rate and link frequency. The CSI-2 bus is clocked + * for 16-bit per pixel, transmitted in DDR over a single lane. For + * parallel mode, the sensor ouputs one pixel in two PIXCLK cycles. + */ + sensor->pixrate = clk_get_rate(sensor->clk) * sensor->pll.m + / ((sensor->pll.n + 1) * (sensor->pll.p + 1)); + + link_freq = sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY + ? sensor->pixrate * 8 : sensor->pixrate * 2; + + if (sensor->bus_cfg.nr_of_link_frequencies != 1 || + sensor->bus_cfg.link_frequencies[0] != link_freq) { + dev_err(&sensor->client->dev, "Unsupported DT link-frequencies\n"); + return -EINVAL; + } + + return 0; +} + +static int mt9m114_identify(struct mt9m114 *sensor) +{ + u64 major, minor, release, customer; + u64 value; + int ret; + + ret = cci_read(sensor->regmap, MT9M114_CHIP_ID, &value, NULL); + if (ret) { + dev_err(&sensor->client->dev, "Failed to read chip ID\n"); + return -ENXIO; + } + + if (value != 0x2481) { + dev_err(&sensor->client->dev, "Invalid chip ID 0x%04llx\n", + value); + return -ENXIO; + } + + cci_read(sensor->regmap, MT9M114_MON_MAJOR_VERSION, &major, &ret); + cci_read(sensor->regmap, MT9M114_MON_MINOR_VERSION, &minor, &ret); + cci_read(sensor->regmap, MT9M114_MON_RELEASE_VERSION, &release, &ret); + cci_read(sensor->regmap, MT9M114_CUSTOMER_REV, &customer, &ret); + if (ret) { + dev_err(&sensor->client->dev, "Failed to read version\n"); + return -ENXIO; + } + + dev_dbg(&sensor->client->dev, + "monitor v%llu.%llu.%04llx customer rev 0x%04llx\n", + major, minor, release, customer); + + return 0; +} + +static int mt9m114_parse_dt(struct mt9m114 *sensor) +{ + struct fwnode_handle *fwnode = dev_fwnode(&sensor->client->dev); + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) { + dev_err(&sensor->client->dev, "No endpoint found\n"); + return -EINVAL; + } + + sensor->bus_cfg.bus_type = V4L2_MBUS_UNKNOWN; + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &sensor->bus_cfg); + fwnode_handle_put(ep); + if (ret < 0) { + dev_err(&sensor->client->dev, "Failed to parse endpoint\n"); + goto error; + } + + switch (sensor->bus_cfg.bus_type) { + case V4L2_MBUS_CSI2_DPHY: + case V4L2_MBUS_PARALLEL: + break; + + default: + dev_err(&sensor->client->dev, "unsupported bus type %u\n", + sensor->bus_cfg.bus_type); + ret = -EINVAL; + goto error; + } + + return 0; + +error: + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return ret; +} + +static int mt9m114_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct mt9m114 *sensor; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->client = client; + + sensor->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(sensor->regmap)) { + dev_err(dev, "Unable to initialize I2C\n"); + return -ENODEV; + } + + ret = mt9m114_parse_dt(sensor); + if (ret < 0) + return ret; + + /* Acquire clocks, GPIOs and regulators. */ + sensor->clk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->clk)) { + ret = PTR_ERR(sensor->clk); + dev_err_probe(dev, ret, "Failed to get clock\n"); + goto error_ep_free; + } + + sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(sensor->reset)) { + ret = PTR_ERR(sensor->reset); + dev_err_probe(dev, ret, "Failed to get reset GPIO\n"); + goto error_ep_free; + } + + sensor->supplies[0].supply = "vddio"; + sensor->supplies[1].supply = "vdd"; + sensor->supplies[2].supply = "vaa"; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to get regulators\n"); + goto error_ep_free; + } + + ret = mt9m114_clk_init(sensor); + if (ret) + return ret; + + /* + * Identify the sensor. The driver supports runtime PM, but needs to + * work when runtime PM is disabled in the kernel. To that end, power + * the sensor on manually here, and initialize it after identification + * to reach the same state as if resumed through runtime PM. + */ + ret = mt9m114_power_on(sensor); + if (ret < 0) { + dev_err_probe(dev, ret, "Could not power on the device\n"); + return ret; + } + + ret = mt9m114_identify(sensor); + if (ret < 0) + goto error_power_off; + + ret = mt9m114_initialize(sensor); + if (ret < 0) + goto error_power_off; + + /* + * Enable runtime PM with autosuspend. As the device has been powered + * manually, mark it as active, and increase the usage count without + * resuming the device. + */ + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + /* Initialize the subdevices. */ + ret = mt9m114_pa_init(sensor); + if (ret < 0) + goto error_pm_cleanup; + + ret = mt9m114_ifp_init(sensor); + if (ret < 0) + goto error_pa_cleanup; + + ret = v4l2_async_register_subdev(&sensor->ifp.sd); + if (ret < 0) + goto error_ifp_cleanup; + + /* + * Decrease the PM usage count. The device will get suspended after the + * autosuspend delay, turning the power off. + */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; + +error_ifp_cleanup: + mt9m114_ifp_cleanup(sensor); +error_pa_cleanup: + mt9m114_pa_cleanup(sensor); +error_pm_cleanup: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); +error_power_off: + mt9m114_power_off(sensor); +error_ep_free: + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return ret; +} + +static void mt9m114_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct mt9m114 *sensor = ifp_to_mt9m114(sd); + struct device *dev = &client->dev; + + v4l2_async_unregister_subdev(&sensor->ifp.sd); + + mt9m114_ifp_cleanup(sensor); + mt9m114_pa_cleanup(sensor); + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + + /* + * Disable runtime PM. In case runtime PM is disabled in the kernel, + * make sure to turn power off manually. + */ + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + mt9m114_power_off(sensor); + pm_runtime_set_suspended(dev); +} + +static const struct of_device_id mt9m114_of_ids[] = { + { .compatible = "onnn,mt9m114" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mt9m114_of_ids); + +static struct i2c_driver mt9m114_driver = { + .driver = { + .name = "mt9m114", + .pm = &mt9m114_pm_ops, + .of_match_table = mt9m114_of_ids, + }, + .probe = mt9m114_probe, + .remove = mt9m114_remove, +}; + +module_i2c_driver(mt9m114_driver); + +MODULE_DESCRIPTION("onsemi MT9M114 Sensor Driver"); +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From b4a3d877dc92963a4db16ddb71df3d333c0d40bd Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Mon, 9 Oct 2023 18:39:39 +0530 Subject: media: ti: Add CSI2RX support for J721E TI's J721E uses the Cadence CSI2RX and DPHY peripherals to facilitate capture over a CSI-2 bus. The Cadence CSI2RX IP acts as a bridge between the TI specific parts and the CSI-2 protocol parts. TI then has a wrapper on top of this bridge called the SHIM layer. It takes in data from stream 0, repacks it, and sends it to memory over PSI-L DMA. This driver acts as the "front end" to V4L2 client applications. It implements the required ioctls and buffer operations, passes the necessary calls on to the bridge, programs the SHIM layer, and performs DMA via the dmaengine API to finally return the data to a buffer supplied by the application. Co-developed-by: Pratyush Yadav Signed-off-by: Pratyush Yadav Co-developed-by: Vaishnav Achath Signed-off-by: Vaishnav Achath Tested-by: Vaishnav Achath Tested-by: Julien Massot Reviewed-by: Tomi Valkeinen Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 7 + drivers/media/platform/ti/Kconfig | 12 + drivers/media/platform/ti/Makefile | 1 + drivers/media/platform/ti/j721e-csi2rx/Makefile | 2 + .../media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 1159 ++++++++++++++++++++ 5 files changed, 1181 insertions(+) create mode 100644 drivers/media/platform/ti/j721e-csi2rx/Makefile create mode 100644 drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 83a0c7f3826b..65cc6a982de9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21594,6 +21594,13 @@ F: Documentation/devicetree/bindings/media/i2c/ti,ds90* F: drivers/media/i2c/ds90* F: include/media/i2c/ds90* +TI J721E CSI2RX DRIVER +M: Jai Luthra +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/ti,j721e-csi2rx.yaml +F: drivers/media/platform/ti/j721e-csi2rx/ + TI KEYSTONE MULTICORE NAVIGATOR DRIVERS M: Nishanth Menon M: Santosh Shilimkar diff --git a/drivers/media/platform/ti/Kconfig b/drivers/media/platform/ti/Kconfig index e1ab56c3be1f..bab998c4179a 100644 --- a/drivers/media/platform/ti/Kconfig +++ b/drivers/media/platform/ti/Kconfig @@ -63,6 +63,18 @@ config VIDEO_TI_VPE_DEBUG help Enable debug messages on VPE driver. +config VIDEO_TI_J721E_CSI2RX + tristate "TI J721E CSI2RX wrapper layer driver" + depends on VIDEO_DEV && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_SUPPORT && MEDIA_CONTROLLER + depends on (PHY_CADENCE_DPHY_RX && VIDEO_CADENCE_CSI2RX) || COMPILE_TEST + depends on ARCH_K3 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Support for TI CSI2RX wrapper layer. This just enables the wrapper driver. + The Cadence CSI2RX bridge driver needs to be enabled separately. + source "drivers/media/platform/ti/am437x/Kconfig" source "drivers/media/platform/ti/davinci/Kconfig" source "drivers/media/platform/ti/omap/Kconfig" diff --git a/drivers/media/platform/ti/Makefile b/drivers/media/platform/ti/Makefile index 98c5fe5c40d6..8a2f74c9380e 100644 --- a/drivers/media/platform/ti/Makefile +++ b/drivers/media/platform/ti/Makefile @@ -3,5 +3,6 @@ obj-y += am437x/ obj-y += cal/ obj-y += vpe/ obj-y += davinci/ +obj-y += j721e-csi2rx/ obj-y += omap/ obj-y += omap3isp/ diff --git a/drivers/media/platform/ti/j721e-csi2rx/Makefile b/drivers/media/platform/ti/j721e-csi2rx/Makefile new file mode 100644 index 000000000000..377afc1d6280 --- /dev/null +++ b/drivers/media/platform/ti/j721e-csi2rx/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_VIDEO_TI_J721E_CSI2RX) += j721e-csi2rx.o diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c new file mode 100644 index 000000000000..ada61391c8d2 --- /dev/null +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c @@ -0,0 +1,1159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI CSI2RX Shim Wrapper Driver + * + * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/ + * + * Author: Pratyush Yadav + * Author: Jai Luthra + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TI_CSI2RX_MODULE_NAME "j721e-csi2rx" + +#define SHIM_CNTL 0x10 +#define SHIM_CNTL_PIX_RST BIT(0) + +#define SHIM_DMACNTX 0x20 +#define SHIM_DMACNTX_EN BIT(31) +#define SHIM_DMACNTX_YUV422 GENMASK(27, 26) +#define SHIM_DMACNTX_SIZE GENMASK(21, 20) +#define SHIM_DMACNTX_FMT GENMASK(5, 0) +#define SHIM_DMACNTX_YUV422_MODE_11 3 +#define SHIM_DMACNTX_SIZE_8 0 +#define SHIM_DMACNTX_SIZE_16 1 +#define SHIM_DMACNTX_SIZE_32 2 + +#define SHIM_PSI_CFG0 0x24 +#define SHIM_PSI_CFG0_SRC_TAG GENMASK(15, 0) +#define SHIM_PSI_CFG0_DST_TAG GENMASK(31, 16) + +#define PSIL_WORD_SIZE_BYTES 16 +/* + * There are no hard limits on the width or height. The DMA engine can handle + * all sizes. The max width and height are arbitrary numbers for this driver. + * Use 16K * 16K as the arbitrary limit. It is large enough that it is unlikely + * the limit will be hit in practice. + */ +#define MAX_WIDTH_BYTES SZ_16K +#define MAX_HEIGHT_LINES SZ_16K + +#define DRAIN_TIMEOUT_MS 50 +#define DRAIN_BUFFER_SIZE SZ_32K + +struct ti_csi2rx_fmt { + u32 fourcc; /* Four character code. */ + u32 code; /* Mbus code. */ + u32 csi_dt; /* CSI Data type. */ + u8 bpp; /* Bits per pixel. */ + u8 size; /* Data size shift when unpacking. */ +}; + +struct ti_csi2rx_buffer { + /* Common v4l2 buffer. Must be first. */ + struct vb2_v4l2_buffer vb; + struct list_head list; + struct ti_csi2rx_dev *csi; +}; + +enum ti_csi2rx_dma_state { + TI_CSI2RX_DMA_STOPPED, /* Streaming not started yet. */ + TI_CSI2RX_DMA_IDLE, /* Streaming but no pending DMA operation. */ + TI_CSI2RX_DMA_ACTIVE, /* Streaming and pending DMA operation. */ +}; + +struct ti_csi2rx_dma { + /* Protects all fields in this struct. */ + spinlock_t lock; + struct dma_chan *chan; + /* Buffers queued to the driver, waiting to be processed by DMA. */ + struct list_head queue; + enum ti_csi2rx_dma_state state; + /* + * Queue of buffers submitted to DMA engine. + */ + struct list_head submitted; + /* Buffer to drain stale data from PSI-L endpoint */ + struct { + void *vaddr; + dma_addr_t paddr; + size_t len; + } drain; +}; + +struct ti_csi2rx_dev { + struct device *dev; + void __iomem *shim; + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct media_device mdev; + struct media_pipeline pipe; + struct media_pad pad; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *source; + struct vb2_queue vidq; + struct mutex mutex; /* To serialize ioctls. */ + struct v4l2_format v_fmt; + struct ti_csi2rx_dma dma; + u32 sequence; +}; + +static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUYV, + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_8, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_8, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .code = MEDIA_BUS_FMT_YVYU8_1X16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_8, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .code = MEDIA_BUS_FMT_VYUY8_1X16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_8, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .bpp = 8, + .size = SHIM_DMACNTX_SIZE_8, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .bpp = 8, + .size = SHIM_DMACNTX_SIZE_8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .bpp = 8, + .size = SHIM_DMACNTX_SIZE_8, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .bpp = 8, + .size = SHIM_DMACNTX_SIZE_8, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, + + /* More formats can be supported but they are not listed for now. */ +}; + +/* Forward declaration needed by ti_csi2rx_dma_callback. */ +static int ti_csi2rx_start_dma(struct ti_csi2rx_dev *csi, + struct ti_csi2rx_buffer *buf); + +static const struct ti_csi2rx_fmt *find_format_by_fourcc(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ti_csi2rx_formats); i++) { + if (ti_csi2rx_formats[i].fourcc == pixelformat) + return &ti_csi2rx_formats[i]; + } + + return NULL; +} + +static const struct ti_csi2rx_fmt *find_format_by_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ti_csi2rx_formats); i++) { + if (ti_csi2rx_formats[i].code == code) + return &ti_csi2rx_formats[i]; + } + + return NULL; +} + +static void ti_csi2rx_fill_fmt(const struct ti_csi2rx_fmt *csi_fmt, + struct v4l2_format *v4l2_fmt) +{ + struct v4l2_pix_format *pix = &v4l2_fmt->fmt.pix; + unsigned int pixels_in_word; + + pixels_in_word = PSIL_WORD_SIZE_BYTES * 8 / csi_fmt->bpp; + + /* Clamp width and height to sensible maximums (16K x 16K) */ + pix->width = clamp_t(unsigned int, pix->width, + pixels_in_word, + MAX_WIDTH_BYTES * 8 / csi_fmt->bpp); + pix->height = clamp_t(unsigned int, pix->height, 1, MAX_HEIGHT_LINES); + + /* Width should be a multiple of transfer word-size */ + pix->width = rounddown(pix->width, pixels_in_word); + + v4l2_fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pix->pixelformat = csi_fmt->fourcc; + pix->bytesperline = pix->width * (csi_fmt->bpp / 8); + pix->sizeimage = pix->bytesperline * pix->height; +} + +static int ti_csi2rx_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, TI_CSI2RX_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, TI_CSI2RX_MODULE_NAME, sizeof(cap->card)); + + return 0; +} + +static int ti_csi2rx_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct ti_csi2rx_fmt *fmt = NULL; + + if (f->mbus_code) { + /* 1-to-1 mapping between bus formats and pixel formats */ + if (f->index > 0) + return -EINVAL; + + fmt = find_format_by_code(f->mbus_code); + } else { + if (f->index >= ARRAY_SIZE(ti_csi2rx_formats)) + return -EINVAL; + + fmt = &ti_csi2rx_formats[f->index]; + } + + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->fourcc; + memset(f->reserved, 0, sizeof(f->reserved)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int ti_csi2rx_g_fmt_vid_cap(struct file *file, void *prov, + struct v4l2_format *f) +{ + struct ti_csi2rx_dev *csi = video_drvdata(file); + + *f = csi->v_fmt; + + return 0; +} + +static int ti_csi2rx_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + const struct ti_csi2rx_fmt *fmt; + + /* + * Default to the first format if the requested pixel format code isn't + * supported. + */ + fmt = find_format_by_fourcc(f->fmt.pix.pixelformat); + if (!fmt) + fmt = &ti_csi2rx_formats[0]; + + /* Interlaced formats are not supported. */ + f->fmt.pix.field = V4L2_FIELD_NONE; + + ti_csi2rx_fill_fmt(fmt, f); + + return 0; +} + +static int ti_csi2rx_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ti_csi2rx_dev *csi = video_drvdata(file); + struct vb2_queue *q = &csi->vidq; + int ret; + + if (vb2_is_busy(q)) + return -EBUSY; + + ret = ti_csi2rx_try_fmt_vid_cap(file, priv, f); + if (ret < 0) + return ret; + + csi->v_fmt = *f; + + return 0; +} + +static int ti_csi2rx_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct ti_csi2rx_fmt *fmt; + unsigned int pixels_in_word; + + fmt = find_format_by_fourcc(fsize->pixel_format); + if (!fmt || fsize->index != 0) + return -EINVAL; + + /* + * Number of pixels in one PSI-L word. The transfer happens in multiples + * of PSI-L word sizes. + */ + pixels_in_word = PSIL_WORD_SIZE_BYTES * 8 / fmt->bpp; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = pixels_in_word; + fsize->stepwise.max_width = rounddown(MAX_WIDTH_BYTES * 8 / fmt->bpp, + pixels_in_word); + fsize->stepwise.step_width = pixels_in_word; + fsize->stepwise.min_height = 1; + fsize->stepwise.max_height = MAX_HEIGHT_LINES; + fsize->stepwise.step_height = 1; + + return 0; +} + +static const struct v4l2_ioctl_ops csi_ioctl_ops = { + .vidioc_querycap = ti_csi2rx_querycap, + .vidioc_enum_fmt_vid_cap = ti_csi2rx_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = ti_csi2rx_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = ti_csi2rx_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = ti_csi2rx_s_fmt_vid_cap, + .vidioc_enum_framesizes = ti_csi2rx_enum_framesizes, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static const struct v4l2_file_operations csi_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int csi_async_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asc) +{ + struct ti_csi2rx_dev *csi = dev_get_drvdata(notifier->v4l2_dev->dev); + + csi->source = subdev; + + return 0; +} + +static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct ti_csi2rx_dev *csi = dev_get_drvdata(notifier->v4l2_dev->dev); + struct video_device *vdev = &csi->vdev; + int ret; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) + return ret; + + ret = v4l2_create_fwnode_links_to_pad(csi->source, &csi->pad, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + + if (ret) { + video_unregister_device(vdev); + return ret; + } + + ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev); + if (ret) + video_unregister_device(vdev); + + return ret; +} + +static const struct v4l2_async_notifier_operations csi_async_notifier_ops = { + .bound = csi_async_notifier_bound, + .complete = csi_async_notifier_complete, +}; + +static int ti_csi2rx_notifier_register(struct ti_csi2rx_dev *csi) +{ + struct fwnode_handle *fwnode; + struct v4l2_async_connection *asc; + struct device_node *node; + int ret; + + node = of_get_child_by_name(csi->dev->of_node, "csi-bridge"); + if (!node) + return -EINVAL; + + fwnode = of_fwnode_handle(node); + if (!fwnode) { + of_node_put(node); + return -EINVAL; + } + + v4l2_async_nf_init(&csi->notifier, &csi->v4l2_dev); + csi->notifier.ops = &csi_async_notifier_ops; + + asc = v4l2_async_nf_add_fwnode(&csi->notifier, fwnode, + struct v4l2_async_connection); + of_node_put(node); + if (IS_ERR(asc)) { + v4l2_async_nf_cleanup(&csi->notifier); + return PTR_ERR(asc); + } + + ret = v4l2_async_nf_register(&csi->notifier); + if (ret) { + v4l2_async_nf_cleanup(&csi->notifier); + return ret; + } + + return 0; +} + +static void ti_csi2rx_setup_shim(struct ti_csi2rx_dev *csi) +{ + const struct ti_csi2rx_fmt *fmt; + unsigned int reg; + + fmt = find_format_by_fourcc(csi->v_fmt.fmt.pix.pixelformat); + + /* De-assert the pixel interface reset. */ + reg = SHIM_CNTL_PIX_RST; + writel(reg, csi->shim + SHIM_CNTL); + + reg = SHIM_DMACNTX_EN; + reg |= FIELD_PREP(SHIM_DMACNTX_FMT, fmt->csi_dt); + + /* + * The hardware assumes incoming YUV422 8-bit data on MIPI CSI2 bus + * follows the spec and is packed in the order U0 -> Y0 -> V0 -> Y1 -> + * ... + * + * There is an option to swap the bytes around before storing in + * memory, to achieve different pixel formats: + * + * Byte3 <----------- Byte0 + * [ Y1 ][ V0 ][ Y0 ][ U0 ] MODE 11 + * [ Y1 ][ U0 ][ Y0 ][ V0 ] MODE 10 + * [ V0 ][ Y1 ][ U0 ][ Y0 ] MODE 01 + * [ U0 ][ Y1 ][ V0 ][ Y0 ] MODE 00 + * + * We don't have any requirement to change pixelformat from what is + * coming from the source, so we keep it in MODE 11, which does not + * swap any bytes when storing in memory. + */ + switch (fmt->fourcc) { + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + reg |= FIELD_PREP(SHIM_DMACNTX_YUV422, + SHIM_DMACNTX_YUV422_MODE_11); + break; + default: + /* Ignore if not YUV 4:2:2 */ + break; + } + + reg |= FIELD_PREP(SHIM_DMACNTX_SIZE, fmt->size); + + writel(reg, csi->shim + SHIM_DMACNTX); + + reg = FIELD_PREP(SHIM_PSI_CFG0_SRC_TAG, 0) | + FIELD_PREP(SHIM_PSI_CFG0_DST_TAG, 0); + writel(reg, csi->shim + SHIM_PSI_CFG0); +} + +static void ti_csi2rx_drain_callback(void *param) +{ + struct completion *drain_complete = param; + + complete(drain_complete); +} + +/* + * Drain the stale data left at the PSI-L endpoint. + * + * This might happen if no buffers are queued in time but source is still + * streaming. In multi-stream scenarios this can happen when one stream is + * stopped but other is still streaming, and thus module-level pixel reset is + * not asserted. + * + * To prevent that stale data corrupting the subsequent transactions, it is + * required to issue DMA requests to drain it out. + */ +static int ti_csi2rx_drain_dma(struct ti_csi2rx_dev *csi) +{ + struct dma_async_tx_descriptor *desc; + struct completion drain_complete; + dma_cookie_t cookie; + int ret; + + init_completion(&drain_complete); + + desc = dmaengine_prep_slave_single(csi->dma.chan, csi->dma.drain.paddr, + csi->dma.drain.len, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + ret = -EIO; + goto out; + } + + desc->callback = ti_csi2rx_drain_callback; + desc->callback_param = &drain_complete; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) + goto out; + + dma_async_issue_pending(csi->dma.chan); + + if (!wait_for_completion_timeout(&drain_complete, + msecs_to_jiffies(DRAIN_TIMEOUT_MS))) { + dmaengine_terminate_sync(csi->dma.chan); + dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n"); + ret = -ETIMEDOUT; + goto out; + } +out: + return ret; +} + +static void ti_csi2rx_dma_callback(void *param) +{ + struct ti_csi2rx_buffer *buf = param; + struct ti_csi2rx_dev *csi = buf->csi; + struct ti_csi2rx_dma *dma = &csi->dma; + unsigned long flags; + + /* + * TODO: Derive the sequence number from the CSI2RX frame number + * hardware monitor registers. + */ + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = csi->sequence++; + + spin_lock_irqsave(&dma->lock, flags); + + WARN_ON(!list_is_first(&buf->list, &dma->submitted)); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + list_del(&buf->list); + + /* If there are more buffers to process then start their transfer. */ + while (!list_empty(&dma->queue)) { + buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list); + + if (ti_csi2rx_start_dma(csi, buf)) { + dev_err(csi->dev, "Failed to queue the next buffer for DMA\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } else { + list_move_tail(&buf->list, &dma->submitted); + } + } + + if (list_empty(&dma->submitted)) + dma->state = TI_CSI2RX_DMA_IDLE; + + spin_unlock_irqrestore(&dma->lock, flags); +} + +static int ti_csi2rx_start_dma(struct ti_csi2rx_dev *csi, + struct ti_csi2rx_buffer *buf) +{ + unsigned long addr; + struct dma_async_tx_descriptor *desc; + size_t len = csi->v_fmt.fmt.pix.sizeimage; + dma_cookie_t cookie; + int ret = 0; + + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + desc = dmaengine_prep_slave_single(csi->dma.chan, addr, len, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + return -EIO; + + desc->callback = ti_csi2rx_dma_callback; + desc->callback_param = buf; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) + return ret; + + dma_async_issue_pending(csi->dma.chan); + + return 0; +} + +static void ti_csi2rx_stop_dma(struct ti_csi2rx_dev *csi) +{ + struct ti_csi2rx_dma *dma = &csi->dma; + enum ti_csi2rx_dma_state state; + unsigned long flags; + int ret; + + spin_lock_irqsave(&dma->lock, flags); + state = csi->dma.state; + dma->state = TI_CSI2RX_DMA_STOPPED; + spin_unlock_irqrestore(&dma->lock, flags); + + if (state != TI_CSI2RX_DMA_STOPPED) { + /* + * Normal DMA termination does not clean up pending data on + * the endpoint if multiple streams are running and only one + * is stopped, as the module-level pixel reset cannot be + * enforced before terminating DMA. + */ + ret = ti_csi2rx_drain_dma(csi); + if (ret && ret != -ETIMEDOUT) + dev_warn(csi->dev, + "Failed to drain DMA. Next frame might be bogus\n"); + } + + ret = dmaengine_terminate_sync(csi->dma.chan); + if (ret) + dev_err(csi->dev, "Failed to stop DMA: %d\n", ret); +} + +static void ti_csi2rx_cleanup_buffers(struct ti_csi2rx_dev *csi, + enum vb2_buffer_state state) +{ + struct ti_csi2rx_dma *dma = &csi->dma; + struct ti_csi2rx_buffer *buf, *tmp; + unsigned long flags; + + spin_lock_irqsave(&dma->lock, flags); + list_for_each_entry_safe(buf, tmp, &csi->dma.queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + list_for_each_entry_safe(buf, tmp, &csi->dma.submitted, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + spin_unlock_irqrestore(&dma->lock, flags); +} + +static int ti_csi2rx_queue_setup(struct vb2_queue *q, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct ti_csi2rx_dev *csi = vb2_get_drv_priv(q); + unsigned int size = csi->v_fmt.fmt.pix.sizeimage; + + if (*nplanes) { + if (sizes[0] < size) + return -EINVAL; + size = sizes[0]; + } + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int ti_csi2rx_buffer_prepare(struct vb2_buffer *vb) +{ + struct ti_csi2rx_dev *csi = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = csi->v_fmt.fmt.pix.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(csi->dev, "Data will not fit into plane\n"); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + return 0; +} + +static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb) +{ + struct ti_csi2rx_dev *csi = vb2_get_drv_priv(vb->vb2_queue); + struct ti_csi2rx_buffer *buf; + struct ti_csi2rx_dma *dma = &csi->dma; + bool restart_dma = false; + unsigned long flags = 0; + int ret; + + buf = container_of(vb, struct ti_csi2rx_buffer, vb.vb2_buf); + buf->csi = csi; + + spin_lock_irqsave(&dma->lock, flags); + /* + * Usually the DMA callback takes care of queueing the pending buffers. + * But if DMA has stalled due to lack of buffers, restart it now. + */ + if (dma->state == TI_CSI2RX_DMA_IDLE) { + /* + * Do not restart DMA with the lock held because + * ti_csi2rx_drain_dma() might block for completion. + * There won't be a race on queueing DMA anyway since the + * callback is not being fired. + */ + restart_dma = true; + dma->state = TI_CSI2RX_DMA_ACTIVE; + } else { + list_add_tail(&buf->list, &dma->queue); + } + spin_unlock_irqrestore(&dma->lock, flags); + + if (restart_dma) { + /* + * Once frames start dropping, some data gets stuck in the DMA + * pipeline somewhere. So the first DMA transfer after frame + * drops gives a partial frame. This is obviously not useful to + * the application and will only confuse it. Issue a DMA + * transaction to drain that up. + */ + ret = ti_csi2rx_drain_dma(csi); + if (ret && ret != -ETIMEDOUT) + dev_warn(csi->dev, + "Failed to drain DMA. Next frame might be bogus\n"); + + ret = ti_csi2rx_start_dma(csi, buf); + if (ret) { + dev_err(csi->dev, "Failed to start DMA: %d\n", ret); + spin_lock_irqsave(&dma->lock, flags); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + dma->state = TI_CSI2RX_DMA_IDLE; + spin_unlock_irqrestore(&dma->lock, flags); + } else { + spin_lock_irqsave(&dma->lock, flags); + list_add_tail(&buf->list, &dma->submitted); + spin_unlock_irqrestore(&dma->lock, flags); + } + } +} + +static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct ti_csi2rx_dev *csi = vb2_get_drv_priv(vq); + struct ti_csi2rx_dma *dma = &csi->dma; + struct ti_csi2rx_buffer *buf; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&dma->lock, flags); + if (list_empty(&dma->queue)) + ret = -EIO; + spin_unlock_irqrestore(&dma->lock, flags); + if (ret) + return ret; + + ret = video_device_pipeline_start(&csi->vdev, &csi->pipe); + if (ret) + goto err; + + ti_csi2rx_setup_shim(csi); + + csi->sequence = 0; + + spin_lock_irqsave(&dma->lock, flags); + buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list); + + ret = ti_csi2rx_start_dma(csi, buf); + if (ret) { + dev_err(csi->dev, "Failed to start DMA: %d\n", ret); + spin_unlock_irqrestore(&dma->lock, flags); + goto err_pipeline; + } + + list_move_tail(&buf->list, &dma->submitted); + dma->state = TI_CSI2RX_DMA_ACTIVE; + spin_unlock_irqrestore(&dma->lock, flags); + + ret = v4l2_subdev_call(csi->source, video, s_stream, 1); + if (ret) + goto err_dma; + + return 0; + +err_dma: + ti_csi2rx_stop_dma(csi); +err_pipeline: + video_device_pipeline_stop(&csi->vdev); + writel(0, csi->shim + SHIM_CNTL); + writel(0, csi->shim + SHIM_DMACNTX); +err: + ti_csi2rx_cleanup_buffers(csi, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void ti_csi2rx_stop_streaming(struct vb2_queue *vq) +{ + struct ti_csi2rx_dev *csi = vb2_get_drv_priv(vq); + int ret; + + video_device_pipeline_stop(&csi->vdev); + + writel(0, csi->shim + SHIM_CNTL); + writel(0, csi->shim + SHIM_DMACNTX); + + ret = v4l2_subdev_call(csi->source, video, s_stream, 0); + if (ret) + dev_err(csi->dev, "Failed to stop subdev stream\n"); + + ti_csi2rx_stop_dma(csi); + ti_csi2rx_cleanup_buffers(csi, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops csi_vb2_qops = { + .queue_setup = ti_csi2rx_queue_setup, + .buf_prepare = ti_csi2rx_buffer_prepare, + .buf_queue = ti_csi2rx_buffer_queue, + .start_streaming = ti_csi2rx_start_streaming, + .stop_streaming = ti_csi2rx_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int ti_csi2rx_init_vb2q(struct ti_csi2rx_dev *csi) +{ + struct vb2_queue *q = &csi->vidq; + int ret; + + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = csi; + q->buf_struct_size = sizeof(struct ti_csi2rx_buffer); + q->ops = &csi_vb2_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->dev = dmaengine_get_dma_device(csi->dma.chan); + q->lock = &csi->mutex; + q->min_buffers_needed = 1; + + ret = vb2_queue_init(q); + if (ret) + return ret; + + csi->vdev.queue = q; + + return 0; +} + +static int ti_csi2rx_link_validate(struct media_link *link) +{ + struct media_entity *entity = link->sink->entity; + struct video_device *vdev = media_entity_to_video_device(entity); + struct ti_csi2rx_dev *csi = container_of(vdev, struct ti_csi2rx_dev, vdev); + struct v4l2_pix_format *csi_fmt = &csi->v_fmt.fmt.pix; + struct v4l2_subdev_format source_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = link->source->index, + }; + const struct ti_csi2rx_fmt *ti_fmt; + int ret; + + ret = v4l2_subdev_call_state_active(csi->source, pad, + get_fmt, &source_fmt); + if (ret) + return ret; + + if (source_fmt.format.width != csi_fmt->width) { + dev_dbg(csi->dev, "Width does not match (source %u, sink %u)\n", + source_fmt.format.width, csi_fmt->width); + return -EPIPE; + } + + if (source_fmt.format.height != csi_fmt->height) { + dev_dbg(csi->dev, "Height does not match (source %u, sink %u)\n", + source_fmt.format.height, csi_fmt->height); + return -EPIPE; + } + + if (source_fmt.format.field != csi_fmt->field && + csi_fmt->field != V4L2_FIELD_NONE) { + dev_dbg(csi->dev, "Field does not match (source %u, sink %u)\n", + source_fmt.format.field, csi_fmt->field); + return -EPIPE; + } + + ti_fmt = find_format_by_code(source_fmt.format.code); + if (!ti_fmt) { + dev_dbg(csi->dev, "Media bus format 0x%x not supported\n", + source_fmt.format.code); + return -EPIPE; + } + + if (ti_fmt->fourcc != csi_fmt->pixelformat) { + dev_dbg(csi->dev, + "Cannot transform source fmt 0x%x to sink fmt 0x%x\n", + ti_fmt->fourcc, csi_fmt->pixelformat); + return -EPIPE; + } + + return 0; +} + +static const struct media_entity_operations ti_csi2rx_video_entity_ops = { + .link_validate = ti_csi2rx_link_validate, +}; + +static int ti_csi2rx_init_dma(struct ti_csi2rx_dev *csi) +{ + struct dma_slave_config cfg = { + .src_addr_width = DMA_SLAVE_BUSWIDTH_16_BYTES, + }; + int ret; + + INIT_LIST_HEAD(&csi->dma.queue); + INIT_LIST_HEAD(&csi->dma.submitted); + spin_lock_init(&csi->dma.lock); + + csi->dma.state = TI_CSI2RX_DMA_STOPPED; + + csi->dma.chan = dma_request_chan(csi->dev, "rx0"); + if (IS_ERR(csi->dma.chan)) + return PTR_ERR(csi->dma.chan); + + ret = dmaengine_slave_config(csi->dma.chan, &cfg); + if (ret) { + dma_release_channel(csi->dma.chan); + return ret; + } + + csi->dma.drain.len = DRAIN_BUFFER_SIZE; + csi->dma.drain.vaddr = dma_alloc_coherent(csi->dev, csi->dma.drain.len, + &csi->dma.drain.paddr, + GFP_KERNEL); + if (!csi->dma.drain.vaddr) + return -ENOMEM; + + return 0; +} + +static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi) +{ + struct media_device *mdev = &csi->mdev; + struct video_device *vdev = &csi->vdev; + const struct ti_csi2rx_fmt *fmt; + struct v4l2_pix_format *pix_fmt = &csi->v_fmt.fmt.pix; + int ret; + + fmt = find_format_by_fourcc(V4L2_PIX_FMT_UYVY); + if (!fmt) + return -EINVAL; + + pix_fmt->width = 640; + pix_fmt->height = 480; + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = V4L2_COLORSPACE_SRGB; + pix_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601, + pix_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE, + pix_fmt->xfer_func = V4L2_XFER_FUNC_SRGB, + + ti_csi2rx_fill_fmt(fmt, &csi->v_fmt); + + mdev->dev = csi->dev; + mdev->hw_revision = 1; + strscpy(mdev->model, "TI-CSI2RX", sizeof(mdev->model)); + + media_device_init(mdev); + + strscpy(vdev->name, TI_CSI2RX_MODULE_NAME, sizeof(vdev->name)); + vdev->v4l2_dev = &csi->v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->fops = &csi_fops; + vdev->ioctl_ops = &csi_ioctl_ops; + vdev->release = video_device_release_empty; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_IO_MC; + vdev->lock = &csi->mutex; + video_set_drvdata(vdev, csi); + + csi->pad.flags = MEDIA_PAD_FL_SINK; + vdev->entity.ops = &ti_csi2rx_video_entity_ops; + ret = media_entity_pads_init(&csi->vdev.entity, 1, &csi->pad); + if (ret) + return ret; + + csi->v4l2_dev.mdev = mdev; + + ret = v4l2_device_register(csi->dev, &csi->v4l2_dev); + if (ret) + return ret; + + ret = media_device_register(mdev); + if (ret) { + v4l2_device_unregister(&csi->v4l2_dev); + media_device_cleanup(mdev); + return ret; + } + + return 0; +} + +static void ti_csi2rx_cleanup_dma(struct ti_csi2rx_dev *csi) +{ + dma_free_coherent(csi->dev, csi->dma.drain.len, + csi->dma.drain.vaddr, csi->dma.drain.paddr); + csi->dma.drain.vaddr = NULL; + dma_release_channel(csi->dma.chan); +} + +static void ti_csi2rx_cleanup_v4l2(struct ti_csi2rx_dev *csi) +{ + media_device_unregister(&csi->mdev); + v4l2_device_unregister(&csi->v4l2_dev); + media_device_cleanup(&csi->mdev); +} + +static void ti_csi2rx_cleanup_subdev(struct ti_csi2rx_dev *csi) +{ + v4l2_async_nf_unregister(&csi->notifier); + v4l2_async_nf_cleanup(&csi->notifier); +} + +static void ti_csi2rx_cleanup_vb2q(struct ti_csi2rx_dev *csi) +{ + vb2_queue_release(&csi->vidq); +} + +static int ti_csi2rx_probe(struct platform_device *pdev) +{ + struct ti_csi2rx_dev *csi; + struct resource *res; + int ret; + + csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL); + if (!csi) + return -ENOMEM; + + csi->dev = &pdev->dev; + platform_set_drvdata(pdev, csi); + + mutex_init(&csi->mutex); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + csi->shim = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(csi->shim)) { + ret = PTR_ERR(csi->shim); + goto err_mutex; + } + + ret = ti_csi2rx_init_dma(csi); + if (ret) + goto err_mutex; + + ret = ti_csi2rx_v4l2_init(csi); + if (ret) + goto err_dma; + + ret = ti_csi2rx_init_vb2q(csi); + if (ret) + goto err_v4l2; + + ret = ti_csi2rx_notifier_register(csi); + if (ret) + goto err_vb2q; + + ret = of_platform_populate(csi->dev->of_node, NULL, NULL, csi->dev); + if (ret) { + dev_err(csi->dev, "Failed to create children: %d\n", ret); + goto err_subdev; + } + + return 0; + +err_subdev: + ti_csi2rx_cleanup_subdev(csi); +err_vb2q: + ti_csi2rx_cleanup_vb2q(csi); +err_v4l2: + ti_csi2rx_cleanup_v4l2(csi); +err_dma: + ti_csi2rx_cleanup_dma(csi); +err_mutex: + mutex_destroy(&csi->mutex); + return ret; +} + +static int ti_csi2rx_remove(struct platform_device *pdev) +{ + struct ti_csi2rx_dev *csi = platform_get_drvdata(pdev); + + video_unregister_device(&csi->vdev); + + ti_csi2rx_cleanup_vb2q(csi); + ti_csi2rx_cleanup_subdev(csi); + ti_csi2rx_cleanup_v4l2(csi); + ti_csi2rx_cleanup_dma(csi); + + mutex_destroy(&csi->mutex); + + return 0; +} + +static const struct of_device_id ti_csi2rx_of_match[] = { + { .compatible = "ti,j721e-csi2rx-shim", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ti_csi2rx_of_match); + +static struct platform_driver ti_csi2rx_pdrv = { + .probe = ti_csi2rx_probe, + .remove = ti_csi2rx_remove, + .driver = { + .name = TI_CSI2RX_MODULE_NAME, + .of_match_table = ti_csi2rx_of_match, + }, +}; + +module_platform_driver(ti_csi2rx_pdrv); + +MODULE_DESCRIPTION("TI J721E CSI2 RX Driver"); +MODULE_AUTHOR("Jai Luthra "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From fa6df857fe7237f0cd97cab7355499dc1fd029e9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 8 Feb 2023 12:00:02 +0200 Subject: media: MAINTAINERS: Add co-maintainer for the rkisp1 driver As I'm actively working on the rkisp1 driver, I would like to volunteer as a co-maintainer, mostly to make sure I get CC on patches. Signed-off-by: Laurent Pinchart Acked-by: Hans Verkuil Reviewed-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 65cc6a982de9..3b47e0b56859 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18539,6 +18539,7 @@ F: sound/soc/rockchip/rockchip_i2s_tdm.* ROCKCHIP ISP V1 DRIVER M: Dafna Hirschfeld +M: Laurent Pinchart L: linux-media@vger.kernel.org L: linux-rockchip@lists.infradead.org S: Maintained -- cgit v1.2.3 From 99b6d9685c33ee38f9fea00e3ea9191da93133c9 Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Fri, 13 Oct 2023 14:27:56 +0530 Subject: media: MAINTAINERS: Fix path for J721E CSI2RX bindings Fix the path of the devicetree bindings. The path was changed during review but MAINTAINERS file was not updated. Fixes: b4a3d877dc92 ("media: ti: Add CSI2RX support for J721E") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202310130411.c66pTXjG-lkp@intel.com/ Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3b47e0b56859..f3e6dbbbbccb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21599,7 +21599,7 @@ TI J721E CSI2RX DRIVER M: Jai Luthra L: linux-media@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/media/ti,j721e-csi2rx.yaml +F: Documentation/devicetree/bindings/media/ti,j721e-csi2rx-shim.yaml F: drivers/media/platform/ti/j721e-csi2rx/ TI KEYSTONE MULTICORE NAVIGATOR DRIVERS -- cgit v1.2.3