Hi Sathyakam, Thank you for the patch. On Mon, Nov 21, 2022 at 10:23:59AM +0530, Sathyakam M wrote: > The FSD MIPI CSI2 Rx controller is compliant to MIPI CSI2 v1.3 and > D-PHY v1.2 specifications. > > There are up to maximum 4 data lanes (default). > Controls are provided for User to change number of lanes if needed. > > Both the video and v4l-subdev instances are exposed to the user > under /dev directory. > > The driver can be built as a loadable module or as a platform_driver. > > Signed-off-by: Sathyakam M <sathya@xxxxxxxxxxx> > Cc: Mauro Carvalho Chehab <mchehab@xxxxxxxxxx> > Cc: Sathyakam M <sathya@xxxxxxxxxxx> > Cc: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx> > Cc: Hans Verkuil <hverkuil-cisco@xxxxxxxxx> > Cc: Jernej Skrabec <jernej.skrabec@xxxxxxxxx> > Cc: Ming Qian <ming.qian@xxxxxxx> > Cc: Dmitry Osipenko <digetx@xxxxxxxxx> > Cc: Jacopo Mondi <jacopo@xxxxxxxxxx> > Cc: Pankaj Kumar Dubey <pankaj.dubey@xxxxxxxxxxx> > Cc: linux-media@xxxxxxxxxxxxxxx > Cc: linux-kernel@xxxxxxxxxxxxxxx > --- > .../media/drivers/fsd-csis-uapi.rst | 78 + > MAINTAINERS | 1 + > drivers/media/platform/Kconfig | 1 + > drivers/media/platform/Makefile | 1 + > drivers/media/platform/fsd/Kconfig | 73 + > drivers/media/platform/fsd/Makefile | 1 + > drivers/media/platform/fsd/fsd-csis.c | 2664 +++++++++++++++++ > drivers/media/platform/fsd/fsd-csis.h | 785 +++++ > include/uapi/linux/fsd-csis.h | 19 + > include/uapi/linux/v4l2-controls.h | 5 + > 10 files changed, 3628 insertions(+) > create mode 100644 Documentation/userspace-api/media/drivers/fsd-csis-uapi.rst > create mode 100644 drivers/media/platform/fsd/Kconfig > create mode 100644 drivers/media/platform/fsd/Makefile > create mode 100644 drivers/media/platform/fsd/fsd-csis.c > create mode 100644 drivers/media/platform/fsd/fsd-csis.h > create mode 100644 include/uapi/linux/fsd-csis.h > > diff --git a/Documentation/userspace-api/media/drivers/fsd-csis-uapi.rst b/Documentation/userspace-api/media/drivers/fsd-csis-uapi.rst > new file mode 100644 > index 000000000000..6d714e9c5d45 > --- /dev/null > +++ b/Documentation/userspace-api/media/drivers/fsd-csis-uapi.rst > @@ -0,0 +1,78 @@ > +.. SPDX-License-Identifier: GPL-2.0-only > + > +FSD MIPI CSI2 Rx Controller driver > +================================== > + > +The CSI2 Rx Controller driver is compliant to MIPI CSI2 v1.3, MIPI D-PHY v1.2 specifications. > +The controller receives images over a 4 lane D-PHY interface. > +A single D-PHY interface is shared among 4 CSI2 Rx controllers. Looking at the driver, this also bundles a DMA engine. Furthermore, the registers show that the CSI-2 RX is a Samsung CSIS, which is already supported by drivers/media/platform/nxp/imx-mipi-csis.c. This driver should be split in two, with this driver handling the DMA engine only, using the imx-mipi-csis.c driver to handle the CSIS and expose it as a subdev. > + > + > +Private IOCTLs > +~~~~~~~~~~~~~~ > + > +The FSD CSI2 Rx Controller implements below private IOCTLs > + > +VIDIOC_CSIS_DMA_SKIP > +^^^^^^^^^^^^^^^^^^^^ > + > +Argument: struct dma_skip_str > + > +**Description**: > + > + The DMA controller can be configured to skip incoming frames > + from being written to memory when needed. e.g. when user application > + needs bandwidth control without reconfiguring camera sensor. > + > +**Return value**: > + > + On success 0 is returned. On error -1 is returned and errno is set > + appropriately. > + > +**Data types**: > + > +.. code-block:: none > + > + * struct dma_skip_str > + > + __u32 ta turn around pointer varibale > + __u32 sseq dma skip sequence variable > + __u32 en dma skip enable > + __u32 vc virtual channel Custom ioctls require an open-source userspace to showcase their usage. Implementation of a test tool is not enough, this must be part of a real userspace framework. libcamera is a good option, but other options may be possible. > + > + > +Custom controls > +~~~~~~~~~~~~~~~ > + > +FSD CSI2 Rx controller implements below custom cotrols > + > +V4L2_CID_USER_FSD_CSIS_NO_OF_LANE > +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > + > +Argument: struct v4l2_control > + > +**Description**: > + > + The D-PHY interface can be configured to receive streaming > + on data lanes between 1 to 4 (inclusive). User applications > + can set the desired number of lanes with this control using > + the video device interface > + > +**Return value**: > + > + On success 0 is returned. On error -1 is returned and errno is set > + appropriately. > + > +**Data types**: > + > +.. code-block:: none > + > + * struct v4l2_control > + > + __u32 id V4L2_CID_USER_FSD_CSIS_NO_OF_LANE > + __s32 value 1 to 4 (inclusive) Why should this be set from userspace, and not from within the kernel ? Does the value need to change depending on the configuration of the CSI-2 source ? If so, you could use a subdev operation to query the number of lanes used by the sensor when starting streaming. > + > +References > +---------- > + > +.. [#] include/uapi/linux/fsd-csis.h > diff --git a/MAINTAINERS b/MAINTAINERS > index bbadba5888ab..c65bacd43f54 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -8386,6 +8386,7 @@ M: Sathyakam M <sathya@xxxxxxxxxxx> > L: linux-media@xxxxxxxxxxxxxxx > S: Orphan > F: Documentation/devicetree/bindings/media/tesla-fsd-csis.yaml I don't see this file in mainline, which tree is this patch based on ? I also don't see patches 1-3 and 5 on the list, have I missed something ? > +F: drivers/media/platform/fsd/* > > FSI SUBSYSTEM > M: Jeremy Kerr <jk@xxxxxxxxxx> > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig > index a9334263fa9b..b48ca5f78bdd 100644 > --- a/drivers/media/platform/Kconfig > +++ b/drivers/media/platform/Kconfig > @@ -69,6 +69,7 @@ source "drivers/media/platform/aspeed/Kconfig" > source "drivers/media/platform/atmel/Kconfig" > source "drivers/media/platform/cadence/Kconfig" > source "drivers/media/platform/chips-media/Kconfig" > +source "drivers/media/platform/fsd/Kconfig" > source "drivers/media/platform/intel/Kconfig" > source "drivers/media/platform/marvell/Kconfig" > source "drivers/media/platform/mediatek/Kconfig" > diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile > index a91f42024273..d73ab62ab0cf 100644 > --- a/drivers/media/platform/Makefile > +++ b/drivers/media/platform/Makefile > @@ -12,6 +12,7 @@ obj-y += aspeed/ > obj-y += atmel/ > obj-y += cadence/ > obj-y += chips-media/ > +obj-y += fsd/ > obj-y += intel/ > obj-y += marvell/ > obj-y += mediatek/ > diff --git a/drivers/media/platform/fsd/Kconfig b/drivers/media/platform/fsd/Kconfig > new file mode 100644 > index 000000000000..9ce44becf3ec > --- /dev/null > +++ b/drivers/media/platform/fsd/Kconfig > @@ -0,0 +1,73 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +# > +# FSD MIPI CSI-2 Rx controller configurations > + > +config VIDEO_FSD_MIPI_CSIS > + tristate "FSD SoC MIPI-CSI2 Rx controller driver" > + depends on VIDEO_DEV && VIDEO_V4L2_SUBDEV_API > + depends on HAS_DMA > + depends on OF > + select VIDEOBUF2_DMA_CONTIG > + select V4L2_FWNODE > + help > + This is a V4L2 driver for FSD SoC MIPI-CSI2 Rx interface. > + To compile this driver as a module, choose M here. > + The module will be called fsd-csis. > + Please select appropriate data rate for D-PHY configuration > + > +choice > + prompt "Select PHY control values" > + depends on VIDEO_FSD_MIPI_CSIS > + default FSD_CSI_1600_MEGA_BITS_PER_SEC > + help > + Select D-PHY Common control values based on CSI Rx > + bandwidth requirement. > + The PHY parameters are set according to the > + selected data rate. This shouldn't be a compile-time option, but a runtime option (possibly coming from DT). > + > +config FSD_CSI_800_MEGA_BITS_PER_SEC > + bool "800Mbps" > + help > + D-PHY Common control values for 800Mbps. > + If set FSD CSI2 Rx controller and the D-PHY are configured > + for data rate up to 800Mbps over the 4 lane interface. > + The D-PHY parameters for HS and Clock settle timings > + are set accordingly. > + > +config FSD_CSI_1000_MEGA_BITS_PER_SEC > + bool "1000Mbps" > + help > + D-PHY Common control values for 1000Mbps. > + If set FSD CSI2 Rx controller and the D-PHY are configured > + for data rate up to 1000Mbps over the 4 lane interface. > + The D-PHY parameters for HS and Clock settle timings > + are set accordingly. > + > +config FSD_CSI_1500_MEGA_BITS_PER_SEC > + bool "1500Mbps" > + help > + D-PHY Common control values for 1500Mbps. > + If set FSD CSI2 Rx controller and the D-PHY are configured > + for data rate up to 1500Mbps over the 4 lane interface. > + The D-PHY parameters for HS and Clock settle timings > + are set accordingly. > + > +config FSD_CSI_1600_MEGA_BITS_PER_SEC > + bool "1600Mbps" > + help > + D-PHY Common control values for 1600Mbps. > + If set FSD CSI2 Rx controller and the D-PHY are configured > + for data rate up to 1600Mbps over the 4 lane interface. > + The D-PHY parameters for HS and Clock settle timings > + are set accordingly. > + > +config FSD_CSI_2100_MEGA_BITS_PER_SEC > + bool "2100Mbps" > + help > + D-PHY Common control values for 2100Mbps. > + If set FSD CSI2 Rx controller and the D-PHY are configured > + for data rate up to 2100Mbps over the 4 lane interface. > + The D-PHY parameters for HS and Clock settle timings > + are set accordingly. > + > +endchoice > diff --git a/drivers/media/platform/fsd/Makefile b/drivers/media/platform/fsd/Makefile > new file mode 100644 > index 000000000000..41d9e1ceb11c > --- /dev/null > +++ b/drivers/media/platform/fsd/Makefile > @@ -0,0 +1 @@ > +obj-$(CONFIG_VIDEO_FSD_MIPI_CSIS) += fsd-csis.o > diff --git a/drivers/media/platform/fsd/fsd-csis.c b/drivers/media/platform/fsd/fsd-csis.c > new file mode 100644 > index 000000000000..713c63c46f09 > --- /dev/null > +++ b/drivers/media/platform/fsd/fsd-csis.c > @@ -0,0 +1,2664 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * FSD CSIS camera interface driver > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License version 2 as published by > + * the Free Software Foundation > + */ > + > +#include <linux/uaccess.h> > +#include <linux/delay.h> > +#include <linux/clk.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/ioctl.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/slab.h> > +#include <linux/videodev2.h> > +#include <linux/of_device.h> > +#include <linux/of_graph.h> > +#include <linux/regmap.h> > +#include <linux/mfd/syscon.h> > + > +#include <media/v4l2-async.h> > +#include <media/v4l2-common.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-event.h> > +#include <media/v4l2-ioctl.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-fh.h> > +#include <media/v4l2-fwnode.h> > +#include <media/videobuf2-core.h> > +#include <media/videobuf2-dma-contig.h> > +#include <media/videobuf2-vmalloc.h> > + > +#include <uapi/linux/fsd-csis.h> > + > +#include "fsd-csis.h" > + > +#define FSD_CSIS_MODULE_NAME "csis" > +#define FSD_CSIS_MODULE_VERSION "0.0.1" > + > +static unsigned int video_nr = -1; > +module_param(video_nr, uint, 0644); > +MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect"); > + > +static unsigned int debug; > +module_param(debug, uint, 0644); > +MODULE_PARM_DESC(debug, "activities debug info"); > + > +static atomic_t drv_instance = ATOMIC_INIT(0); > + > +/* fsd_csis_formats - array of image formats supported */ > +static const struct fsd_csis_fmt fsd_csis_formats[FSD_CSIS_MAX_FORMATS] = { > + { > + .name = "RGB565", > + .fourcc = V4L2_PIX_FMT_RGB565, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .code = MEDIA_BUS_FMT_RGB565_1X16, > + .depth = 16, > + }, { > + .name = "RGB666", > + .fourcc = V4L2_PIX_FMT_BGR666, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .code = MEDIA_BUS_FMT_RGB666_1X18, > + .depth = 18, > + }, { > + .name = "RGB888-24", > + .fourcc = V4L2_PIX_FMT_RGB24, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .code = MEDIA_BUS_FMT_RGB888_1X24, > + .depth = 24, > + }, { > + .name = "RGB888-32", > + .fourcc = V4L2_PIX_FMT_RGB32, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .code = MEDIA_BUS_FMT_RGB888_1X32_PADHI, > + .depth = 32, > + }, { > + .name = "XRGB888", > + .fourcc = V4L2_PIX_FMT_XRGB32, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .code = MEDIA_BUS_FMT_RGB888_1X32_PADHI, > + .depth = 32, > + }, { > + .name = "UYVY-16", > + .fourcc = V4L2_PIX_FMT_UYVY, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_UYVY8_2X8, > + .depth = 16, > + }, { > + .name = "YUV422-8", > + .fourcc = V4L2_PIX_FMT_YUYV, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_VYUY8_2X8, > + .depth = 16, > + }, { > + .name = "SBGGR8", > + .fourcc = V4L2_PIX_FMT_SBGGR8, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SBGGR8_1X8, > + .depth = 8, > + }, { > + .name = "SGBRG8", > + .fourcc = V4L2_PIX_FMT_SGBRG8, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SGBRG8_1X8, > + .depth = 8, > + }, { > + .name = "SGRBG8", > + .fourcc = V4L2_PIX_FMT_SGRBG8, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SGRBG8_1X8, > + .depth = 8, > + }, { > + .name = "SRGGB8", > + .fourcc = V4L2_PIX_FMT_SRGGB8, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SRGGB8_1X8, > + .depth = 8, > + }, { > + .name = "SBGGR10", > + .fourcc = V4L2_PIX_FMT_SBGGR10, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SBGGR10_1X10, > + .depth = 10, > + }, { > + .name = "SGBRG10", > + .fourcc = V4L2_PIX_FMT_SGBRG10, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SGBRG10_1X10, > + .depth = 10, > + }, { > + .name = "SGRBG10", > + .fourcc = V4L2_PIX_FMT_SGRBG10, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SGRBG10_1X10, > + .depth = 10, > + }, { > + .name = "SRGGB10", > + .fourcc = V4L2_PIX_FMT_SRGGB10, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SRGGB10_1X10, > + .depth = 10, > + }, { > + .name = "SBGGR12", > + .fourcc = V4L2_PIX_FMT_SBGGR12, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SBGGR12_1X12, > + .depth = 12, > + }, { > + .name = "SGBRG12", > + .fourcc = V4L2_PIX_FMT_SGBRG12, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SGBRG12_1X12, > + .depth = 12, > + }, { > + .name = "SGRBG12", > + .fourcc = V4L2_PIX_FMT_SGRBG12, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SGRBG12_1X12, > + .depth = 12, > + }, { > + .name = "SRGGB12", > + .fourcc = V4L2_PIX_FMT_SRGGB12, > + .colorspace = V4L2_COLORSPACE_RAW, > + .code = MEDIA_BUS_FMT_SRGGB12_1X12, > + .depth = 12, > + }, { > + .name = "JPEG", > + .fourcc = V4L2_PIX_FMT_JPEG, > + .colorspace = V4L2_COLORSPACE_JPEG, > + .code = MEDIA_BUS_FMT_JPEG_1X8, > + .depth = 16, > + }, > +}; > + > +/* > + * fourcc_to_str() - Utility function to display fourcc > + * @fmt: fourcc value of image format > + * Return: string equevalent of fourcc value > + */ > +static char *fourcc_to_str(u32 fmt) > +{ > + static unsigned char code[5]; > + > + code[0] = (unsigned char)(fmt & 0xff); > + code[1] = (unsigned char)((fmt >> 8) & 0xff); > + code[2] = (unsigned char)((fmt >> 16) & 0xff); > + code[3] = (unsigned char)((fmt >> 24) & 0xff); > + code[4] = '\0'; > + return code; > +} Use the printk %p4CC format specifier instead. > + > +/* > + * timeperframe: min/max and default > + */ > +static const struct v4l2_fract fsd_csis_tpf_default = { > + .numerator = 1001, > + .denominator = 30000 > +}; > + > +/* > + * fsd_csis_clear_vid_irqs() - clear the interrupt sources > + * @dev: pointer to fsd_csis_dev structure > + * Return: none > + */ > +static void fsd_csis_clear_vid_irqs(struct fsd_csis_dev *dev) > +{ > + unsigned int int_src = 0; > + > + int_src = readl(dev->base + CSIS_INT_SRC0); > + writel(int_src, dev->base + CSIS_INT_SRC0); > + > + int_src = readl(dev->base + CSIS_INT_SRC1); > + writel(int_src, dev->base + CSIS_INT_SRC1); > +} > + > +/* > + * fsd_csis_disable_interrupts() - Disable the interrupt sources by masking > + * @dev: pointer to fsd_csis_dev structure > + * Return: none > + */ > +static void fsd_csis_disable_irqs(struct fsd_csis_dev *dev) > +{ > + writel(CSIS_INT_MSK0_MASK_ALL, dev->base + CSIS_INT_MSK0); > + writel(CSIS_INT_MSK1_MASK_ALL, dev->base + CSIS_INT_MSK1); > +} > + > +/* > + * fsd_csis_enable_vid_irqs() - Enable the interrupt sources by unmasking > + * @dev: pointer to fsd_csis_dev structure > + * Return: none > + */ > +static void fsd_csis_enable_vid_irqs(struct fsd_csis_dev *dev) > +{ > + writel(CSIS_INT_MSK0_ENABLE_ALL, dev->base + CSIS_INT_MSK0); > + writel(CSIS_INT_MSK1_ENABLE_ALL, dev->base + CSIS_INT_MSK1); > +} > + > +/* > + * fsd_csis_dphy_reset() - reset and release D-PHY i/f > + * for the given csi > + * @dev: pointer to fsd_csis_dev structure > + * @reset: Reset enable/ disable > + * Return: none > + */ > +static void fsd_csis_dphy_reset(struct fsd_csis_dev *dev, bool reset) > +{ > + unsigned int dphy = 0, sw_resetn_dphy = 0x0; > + > + /* There are 4 CSIs per each D-PHY i/f */ > + dphy = dev->id / FSD_CSIS_NB_CSI_PER_PHY; > + regmap_read(dev->sysreg_map, SW_RESETEN_DPHY, &sw_resetn_dphy); > + > + /* > + * 0: reset > + * 1: reset release > + */ > + if (reset) > + sw_resetn_dphy &= reset_bits(CSIS_SW_RESETEN_DPHY_MASK(dphy)); > + else > + sw_resetn_dphy |= set_bits(CSIS_SW_RESETEN_DPHY, CSIS_SW_RESETEN_DPHY_MASK(dphy)); > + > + regmap_write(dev->sysreg_map, SW_RESETEN_DPHY, sw_resetn_dphy); > +} > + > +/* > + * fsd_csis_mipi_dphy_init() - initialize D-PHY slave rx parameters > + * @dev: pointer to fsd_csis_dev structure > + * Return: none > + */ > +static void fsd_csis_mipi_dphy_init(struct fsd_csis_dev *dev) > +{ > + unsigned int dphy_sctrl = 0; > + > + dphy_sctrl = readl(dev->base + PHY_SCTRL_H); > + dphy_sctrl &= reset_bits(SKEW_CAL_MAX_SKEW_CODE_CTRL_MASK | SKEW_CAL_EN_MASK); > + /* Enable Rx Skew calibration */ > + dphy_sctrl |= set_bits(SKEW_CAL_EN, SKEW_CAL_EN_MASK); > + /* Set Rx Skew Calibratin to Max Code Control */ > + dphy_sctrl |= set_bits(SKEW_CAL_MAX_SKEW_CODE_CTRL, SKEW_CAL_MAX_SKEW_CODE_CTRL_MASK); > + writel(dphy_sctrl, dev->base + PHY_SCTRL_H); > +} > + > +/* > + * fsd_csis_set_hs_settle() - set HSsettle[7:0] value for PHY > + * @dev: pointer to fsd_csis_dev structure > + * @hs_settle: HS-Rx Settle time > + * Return: none > + */ > +static void fsd_csis_set_hs_settle(struct fsd_csis_dev *dev, unsigned int hs_settle) > +{ > + u32 phy_cmn_ctrl; > + > + phy_cmn_ctrl = readl(dev->base + PHY_CMN_CTRL); > + phy_cmn_ctrl &= reset_bits(HSSETTLE_MASK); > + phy_cmn_ctrl |= set_bits(hs_settle, HSSETTLE_MASK); > + writel(phy_cmn_ctrl, (dev->base + PHY_CMN_CTRL)); > +} > + > +/* > + * fsd_csis_setclk_settle_ctl() - set slave clock lane settle time > + * @dev: pointer to fsd_csis_dev structure > + * @clksettlectl: T-CLK_SETTLE value > + * Return: none > + */ > +static void fsd_csis_setclk_settle_ctl(struct fsd_csis_dev *dev, unsigned int clksettlectl) > +{ > + u32 phy_cmn_ctrl; > + > + phy_cmn_ctrl = readl(dev->base + PHY_CMN_CTRL); > + phy_cmn_ctrl &= reset_bits(S_CLKSETTLE_MASK); > + phy_cmn_ctrl |= set_bits(clksettlectl, S_CLKSETTLE_MASK); > + writel(phy_cmn_ctrl, (dev->base + PHY_CMN_CTRL)); > +} > + > +/* > + * fsd_csis_enable_deskew_logic()- enable or disable DeSkew logic > + * @dev: pointer to fsd_csis_dev structure > + * @enable: boolean value enable = true/ disable = false > + * Return: none > + */ > +static void fsd_csis_enable_deskew_logic(struct fsd_csis_dev *dev, bool enable) > +{ > + u32 csis_cmn_ctrl; > + > + /* CSIS de-skew logic */ > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + csis_cmn_ctrl &= reset_bits(DESKEW_ENABLE_MASK); > + > + if (enable) > + csis_cmn_ctrl |= set_bits(DESKEW_ENABLE, DESKEW_ENABLE_MASK); > + writel(csis_cmn_ctrl, (dev->base + CSIS_CMN_CTRL)); > +} > + > +/* > + * fsd_csis_update_shadow_ctx() - update the CSI configuration > + * @ctx: pointer to CSI context > + * Return: none > + */ > +static void fsd_csis_update_shadow_ctx(struct fsd_csis_ctx *ctx) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + u32 csis_cmn_ctrl, vc = ctx->virtual_channel; > + > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + csis_cmn_ctrl |= set_bits(UPDATE_SHADOW, UPDATE_SHADOW_CH_MASK(vc)); > + writel(csis_cmn_ctrl, (dev->base + CSIS_CMN_CTRL)); > +} > + > +/* > + * fsd_csis_set_update_shadow_ctrl() - set the shadow registers update control > + * @dev: pointer to csis device structure > + * @update_shado_ctrl: boolean value to set or reset shadow control > + * Return: none > + */ > +static void fsd_csis_set_update_shadow_ctrl(struct fsd_csis_dev *dev, bool update) > +{ > + u32 csis_cmn_ctrl; > + > + /* CSIS Update Shadow control */ > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + csis_cmn_ctrl &= reset_bits(UPDATE_SHADOW_CTRL_MASK); > + > + if (update) > + csis_cmn_ctrl |= set_bits(UPDATE_SHADOW_CTRL, UPDATE_SHADOW_CTRL_MASK); > + writel(csis_cmn_ctrl, (dev->base + CSIS_CMN_CTRL)); > +} > + > +/* > + * fsd_csis_set_clkgate_trail() - set the trailing clocks for ISP i/f > + * @ctx: csis context structure for this stream > + * @clkgate_trail: number of trailing clocks > + * Return: none > + */ > +static void fsd_csis_set_clkgate_trail(struct fsd_csis_ctx *ctx, unsigned short clkgate_trail) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int csis_clk_ctrl, vc = ctx->virtual_channel; > + > + csis_clk_ctrl = readl(dev->base + CSIS_CLK_CTRL); > + csis_clk_ctrl &= reset_bits(CLKGATE_TRAIL_MASK(vc)); > + csis_clk_ctrl |= set_bits(clkgate_trail, CLKGATE_TRAIL_MASK(vc)); > + > + writel(csis_clk_ctrl, dev->base + CSIS_CLK_CTRL); > +} > + > +/* > + * fsd_csis_set_clkgate_en() - enable clock gating for Pixel clock > + * @ctx: csis context structure for this stream > + * @clk_gate_en: boolean value to enable or disable pixel clock gating > + * Return: none > + */ > +static void fsd_csis_set_clkgate_en(struct fsd_csis_ctx *ctx, bool clk_gate_en) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int csis_clk_ctrl, vc = ctx->virtual_channel; > + > + csis_clk_ctrl = readl(dev->base + CSIS_CLK_CTRL); > + csis_clk_ctrl &= reset_bits(CLKGATE_EN_MASK(vc)); > + > + if (clk_gate_en) > + csis_clk_ctrl |= set_bits(CLKGATE_EN, CLKGATE_EN_MASK(vc)); > + > + writel(csis_clk_ctrl, dev->base + CSIS_CLK_CTRL); > +} > + > +/* > + * fsd_csis_set_vc_passing() - select the Virtual Channel for processing > + * @ctx: csis context structure for this stream > + * Return: none > + */ > +static void fsd_csis_set_vc_passing(struct fsd_csis_ctx *ctx) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int vc_passing; > + unsigned int vc = ctx->virtual_channel; > + > + vc_passing = readl(dev->base + VC_PASSING); > + vc_passing &= reset_bits(VC_PASSING_MASK); > + vc_passing |= set_bits(vc, VC_PASSING_MASK); > + vc_passing |= set_bits(VC_PASSING_ENABLE, VC_PASSING_ENABLE_MASK); > + writel(vc_passing, dev->base + VC_PASSING); > +} > + > +/* > + * fsd_csis_set_dma_clk() - set the number of trailing clocks for DMA clock gating > + * @dev: pointer to fsd_csis_dev structure > + * Return: none > + */ > +static void fsd_csis_set_dma_clk(struct fsd_csis_dev *dev) > +{ > + unsigned int dma_clk_ctrl = 0x0; > + > + dma_clk_ctrl = readl(dev->base + DMA_CLK_CTRL); > + dma_clk_ctrl &= reset_bits(DMA_CLK_GATE_EN_MASK); > + dma_clk_ctrl |= set_bits(DMA_CLK_GATE_TRAIL, DMA_CLK_GATE_TRAIL_MASK); > + writel(dma_clk_ctrl, dev->base + DMA_CLK_CTRL); > +} > + > +/* > + * fsd_csis_sw_reset() - Soft reset the CSI instance > + * @dev: pointer to fsd_csis_dev structure > + * Return: none > + */ > +static void fsd_csis_sw_reset(struct fsd_csis_dev *dev) > +{ > + u32 csis_cmn_ctrl = 0; > + > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + > + /* Disable CSI first */ > + csis_cmn_ctrl &= reset_bits(CSI_EN_MASK); > + writel(csis_cmn_ctrl, dev->base + CSIS_CMN_CTRL); > + > + /* SW Reset CSI */ > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + csis_cmn_ctrl |= set_bits(SW_RESET, SW_RESET_MASK); > + > + while (csis_cmn_ctrl & SW_RESET_MASK) { > + writel(csis_cmn_ctrl, dev->base + CSIS_CMN_CTRL); > + usleep_range(1000, 2000); /* Wait min 10ms, max 20ms */ > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + } > +} > + > +/* > + * fsd_csis_set_num_of_datalane() - Configure the number of data lanes for use > + * @dev: pointer to fsd_csis_dev structure > + * @nb_data_lane: number of data lanes to configure > + * Return: 0 or -EINVAL > + */ > +static int fsd_csis_set_num_of_datalane(struct fsd_csis_dev *dev, unsigned int nb_data_lane) > +{ > + u32 csis_cmn_ctrl = 0, csis_nb_lane = nb_data_lane - 1; > + > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + csis_cmn_ctrl &= reset_bits(LANE_NUMBER_MASK); > + > + switch (csis_nb_lane) { > + case DATALANE0: > + case DATALANE1: > + case DATALANE2: > + case DATALANE3: > + csis_cmn_ctrl |= set_bits(csis_nb_lane, LANE_NUMBER_MASK); > + break; > + default: > + fsd_csis_err(dev, "Wrong number of data lanes %d to configure!\n", nb_data_lane); > + return -EINVAL; > + } > + writel(csis_cmn_ctrl, dev->base + CSIS_CMN_CTRL); > + return 0; > +} > + > +/* > + * fsd_csis_set_phy_on() - turn on or off the PHY > + * @dev: pointer to fsd_csis_dev structure > + * @nb_data_lane: number of data lanes in use by this CSI instance > + * Return: none > + */ > +static void fsd_csis_set_phy_on(struct fsd_csis_dev *dev, unsigned int nb_data_lane) > +{ > + u32 phy_cmn_ctrl; > + > + phy_cmn_ctrl = readl(dev->base + PHY_CMN_CTRL); > + phy_cmn_ctrl &= reset_bits((ENABLE_DAT_MASK | S_BYTE_CLK_ENABLE_MASK | ENABLE_CLK_MASK)); > + phy_cmn_ctrl |= set_bits(ENABLE_DAT(nb_data_lane), ENABLE_DAT_MASK); > + phy_cmn_ctrl |= set_bits(S_BYTE_CLK_ENABLE, S_BYTE_CLK_ENABLE_MASK); > + phy_cmn_ctrl |= set_bits(ENABLE_CLK, ENABLE_CLK_MASK); > + writel(phy_cmn_ctrl, dev->base + PHY_CMN_CTRL); > + > + fsd_csis_dbg(3, dev, "Data lane %d phy_cmn_ctrl %x\n", nb_data_lane, phy_cmn_ctrl); > +} > + > +/* > + * fsd_csis_set_pixel_mode() - set pixel i/f OTF mode > + * to single/dual/quad/octa pixel mode > + * @ctx: pointer to CSI context > + * @vc: virtual channel id > + * @fmt: image format information > + * Return: none > + */ > +static void fsd_csis_set_pixel_mode(struct fsd_csis_ctx *ctx, unsigned int vc, > + const struct fsd_csis_fmt *fmt) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int fourcc = fmt->fourcc; > + u32 isp_config_ch; > + unsigned int pixel_mode; > + > + switch (fourcc) { > + case V4L2_PIX_FMT_SBGGR8: > + case V4L2_PIX_FMT_SGBRG8: > + case V4L2_PIX_FMT_SGRBG8: > + case V4L2_PIX_FMT_SRGGB8: > + case V4L2_PIX_FMT_YUYV: > + case V4L2_PIX_FMT_BGR666: > + case V4L2_PIX_FMT_RGB24: > + pixel_mode = QUAD_PIXEL_MODE; > + break; > + case V4L2_PIX_FMT_SBGGR10: > + case V4L2_PIX_FMT_SGBRG10: > + case V4L2_PIX_FMT_SGRBG10: > + case V4L2_PIX_FMT_SRGGB10: > + case V4L2_PIX_FMT_SBGGR12: > + case V4L2_PIX_FMT_SGBRG12: > + case V4L2_PIX_FMT_SGRBG12: > + case V4L2_PIX_FMT_SRGGB12: > + case V4L2_PIX_FMT_RGB565: > + pixel_mode = OCTA_PIXEL_MODE; > + break; > + default: > + pixel_mode = SINGLE_PIXEL_MODE; > + break; > + } > + > + fsd_csis_ctx_dbg(3, ctx, "Selected PIXEL_MODE: %u\n", pixel_mode); > + isp_config_ch = readl(dev->base + ISP_CONFIG_CH0 + ISP_CH_OFFSET * vc); > + isp_config_ch &= reset_bits(PIXEL_MODE_MASK); > + isp_config_ch |= set_bits(pixel_mode, PIXEL_MODE_MASK); > + writel(isp_config_ch, dev->base + ISP_CONFIG_CH0 + ISP_CH_OFFSET * vc); > +} > + > +/* > + * fsd_csis_set_paralle_mode() - configure pixel alignmnet for OTF i/f > + * @ctx: pointer to CSI context > + * @data_align: parallel mode value indicating alignment > + * Return: none > + */ > +static void fsd_csis_set_paralle_mode(struct fsd_csis_ctx *ctx, > + enum FSD_CSIS_PARALLEL_MODE data_align) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + u32 isp_config_ch, vc = ctx->virtual_channel; > + > + isp_config_ch = readl(dev->base + ISP_CONFIG_CH0 + ISP_CH_OFFSET * vc); > + isp_config_ch &= reset_bits(PARALLEL_MODE_MASK); > + isp_config_ch |= set_bits(data_align, PARALLEL_MODE_MASK); > + writel(isp_config_ch, dev->base + ISP_CONFIG_CH0 + ISP_CH_OFFSET * vc); > +} > + > +/* > + * fsd_csis_set_img_fmt() - configure selected image format for streaming > + * @ctx: pointer to CSI context > + * @vc: virtual channel id > + * @fmt: format to configure > + * Return: none > + */ > +static void fsd_csis_set_img_fmt(struct fsd_csis_ctx *ctx, unsigned int vc, > + const struct fsd_csis_fmt *fmt) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int isp_config_ch, fourcc = fmt->fourcc; > + > + isp_config_ch = readl(dev->base + ISP_CONFIG_CH0 + ISP_CH_OFFSET * vc); > + isp_config_ch &= reset_bits(DATAFORMAT_MASK); > + > + switch (fourcc) { > + case V4L2_PIX_FMT_RGB565: > + /* RGB565 */ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_RGB565, DATAFORMAT_MASK); > + break; > + case V4L2_PIX_FMT_BGR666: > + /* RGB666 */ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_RGB666, DATAFORMAT_MASK); > + break; > + case V4L2_COLORSPACE_SRGB: > + case V4L2_PIX_FMT_XRGB32: > + case V4L2_PIX_FMT_RGB24: > + case V4L2_PIX_FMT_RGB32: > + /* RGB888 */ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_RGB888, DATAFORMAT_MASK); > + break; > + case V4L2_PIX_FMT_YUYV: > + case V4L2_PIX_FMT_UYVY: > + /* YUYV-16/YUV422-8, UYVY-16 / YUV 422 */ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_YUV422_8, DATAFORMAT_MASK); > + fsd_csis_set_paralle_mode(ctx, FSD_CSIS_PARALLEL_MODE_OFF); > + break; > + case V4L2_PIX_FMT_SGBRG8: > + case V4L2_PIX_FMT_SGRBG8: > + case V4L2_PIX_FMT_SRGGB8: > + /* SGBRG8 / RAW8*/ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_RAW8, DATAFORMAT_MASK); > + break; > + case V4L2_PIX_FMT_SBGGR10: > + case V4L2_PIX_FMT_SGBRG10: > + case V4L2_PIX_FMT_SGRBG10: > + case V4L2_PIX_FMT_SRGGB10: > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_RAW10, DATAFORMAT_MASK); > + break; > + case V4L2_PIX_FMT_SBGGR12: > + case V4L2_PIX_FMT_SGBRG12: > + case V4L2_PIX_FMT_SGRBG12: > + case V4L2_PIX_FMT_SRGGB12: > + /* SRGGB12, SGRBG12, SGBRG12, SBGGR12 / RAW-12 */ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_RAW12, DATAFORMAT_MASK); > + fsd_csis_set_paralle_mode(ctx, FSD_CSIS_PARALLEL_MODE_OFF); > + break; > + case V4L2_PIX_FMT_SBGGR14P: > + case V4L2_PIX_FMT_SGBRG14P: > + case V4L2_PIX_FMT_SGRBG14P: > + case V4L2_PIX_FMT_SRGGB14P: > + /* SBGGR14, SGBRG14, SGRRBG14, SRGGB14 / RAW14 */ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_RAW14, DATAFORMAT_MASK); > + break; > + case V4L2_PIX_FMT_SGBRG16: > + case V4L2_PIX_FMT_SGRBG16: > + case V4L2_PIX_FMT_SRGGB16: > + /* SGBRG16, SGRBG16, SRGGB16 / RAW16 */ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_RAW16, DATAFORMAT_MASK); > + break; > + case V4L2_PIX_FMT_JPEG: > + /* JPEG */ > + isp_config_ch |= set_bits(ISP_DATA_FORMAT_USER_DEFINED_2, DATAFORMAT_MASK); > + break; > + default: > + fsd_csis_ctx_err(ctx, "image format %x not supported\n", fourcc); > + break; > + } > + > + isp_config_ch &= reset_bits(VIRTUAL_CHANNEL_MASK); > + isp_config_ch |= set_bits(vc, VIRTUAL_CHANNEL_MASK); > + writel(isp_config_ch, dev->base + ISP_CONFIG_CH0 + ISP_CH_OFFSET * vc); > + fsd_csis_ctx_dbg(3, ctx, "format %x set\n", fourcc); > +} > + > +/* > + * fsd_csis_set_resolution() - configure selected resolution for streaming > + * @ctx: pointer to CSI context > + * @vc: virtual channel id > + * @width: horizontal image resolution > + * @height: vertical image resolution > + * Return: none > + */ > +static void fsd_csis_set_resolution(struct fsd_csis_ctx *ctx, unsigned int vc, unsigned int width, > + unsigned int height) > +{ > + u32 isp_resol_ch = 0; > + struct fsd_csis_dev *dev = ctx->dev; > + > + isp_resol_ch &= reset_bits((HRESOL_MASK | VRESOL_MASK)); > + isp_resol_ch |= set_bits(width, HRESOL_MASK); > + isp_resol_ch |= set_bits(height, VRESOL_MASK); > + writel(isp_resol_ch, dev->base + ISP_RESOL_CH0 + ISP_CH_OFFSET * vc); > + fsd_csis_ctx_dbg(3, ctx, "resolution %08dx%08d set\n", width, height); > +} > + > +/* > + * fsd_csis_format_size() - set image size for selected resolution > + * @ctx: pointer to CSI context > + * @fmt: image format > + * @f: format whose size to be updated > + * Return: 0 > + */ > +static int fsd_csis_format_size(struct fsd_csis_ctx *ctx, const struct fsd_csis_fmt *fmt, > + struct v4l2_format *f) > +{ > + if (!fmt) { > + fsd_csis_ctx_err(ctx, "No format provided\n"); > + return -EINVAL; > + } > + > + v4l_bound_align_image(&f->fmt.pix.width, FSD_CSIS_WMIN, FSD_CSIS_WMAX, FSD_CSIS_WALIGN, > + &f->fmt.pix.height, FSD_CSIS_HMIN, FSD_CSIS_HMAX, FSD_CSIS_HALIGN, > + FSD_CSIS_SALIGN); > + > + f->fmt.pix.bytesperline = bytes_per_line(f->fmt.pix.width, fmt->depth); > + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; > + fsd_csis_set_resolution(ctx, ctx->virtual_channel, f->fmt.pix.width, f->fmt.pix.height); > + > + fsd_csis_ctx_dbg(3, ctx, "fourcc %s width %d height %d bpl %d img size %d set\n", > + fourcc_to_str(f->fmt.pix.pixelformat), f->fmt.pix.width, f->fmt.pix.height, > + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); > + > + return 0; > +} > + > +/* > + * fsd_csis_set_hsync_lintv_timing() - set Hsync_Lintv value for CSI > + * @ctx: pointer to CSI context > + * @vc: virtual channel id > + * @hsync_lintv: interval between last falling of DVALID and falling of HSYNC > + * Return: none > + */ > +static void fsd_csis_set_hsync_lintv_timing(struct fsd_csis_ctx *ctx, unsigned int vc, > + unsigned int hsync_lintv) > +{ > + u32 isp_sync_ch; > + struct fsd_csis_dev *dev = ctx->dev; > + > + isp_sync_ch = readl(dev->base + ISP_SYNC_CH0 + ISP_CH_OFFSET * vc); > + isp_sync_ch &= reset_bits(HSYNC_LINTV_MASK); > + isp_sync_ch |= set_bits(hsync_lintv, HSYNC_LINTV_MASK); > + writel(isp_sync_ch, dev->base + ISP_SYNC_CH0 + ISP_CH_OFFSET * vc); > +} > + > +/* > + * fsd_csis_set_pack() - select DMA memory storing style > + * @dev: pointer to fsd_csis_dev structure > + * @vc: virtual channel id > + * @dma_pack: 1: Memory storing style is 1 dimension/ 0: 2 Dimension > + * Return: none > + */ > +static void fsd_csis_set_pack(struct fsd_csis_dev *dev, u32 vc, enum FSD_CSIS_DMA_PACK dma_pack) > +{ > + u32 dma_fmt; > + > + dma_fmt = readl(dev->base + DMA0_FMT + vc * DMA_ADDR_OFFSET); > + dma_fmt &= reset_bits(ACTIVE_DMA_PACK_MASK); > + dma_fmt |= set_bits(dma_pack, ACTIVE_DMA_PACK_MASK); > + writel(dma_fmt, dev->base + DMA0_FMT + vc * DMA_ADDR_OFFSET); > +} > + > +/* > + * fsd_csis_set_dma_dump() - set DMA dump OTF output without realigning > + * @dev: pointer to fsd_csis_dev structure > + * @vc: virtual channel id > + * @set_dump: boolean value enable = true/ disable = false > + * Return: none > + */ > +static void fsd_csis_set_dma_dump(struct fsd_csis_dev *dev, unsigned int vc, bool set_dump) > +{ > + u32 dma_fmt; > + > + dma_fmt = readl(dev->base + DMA0_FMT + vc * DMA_ADDR_OFFSET); > + dma_fmt &= reset_bits(DMA_DUMP_MASK); > + > + if (set_dump) > + dma_fmt |= set_bits(DMA_DUMP_OTF, DMA_DUMP_MASK); > + > + writel(dma_fmt, dev->base + DMA0_FMT + vc * DMA_ADDR_OFFSET); > +} > + > +/* > + * fsd_csis_set_dma_dimension() - set DMA memory storing style > + * @dev: pointer to fsd_csis_dev structure > + * @vc: virtual channel id > + * @set_dim: 0: Normal (2D DMA)/ 1: 1D DMA > + * Return: none > + */ > +static void fsd_csis_set_dma_dimension(struct fsd_csis_dev *dev, unsigned int vc, bool set_dim) > +{ > + u32 dma_fmt; > + > + dma_fmt = readl(dev->base + DMA0_FMT + vc * DMA_ADDR_OFFSET); > + dma_fmt &= reset_bits(ACTIVE_DMA_DIM_MASK); > + > + if (set_dim) > + dma_fmt |= set_bits(DMA_DIM_1D, ACTIVE_DMA_DIM_MASK); > + > + writel(dma_fmt, dev->base + DMA0_FMT + vc * DMA_ADDR_OFFSET); > +} > + > +/* > + * fsd_csis_set_dma_format() - set DMA format based > + * on selected image format > + * @ctx: pointer to CSI context > + * @fmt: image format > + * Return: none > + */ > +static void fsd_csis_set_dma_format(struct fsd_csis_ctx *ctx, const struct fsd_csis_fmt *fmt) > +{ > + unsigned int fourcc = fmt->fourcc; > + > + switch (fourcc) { > + case V4L2_PIX_FMT_SBGGR10: > + case V4L2_PIX_FMT_SGBRG10: > + case V4L2_PIX_FMT_SGRBG10: > + case V4L2_PIX_FMT_SRGGB10: > + fsd_csis_set_pack(ctx->dev, ctx->virtual_channel, DMA_PACK_10); > + break; > + case V4L2_PIX_FMT_SBGGR12: > + case V4L2_PIX_FMT_SGBRG12: > + case V4L2_PIX_FMT_SGRBG12: > + case V4L2_PIX_FMT_SRGGB12: > + fsd_csis_set_pack(ctx->dev, ctx->virtual_channel, DMA_PACK_12); > + break; > + case V4L2_PIX_FMT_SBGGR14P: > + fsd_csis_set_pack(ctx->dev, ctx->virtual_channel, DMA_PACK_14); > + break; > + case V4L2_PIX_FMT_BGR666: > + fsd_csis_set_pack(ctx->dev, ctx->virtual_channel, DMA_PACK_18); > + break; > + default: > + /* Default DMA_PACK_NORMAL will be used */ > + break; > + } > + > + fsd_csis_set_dma_dump(ctx->dev, ctx->virtual_channel, false); > + fsd_csis_set_dma_dimension(ctx->dev, ctx->virtual_channel, false); > +} > + > +/* > + * fsd_csis_dma_enable() - enable/disable DMA > + * @ctx: pointer to CSI context > + * @en_dma: boolean value enable = true/ disable = false > + * Return: none > + */ > +static void fsd_csis_dma_enable(struct fsd_csis_ctx *ctx, bool en_dma) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int dma_ctrl, vc = ctx->virtual_channel; > + > + dma_ctrl = readl(dev->base + DMA0_CTRL + DMA_CH_OFFSET * vc); > + /* DMA disable = 'b1, enable = 'b0 */ > + dma_ctrl |= set_bits(DMA_DISABLE, DMA_DISABLE_MASK); > + > + if (en_dma) > + dma_ctrl &= reset_bits(DMA_DISABLE_MASK); > + writel(dma_ctrl, dev->base + DMA0_CTRL + DMA_CH_OFFSET * vc); > +} > + > +/* > + * fsd_csis_set_interleave_mode() - set interleaving mode > + * @dev: pointer to fsd_csis_dev structure > + * @fsd_csis_interleave_mode: interleave mode value > + * Return: none > + */ > +static void fsd_csis_set_interleave_mode(struct fsd_csis_dev *dev, > + enum FSD_CSIS_INTERLEAVE csis_interleave_mode) > +{ > + u32 csis_cmn_ctrl; > + > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + csis_cmn_ctrl &= reset_bits(INTERLEAVE_MODE_MASK); > + csis_cmn_ctrl |= set_bits(csis_interleave_mode, INTERLEAVE_MODE_MASK); > + writel(csis_cmn_ctrl, dev->base + CSIS_CMN_CTRL); > +} > + > +/* > + * fsd_csis_enable_irqs_for_ctx() - enable interrupts for CSI context > + * @ctx: pointer to CSI context > + * Return: none > + */ > +static void fsd_csis_enable_irqs_for_ctx(struct fsd_csis_ctx *ctx) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int int_mask, vc = ctx->virtual_channel; > + > + int_mask = readl(dev->base + CSIS_INT_MSK0); > + int_mask |= set_bits(ERR_SOT_HS_ENABLE, ERR_SOT_HS_CH_MASK(vc)); > + int_mask |= set_bits(ERR_LOST_FS_ENABLE, ERR_LOST_FS_CH_MASK(vc)); > + int_mask |= set_bits(ERR_LOST_FE_ENABLE, ERR_LOST_FE_CH_MASK(vc)); > + writel(int_mask, dev->base + CSIS_INT_MSK0); > + > + int_mask = readl(dev->base + CSIS_INT_MSK1); > + int_mask |= set_bits(DMA_OTF_OVERLAP_ENABLE, DMA_OTF_OVERLAP_CH_MASK(vc)); > + int_mask |= set_bits(DMA_FRM_END_ENABLE, DMA_FRM_END_CH_MASK(vc)); > + int_mask |= set_bits(DMA_ABORT_ENABLE, DMA_ABORT_DONE_MASK); > + int_mask |= set_bits(DMA_ERROR_ENABLE, DMA_ERROR_MASK); > + writel(int_mask, dev->base + CSIS_INT_MSK1); > +} > + > +/* > + * fsd_csis_disable_irqs_for_ctx() - disable interrupts for CSI context > + * @ctx: pointer to CSI context > + * Return: none > + */ > +static void fsd_csis_disable_irqs_for_ctx(struct fsd_csis_ctx *ctx) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int int_mask, vc = ctx->virtual_channel; > + > + int_mask = readl(dev->base + CSIS_INT_MSK0); > + int_mask &= reset_bits(FRAMESTART_CH_MASK(vc)); > + int_mask &= reset_bits(FRAMEEND_CH_MASK(vc)); > + int_mask &= reset_bits(ERR_SOT_HS_CH_MASK(vc)); > + int_mask &= reset_bits(ERR_LOST_FS_CH_MASK(vc)); > + int_mask &= reset_bits(ERR_LOST_FE_CH_MASK(vc)); > + writel(int_mask, dev->base + CSIS_INT_MSK0); > + > + int_mask = readl(dev->base + CSIS_INT_MSK1); > + int_mask &= reset_bits(DMA_OTF_OVERLAP_CH_MASK(vc)); > + int_mask &= reset_bits(DMA_FRM_END_CH_MASK(vc)); > + int_mask &= reset_bits(LINE_END_CH_MASK(vc)); > + int_mask &= reset_bits(DMA_ABORT_DONE_MASK); > + int_mask &= reset_bits(DMA_ERROR_MASK); > + writel(int_mask, dev->base + CSIS_INT_MSK1); > +} > + > +/* > + * fsd_csis_dma_set_vid_base_addr() - set the DMA address for streaming > + * @ctx: pointer to CSI context > + * @frm_no: frame number for which DMA address to be set > + * @addr: address to use by CSI DMA > + * Return: none > + */ > +static void fsd_csis_dma_set_vid_base_addr(struct fsd_csis_ctx *ctx, int frm_no, unsigned long addr) > +{ > + struct fsd_csis_dev *dev = ctx->dev; > + unsigned int vc = ctx->virtual_channel; > + unsigned int dma_addr = 0; > + > + dma_addr = DMA0_ADDR1 + DMA_CH_OFFSET * vc; > + dma_addr = dma_addr + (frm_no * 4); > + mutex_lock(&dev->mutex_csis_dma_reg); > + writel(addr, dev->base + dma_addr); > + mutex_unlock(&dev->mutex_csis_dma_reg); > +} > + > +/* > + * fsd_csis_ip_configure() - configure CSI instance for streaming > + * @ctx: pointer to fsd_csis_ctx structure > + * Return: 0 on success. error value otherwise > + */ > +static void fsd_csis_ip_configure(struct fsd_csis_ctx *ctx) > +{ > + unsigned int i; > + struct fsd_csis_dev *dev; > + > + dev = ctx->dev; > + /* > + * Caution: CSI is reset every time during configuration > + * as recommended by initialization sequence. > + * In multi-stream scenario, reset should be avoided and > + * only format related configuration should be done > + */ > + fsd_csis_dphy_reset(dev, true); > + fsd_csis_sw_reset(dev); > + fsd_csis_mipi_dphy_init(dev); > + fsd_csis_set_vc_passing(ctx); > + > + if (!dev->nb_data_lane) > + dev->nb_data_lane = ctx->endpoint.bus.mipi_csi2.num_data_lanes; > + fsd_csis_set_interleave_mode(dev, VC_DT_BOTH); > + fsd_csis_set_update_shadow_ctrl(dev, true); > + > + /* DeSkew logic is needed when data lane speed is above or equal to 1500Mbps */ > + if (dev->lane_speed >= 1500) > + fsd_csis_enable_deskew_logic(dev, true); > + fsd_csis_set_hs_settle(dev, S_HSSETTLECTL_VAL); > + fsd_csis_setclk_settle_ctl(dev, S_CLKSETTLECTL_VAL); > + fsd_csis_set_num_of_datalane(dev, dev->nb_data_lane); > + > + for (i = 0; i < FSD_CSIS_MAX_VC; i++) { > + if (dev->ctx[i]) { > + fsd_csis_set_clkgate_en(dev->ctx[i], true); > + fsd_csis_set_clkgate_trail(dev->ctx[i], CLKGATE_TRAIL_VAL); > + } > + } > + > + fsd_csis_set_phy_on(dev, dev->nb_data_lane); > + > + for (i = 0; i < FSD_CSIS_MAX_VC; i++) { > + struct fsd_csis_ctx *temp_ctx = ctx->dev->ctx[i]; > + > + if (temp_ctx) { > + fsd_csis_set_pixel_mode(temp_ctx, temp_ctx->virtual_channel, temp_ctx->fmt); > + fsd_csis_set_img_fmt(temp_ctx, temp_ctx->virtual_channel, temp_ctx->fmt); > + fsd_csis_format_size(temp_ctx, temp_ctx->fmt, &temp_ctx->v_fmt); > + fsd_csis_set_hsync_lintv_timing(temp_ctx, temp_ctx->virtual_channel, > + HSYNC_LINTV); > + fsd_csis_set_dma_format(temp_ctx, temp_ctx->fmt); > + fsd_csis_update_shadow_ctx(temp_ctx); > + fsd_csis_dma_enable(temp_ctx, false); > + } > + } > + > + fsd_csis_set_dma_clk(dev); > + fsd_csis_dphy_reset(dev, false); > + fsd_csis_clear_vid_irqs(dev); > + > + for (i = 0; i < FSD_CSIS_MAX_VC; i++) { > + struct fsd_csis_ctx *temp_ctx = ctx->dev->ctx[i]; > + > + if (temp_ctx && ctx_stream_enabled(temp_ctx)) > + fsd_csis_enable_irqs_for_ctx(temp_ctx); > + } > +} > + > +/* > + * fsd_csis_irq_handler() - interrupt handler for CSI instance > + * @irq_csis: interrupt number of this CSI instance > + * @data: device structure of the CSI instance > + * Return: IRQ_HANDLED > + */ > +static irqreturn_t fsd_csis_irq_handler(int irq_csis, void *data) > +{ > + struct fsd_csis_dev *dev; > + struct fsd_csis_ctx *ctx; > + int vc; > + unsigned int int_src0 = 0x0, int_src1 = 0x0; > + unsigned int dma_frame_end = 0x0; > + unsigned int dma_frame_end_vc = 0x0; > + unsigned int int0_err = 0x0, int1_err = 0x0; > + unsigned int dma_error_code = 0x0, dma_error_vc = 0; > + > + dev = data; > + int_src0 = readl(dev->base + CSIS_INT_SRC0); > + int_src1 = readl(dev->base + CSIS_INT_SRC1); > + int0_err = get_bits(int_src0, CSIS_INT_SRC0_ERR_ALL_MASK); > + int1_err = get_bits(int_src1, CSIS_INT_SRC1_ERR_ALL_MASK); > + dma_frame_end = get_bits(int_src1, DMA_FRM_END_MASK); > + > + if (dma_frame_end || int1_err) { > + for (vc = 0; vc < FSD_CSIS_MAX_VC; vc++) { > + dma_frame_end_vc = (dma_frame_end >> vc) & 0x01; > + ctx = dev->ctx[vc]; > + > + if (ctx) { > + if (int1_err) { > + dma_error_vc = get_bits(int_src1, > + DMA_OTF_OVERLAP_CH_MASK(vc)); > + if (get_bits(int_src1, DMA_ERROR_MASK)) { > + dma_error_code = get_bits(int_src1, DMA_ERR_CODE); > + dma_error_vc |= get_bits(dma_error_code, > + (DMAFIFO_FULL_MASK | > + TRXFIFO_FULL_MASK)); > + dma_error_vc |= get_bits(dma_error_code, > + BRESP_ERROR_CH_MASK(vc)); > + } > + } > + > + if (dma_frame_end_vc || dma_error_vc) { > + ctx->dma_error = dma_error_vc; > + schedule_work(&ctx->csis_ctx_work); > + } > + } > + } > + } > + > + if (int0_err) > + fsd_csis_dbg(1, dev, "CSIS_INT_SRC0 ERRORS OCCURRED!: %08x\n", int0_err); > + > + if (int1_err) > + fsd_csis_dbg(1, dev, "DMA ERRORS OCCURRED!: %08x\n", int1_err); > + > + /* clear the interrupts */ > + writel(int_src0, dev->base + CSIS_INT_SRC0); > + writel(int_src1, dev->base + CSIS_INT_SRC1); > + > + return IRQ_HANDLED; > +} > + > +/* > + * fsd_csis_add_to_ring_buffer() - add vb2 buffer to DMA > + * @ctx: pointer to CSI context > + * @buf: pointer to fsd_csis_buffer structure > + * @index: index of DMA buffer address > + * Return: none > + */ > +static void fsd_csis_add_to_ring_buffer(struct fsd_csis_ctx *ctx, > + struct fsd_csis_buffer *buf, u8 index) > +{ > + ctx->frame[index] = buf; > + ctx->frame_addr[index] = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); > + fsd_csis_dma_set_vid_base_addr(ctx, index, ctx->frame_addr[index]); > +} > + > +/* > + * fsd_csis_irq_worker() - worker thread processing receieved image in DMA > + * @work: pointer to work_struct > + * Return: none > + */ > +static void fsd_csis_irq_worker(struct work_struct *work) > +{ > + struct fsd_csis_ctx *ctx = > + container_of(work, struct fsd_csis_ctx, csis_ctx_work); > + struct fsd_csis_buffer *buf_from; > + struct fsd_csis_buffer *buf_to; > + struct fsd_csis_dmaqueue *vidq = &ctx->vidq; > + unsigned int i; > + > + if (atomic_read(&ctx->end_irq_worker) == 0) > + return; > + > + ctx->current_dma_ptr = fsd_csis_current_dma_ptr(ctx); > + ctx->current_frame_counter = fsd_csis_current_frame_counter(ctx); > + > + if (ctx->dma_error) { > + ctx->prev_dma_ptr = ctx->current_dma_ptr; > + goto update_prev_counters; > + } > + > + if (ctx->current_dma_ptr >= ctx->prev_dma_ptr) > + ctx->number_of_ready_bufs = ctx->current_dma_ptr - ctx->prev_dma_ptr; > + else > + ctx->number_of_ready_bufs = FSD_CSIS_NB_DMA_OUT_CH - ctx->prev_dma_ptr + > + ctx->current_dma_ptr; > + > + for (i = 0; i < ctx->number_of_ready_bufs; i++) { > + ctx->prev_dma_ptr = (ctx->prev_dma_ptr + 1) % FSD_CSIS_NB_DMA_OUT_CH; > + > + mutex_lock(&ctx->mutex_buf); > + > + /* > + * Before dequeuing buffer from DMA at least > + * one buffer should be ready in vb2_queue > + */ > + if (list_empty(&vidq->active)) { > + mutex_unlock(&ctx->mutex_buf); > + fsd_csis_ctx_info(ctx, "active buffer queue empty\n"); > + ctx->prev_dma_ptr = ctx->current_dma_ptr; > + goto update_prev_counters; > + > + } else { > + buf_from = list_entry(vidq->active.next, struct fsd_csis_buffer, list); > + list_del(&buf_from->list); > + } > + > + mutex_unlock(&ctx->mutex_buf); > + buf_to = ctx->frame[ctx->prev_dma_ptr]; > + fsd_csis_add_to_ring_buffer(ctx, buf_from, ctx->prev_dma_ptr); > + > + if (buf_to) { > + buf_to->vb.vb2_buf.timestamp = ktime_get_ns(); > + vb2_buffer_done(&buf_to->vb.vb2_buf, VB2_BUF_STATE_DONE); > + } else { > + fsd_csis_ctx_err(ctx, "DMA buffer pointer is not valid\n"); > + } > + } > + > +update_prev_counters: > + ctx->prev_frame_counter = ctx->current_frame_counter; > +} > + > +/* > + * fsd_csis_ip_s_ctrl() - set new control value for CSI v4l2 device > + * @ctrl: pointer to control value passed by user > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_ip_s_ctrl(struct v4l2_ctrl *ctrl) > +{ > + struct fsd_csis_dev *dev = > + container_of(ctrl->handler, struct fsd_csis_dev, ctrl_handler); > + int ret = 0; > + > + switch (ctrl->id) { > + case V4L2_CID_USER_FSD_CSIS_NO_OF_LANE: > + > + dev->nb_data_lane = ctrl->val; > + if (!dev->stream_enabled) > + ret = fsd_csis_set_num_of_datalane(dev, dev->nb_data_lane); > + else > + ret = -EBUSY; > + default: > + break; > + } > + > + return ret; > +} > + > +/* > + * fsd_csis_enable() - enable CSI instance > + * @dev: pointer to fsd_csis_dev structure > + * Return: none > + */ > +static void fsd_csis_enable(struct fsd_csis_dev *dev) > +{ > + u32 csis_cmn_ctrl = 0; > + > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + csis_cmn_ctrl |= set_bits(CSI_EN, CSI_EN_MASK); > + writel(csis_cmn_ctrl, (dev->base + CSIS_CMN_CTRL)); > + > + fsd_csis_enable_vid_irqs(dev); > +} > + > +/* > + * fsd_csis_disable() - disable CSI instance > + * @dev: pointer to fsd_csis_dev structure > + * Return: none > + */ > +static void fsd_csis_disable(struct fsd_csis_dev *dev) > +{ > + u32 csis_cmn_ctrl = 0, i; > + > + for (i = 0; i < FSD_CSIS_MAX_VC; i++) { > + if (dev->ctx[i]) > + fsd_csis_dma_enable(dev->ctx[i], false); > + } > + > + csis_cmn_ctrl = readl(dev->base + CSIS_CMN_CTRL); > + > + /* Disable CSI */ > + csis_cmn_ctrl &= reset_bits(CSI_EN_MASK); > + writel(csis_cmn_ctrl, (dev->base + CSIS_CMN_CTRL)); > +} > + > +/* > + * find_format_by_pix() - find matching fourcc value of > + * context for given v4l2 pixel format > + * @ctx: pointer to CSI context > + * @pixelformat: pixel format to find > + * Return: pointer to csi_fmt on success, NULL otherwise > + */ > +static const struct fsd_csis_fmt *find_format_by_pix(struct fsd_csis_ctx *ctx, > + unsigned int pixelformat) > +{ > + const struct fsd_csis_fmt *fmt; > + unsigned int i; > + > + for (i = 0; i < ctx->num_active_fmt; i++) { > + fmt = ctx->active_fmt[i]; > + > + if (fmt->fourcc == pixelformat) > + return fmt; > + } > + > + return NULL; > +} > + > +/* > + * find_format_by_code() - find matching media bus code of > + * context for given v4l2 pixel format > + * @ctx: pointer to CSI context > + * @pixelformat: pixel format to find > + * Return: pointer to fsd_csis_fmt structure on success, NULL otherwise > + */ > +static const struct fsd_csis_fmt *find_format_by_code(struct fsd_csis_ctx *ctx, > + unsigned int pixelformat) > +{ > + const struct fsd_csis_fmt *fmt; > + unsigned int i; > + > + for (i = 0; i < ctx->num_active_fmt; i++) { > + fmt = ctx->active_fmt[i]; > + > + if (fmt->code == pixelformat) > + return fmt; > + } > + > + return NULL; > +} > + > +static inline struct fsd_csis_ctx *notifier_to_ctx(struct v4l2_async_notifier *n) > +{ > + return container_of(n, struct fsd_csis_ctx, notifier); > +} > + > +/* > + * fsd_csis_subdev_get_format() - get the sensor sub device format > + * @ctx: pointer to CSI context > + * @frmfmt: out parameter filled with subdev format > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_subdev_get_format(struct fsd_csis_ctx *ctx, struct v4l2_mbus_framefmt *frmfmt) > +{ > + struct v4l2_subdev_format sd_fmt; > + struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; > + int ret; > + > + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + sd_fmt.pad = 0; > + > + ret = v4l2_subdev_call(ctx->sensor, pad, get_fmt, NULL, &sd_fmt); > + > + if (ret) > + return ret; > + *frmfmt = *mbus_fmt; > + fsd_csis_ctx_dbg(3, ctx, "%dx%d code:%04X\n", frmfmt->width, frmfmt->height, frmfmt->code); > + return 0; > +} > + > +/* > + * fsd_csis_subdev_set_format() - set the sensor sub device format > + * @ctx: pointer to CSI context > + * @frmfmt: subdev format to set > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_subdev_set_format(struct fsd_csis_ctx *ctx, struct v4l2_mbus_framefmt *frmfmt) > +{ > + struct v4l2_subdev_format sd_fmt; > + struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; > + int ret; > + > + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + sd_fmt.pad = 0; > + *mbus_fmt = *frmfmt; > + > + ret = v4l2_subdev_call(ctx->sensor, pad, set_fmt, NULL, &sd_fmt); > + > + if (ret) > + return ret; > + *frmfmt = *mbus_fmt; > + return 0; > +} > + > +/* > + * fsd_csis_querycap() - provide v4l2_capability information > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @cap: out parameter filled with driver information > + * Return: 0 > + */ > +static int fsd_csis_querycap(struct file *file, void *priv, struct v4l2_capability *cap) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + > + strscpy(cap->driver, FSD_CSIS_MODULE_NAME, sizeof(cap->driver)); > + strscpy(cap->card, FSD_CSIS_MODULE_NAME, sizeof(cap->card)); > + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", ctx->v4l2_dev->name); > + return 0; > +} > + > +/* > + * fsd_csis_enum_fmt_vid_cap() - enumerate v4l2 format information > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @f: out parameter with enumerated format information > + * Return: 0 > + */ > +static int fsd_csis_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + const struct fsd_csis_fmt *fmt = NULL; > + > + if (f->index >= ctx->num_active_fmt) > + return -EINVAL; > + > + fmt = ctx->active_fmt[f->index]; > + f->pixelformat = fmt->fourcc; > + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + return 0; > +} > + > +/* > + * fsd_csis_try_fmt_vid_cap() - try image format to set > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @f: format to try. Can be overwrittenwith driver supported values. > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + const struct fsd_csis_fmt *fmt; > + struct v4l2_subdev_frame_size_enum fse; > + int ret, found; > + > + fmt = find_format_by_pix(ctx, f->fmt.pix.pixelformat); > + > + if (!fmt) { > + fsd_csis_ctx_info(ctx, > + "Fourcc format 0x%08x not found, setting active format 0x%08x\n", > + f->fmt.pix.pixelformat, ctx->active_fmt[0]->fourcc); > + > + /* Just get the first one enumerated */ > + fmt = ctx->active_fmt[0]; > + f->fmt.pix.pixelformat = fmt->fourcc; > + f->fmt.pix.colorspace = fmt->colorspace; > + } > + > + f->fmt.pix.field = ctx->v_fmt.fmt.pix.field; > + > + /* check for / find a valid width, height */ > + ret = 0; > + found = false; > + fse.pad = 0; > + fse.code = fmt->code; > + fse.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + > + /* loop through supported frame sizes by sensor > + * if there are none -EINVAL is returned from the sub-device > + */ > + for (fse.index = 0; ; fse.index++) { > + ret = v4l2_subdev_call(ctx->sensor, pad, enum_frame_size, NULL, &fse); > + > + if (ret) > + break; > + > + if (f->fmt.pix.width == fse.max_width && f->fmt.pix.height == fse.max_height) { > + found = true; > + break; > + } else if (f->fmt.pix.width <= fse.max_width && > + f->fmt.pix.height >= fse.min_height && > + f->fmt.pix.height <= fse.min_height) { > + found = true; > + break; > + } > + } > + > + if (!found) { > + fsd_csis_ctx_info(ctx, "Width %d Height %d not supported! Setting to %dx%d\n", > + f->fmt.pix.width, f->fmt.pix.height, ctx->v_fmt.fmt.pix.width, > + ctx->v_fmt.fmt.pix.height); > + /* use existing values as default */ > + f->fmt.pix.width = ctx->v_fmt.fmt.pix.width; > + f->fmt.pix.height = ctx->v_fmt.fmt.pix.height; > + } > + > + fsd_csis_format_size(ctx, fmt, f); > + return 0; > +} > + > +/* > + * fsd_csis_s_fmt_vid_cap() - set format to use > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @f: format to set > + * Return: 0 on success. error value otherwisen > + */ > +static int fsd_csis_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + struct vb2_queue *q = &ctx->vb_vidq; > + const struct fsd_csis_fmt *fmt; > + struct v4l2_mbus_framefmt mbus_fmt; > + int ret; > + > + if (vb2_is_busy(q)) { > + fsd_csis_ctx_dbg(3, ctx, "device busy: %d\n", q->num_buffers); > + return -EBUSY; > + } > + > + ret = fsd_csis_try_fmt_vid_cap(file, priv, f); > + > + if (ret < 0) { > + fsd_csis_ctx_err(ctx, "%x try format failed\n", f->fmt.pix.pixelformat); > + return ret; > + } > + > + fmt = find_format_by_pix(ctx, f->fmt.pix.pixelformat); > + > + if (!fmt) { > + fsd_csis_ctx_err(ctx, "Fourcc format (0x%08x) not found\n", f->fmt.pix.pixelformat); > + return -EINVAL; > + } > + > + v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, fmt->code); > + > + ret = fsd_csis_subdev_set_format(ctx, &mbus_fmt); > + > + if (ret) { > + fsd_csis_ctx_err(ctx, "%x not supported by subdev\n", f->fmt.pix.pixelformat); > + return ret; > + } > + > + if (mbus_fmt.code != fmt->code) { > + fsd_csis_ctx_dbg(3, ctx, "changed format! This should not happen.\n"); > + return -EINVAL; > + } > + > + v4l2_fill_pix_format(&ctx->v_fmt.fmt.pix, &mbus_fmt); > + ctx->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + ctx->v_fmt.fmt.pix.pixelformat = fmt->fourcc; > + ctx->v_fmt.fmt.pix.colorspace = fmt->colorspace; > + ctx->fmt = fmt; > + ctx->m_fmt = mbus_fmt; > + > + fsd_csis_ip_configure(ctx); > + *f = ctx->v_fmt; > + return 0; > +} > + > +/* > + * fsd_csis_g_fmt_vid_cap() - get current format in use > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @f: out parameter filled format information > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + > + *f = ctx->v_fmt; > + > + return 0; > +} > + > +/* > + * fsd_csis_enum_framesizes() - enumerate frame sizes > + * @file: pointer to file structure of v4l2 device > + * @fh: pointer to file handle > + * @fsize: enumerated frame sizes > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *fsize) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + const struct fsd_csis_fmt *fmt; > + struct v4l2_subdev_frame_size_enum fse; > + int ret; > + > + fmt = find_format_by_pix(ctx, fsize->pixel_format); > + > + if (!fmt) { > + fsd_csis_ctx_err(ctx, "Invalid pixel code: %x\n", fsize->pixel_format); > + return -EINVAL; > + } > + > + fse.index = fsize->index; > + fse.pad = 0; > + fse.code = fmt->code; > + > + ret = v4l2_subdev_call(ctx->sensor, pad, enum_frame_size, NULL, &fse); > + > + if (ret) > + return ret; > + > + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; > + fsize->discrete.width = fse.max_width; > + fsize->discrete.height = fse.max_height; > + return 0; > +} > + > +/* > + * fsd_csis_enum_frameintervals() - enumerate frame intervals > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @fival: enumerated frame interval information > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_enum_frameintervals(struct file *file, void *priv, > + struct v4l2_frmivalenum *fival) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + const struct fsd_csis_fmt *fmt; > + struct v4l2_subdev_frame_interval_enum fie = { > + .index = fival->index, > + .width = fival->width, > + .height = fival->height, > + .which = V4L2_SUBDEV_FORMAT_ACTIVE, > + }; > + int ret; > + > + fmt = find_format_by_pix(ctx, fival->pixel_format); > + > + if (!fmt) > + return -EINVAL; > + > + fie.code = fmt->code; > + ret = v4l2_subdev_call(ctx->sensor, pad, enum_frame_interval, NULL, &fie); > + > + if (ret) > + return ret; > + > + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; > + fival->discrete = fie.interval; > + return 0; > +} > + > +/* > + * fsd_csis_enum_input() - enumerate video input information > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @inp: video input information > + * Return: 0 > + */ > +static int fsd_csis_enum_input(struct file *file, void *priv, struct v4l2_input *inp) > +{ > + if (inp->index >= FSD_CSIS_NB_INPUT) > + return -EINVAL; > + > + inp->type = V4L2_INPUT_TYPE_CAMERA; > + snprintf(inp->name, sizeof(inp->name), "Camera %u\n", inp->index); > + return 0; > +} > + > +/* > + * fsd_csis_g_input() - get video input number > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @i: video input number > + * Return: 0 > + */ > +static int fsd_csis_g_input(struct file *file, void *priv, unsigned int *i) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + > + *i = ctx->input; > + > + return 0; > +} > + > +/* > + * fsd_csis_s_input() - select video input > + * @file: pointer to file structure of v4l2 device > + * @priv: file handle of v4l2 device > + * @i: video input number > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_s_input(struct file *file, void *priv, unsigned int i) > +{ > + struct fsd_csis_ctx *ctx = video_drvdata(file); > + > + if (i >= FSD_CSIS_NB_INPUT) > + return -EINVAL; > + ctx->input = i; > + return 0; > +} > + > +/* > + * fsd_csis_queue_setup() - sets up the number of buffers, > + * planes and size required for selected image format > + * @vq: vb2 bufffer queue in use > + * Return: 0 > + */ > +static int fsd_csis_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, > + unsigned int *nplanes, unsigned int sizes[], > + struct device *alloc_devs[]) > +{ > + struct fsd_csis_ctx *ctx = vb2_get_drv_priv(vq); > + unsigned int size = ctx->v_fmt.fmt.pix.sizeimage; > + > + if (*nplanes) { > + if (sizes[0] < size) > + return -EINVAL; > + size = sizes[0]; > + } > + > + *nplanes = 1; > + sizes[0] = size; > + fsd_csis_ctx_dbg(3, ctx, "nbuffers %d size %d\n", *nbuffers, sizes[0]); > + return 0; > +} > + > +/* > + * fsd_csis_buffer_prepare() - initialize and validate > + * the buffer size before queueing > + * @vb: pointer to vb2_buffer in use > + * Return: 0 or -EINVAL > + */ > +static int fsd_csis_buffer_prepare(struct vb2_buffer *vb) > +{ > + struct fsd_csis_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); > + struct fsd_csis_buffer *buf = container_of(vb, struct fsd_csis_buffer, > + vb.vb2_buf); > + unsigned long size, plane_size = 0; > + > + if (WARN_ON(!ctx->fmt)) > + return -EINVAL; > + > + size = ctx->v_fmt.fmt.pix.sizeimage; > + plane_size = vb2_plane_size(vb, 0); > + > + if (plane_size < size) { > + fsd_csis_ctx_err(ctx, "Data will not fit into plane (%lu < %lu)\n", plane_size, > + size); > + return -EINVAL; > + } > + > + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size); > + > + return 0; > +} > + > +/* > + * fsd_csis_buffer_queue() - pass the buffer vb to CSI for streaming > + * @vb: pointer to vb2_buffer in use > + * Return: none > + */ > +static void fsd_csis_buffer_queue(struct vb2_buffer *vb) > +{ > + struct fsd_csis_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); > + struct fsd_csis_buffer *buf = > + container_of(vb, struct fsd_csis_buffer, vb.vb2_buf); > + struct fsd_csis_dmaqueue *vidq = &ctx->vidq; > + > + mutex_lock(&ctx->mutex_buf); > + list_add_tail(&buf->list, &vidq->active); > + buf->sequence = ctx->sequence++; > + mutex_unlock(&ctx->mutex_buf); > +} > + > +/* > + * fsd_csis_start_streaming() - enter streaming for the CSI context > + * @q: pointer to vb2_queue in use > + * @count: number of already queued buffers > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_start_streaming(struct vb2_queue *q, unsigned int count) > +{ > + struct fsd_csis_ctx *ctx = vb2_get_drv_priv(q); > + struct fsd_csis_dev *dev = ctx->dev; > + struct fsd_csis_dmaqueue *vidq = &ctx->vidq; > + struct fsd_csis_buffer *buf, *tmp; > + int i, ret; > + u64 t_stamp; > + > + for (i = 0; i < FSD_CSIS_NB_DMA_OUT_CH; i++) { > + mutex_lock(&ctx->mutex_buf); > + > + if (list_empty(&vidq->active)) { > + mutex_unlock(&ctx->mutex_buf); > + fsd_csis_ctx_err(ctx, "Active buffer queue empty!\n"); > + return -EIO; > + } > + > + buf = list_entry(vidq->active.next, struct fsd_csis_buffer, list); > + list_del(&buf->list); > + fsd_csis_add_to_ring_buffer(ctx, buf, i); > + mutex_unlock(&ctx->mutex_buf); > + } > + > + ret = pm_runtime_resume_and_get(dev->device); > + > + if (ret < 0) > + goto error_stop; > + /* > + * save last frame counter and dma pointer location > + * just before enabling dma > + */ > + ctx->prev_dma_ptr = fsd_csis_current_dma_ptr(ctx); > + ctx->prev_frame_counter = fsd_csis_current_frame_counter(ctx); > + ctx->current_frame_counter = ctx->prev_frame_counter; > + fsd_csis_clear_vid_irqs(dev); > + fsd_csis_dma_enable(ctx, true); > + dev->stream_enabled |= (1 << ctx->virtual_channel); > + > + ret = v4l2_subdev_call(ctx->sensor, video, s_stream, 1); > + > + if (ret) { > + fsd_csis_ctx_err(ctx, "subdev start streaming failed! : %d\n", ret); > + goto error_stop; > + } > + atomic_set(&ctx->end_irq_worker, 1); > + fsd_csis_enable_irqs_for_ctx(ctx); > + fsd_csis_enable(dev); > + fsd_csis_ctx_info(ctx, "stream start vc %d\n", ctx->virtual_channel); > + > + return 0; > + > +error_stop: > + fsd_csis_dma_enable(ctx, false); > + pm_runtime_put_sync(dev->device); > + dev->stream_enabled &= (~(1 << ctx->virtual_channel)); > + t_stamp = ktime_get_ns(); > + > + list_for_each_entry_safe(buf, tmp, &vidq->active, list) { > + list_del(&buf->list); > + buf->vb.vb2_buf.timestamp = t_stamp; > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); > + } > + return ret; > +} > + > +/* > + * fsd_csis_stop_streaming() - stop streaming for CSI context > + * @q: pointer to vb2_queue in use > + * Return: none > + */ > +static void fsd_csis_stop_streaming(struct vb2_queue *q) > +{ > + struct fsd_csis_ctx *ctx = vb2_get_drv_priv(q); > + struct fsd_csis_dev *dev = ctx->dev; > + struct fsd_csis_dmaqueue *vidq = &ctx->vidq; > + struct fsd_csis_buffer *buf, *tmp; > + unsigned int timeout_cnt = 0; > + int i; > + void __iomem *dma_act_ctrl = 0; > + u64 t_stamp; > + > + fsd_csis_dma_enable(ctx, false); > + dev->stream_enabled &= (~(1 << ctx->virtual_channel)); > + fsd_csis_disable(dev); > + fsd_csis_disable_irqs_for_ctx(ctx); > + atomic_set(&ctx->end_irq_worker, 0); > + > + /* Wait for DMA Operation to finish */ > + dma_act_ctrl = dev->base + DMA0_ACT_CTRL + DMA_CH_OFFSET * ctx->virtual_channel; > + > + while ((readl(dma_act_ctrl) & 0x1) == 0x0) { > + if (timeout_cnt > 50) { > + fsd_csis_warn(dev, "DMA did not finish in 500ms.\n"); > + break; > + } > + usleep_range(10000, 20000); /* Wait min 10ms, max 20ms */ > + timeout_cnt++; > + } > + > + /* > + * If DMA operation still exists after disabled IRQ, it will > + * update dma_done part in interrupt source register. For next > + * streaming session, this could be interpreted as current session's > + * first frame done. To prevent this incorrect dma_done receiving, > + * clear the interrupt source register here. > + */ > + fsd_csis_clear_vid_irqs(dev); > + > + if (v4l2_subdev_call(ctx->sensor, video, s_stream, 0)) > + fsd_csis_ctx_err(ctx, "Failed to disable streaming in subdev\n"); > + fsd_csis_ctx_info(ctx, "stream stop vc %d\n", ctx->virtual_channel); > + > + pm_runtime_put_sync(dev->device); > + > + /* Release all active buffers */ > + mutex_lock(&ctx->mutex_buf); > + > + t_stamp = ktime_get_ns(); > + list_for_each_entry_safe(buf, tmp, &vidq->active, list) { > + list_del(&buf->list); > + buf->vb.vb2_buf.timestamp = t_stamp; > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); > + } > + mutex_unlock(&ctx->mutex_buf); > + > + for (i = 0; i < FSD_CSIS_NB_DMA_OUT_CH; i++) { > + buf = ctx->frame[i]; > + > + if (buf) { > + buf->vb.vb2_buf.timestamp = t_stamp; > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); > + } > + } > +} > + > +/* > + * Videobuf operations > + */ > +static const struct vb2_ops fsd_csis_video_ops = { > + .queue_setup = fsd_csis_queue_setup, > + .wait_prepare = vb2_ops_wait_prepare, > + .wait_finish = vb2_ops_wait_finish, > + .buf_prepare = fsd_csis_buffer_prepare, > + .start_streaming = fsd_csis_start_streaming, > + .stop_streaming = fsd_csis_stop_streaming, > + .buf_queue = fsd_csis_buffer_queue, > +}; > + > +static int fsd_csis_runtime_pm(struct fsd_csis_dev *dev, int on) > +{ > + int i, j, ret = 0; > + > + if (on) { > + if (!dev->ip_is_on) { > + ret = pm_runtime_get_sync(dev->device); > + > + for (i = 0; i < dev->nb_clocks; i++) { > + ret = clk_prepare_enable(dev->clk[i]); > + > + if (ret) { > + fsd_csis_err(dev, "clock %d enable Failed\n", i); > + for (j = 0; j < i; j++) > + clk_disable(dev->clk[j]); > + pm_runtime_put_sync(dev->device); > + return ret; > + } > + } > + enable_irq(dev->irq); > + dev->ip_is_on = true; > + } > + > + } else { > + if (!dev->stream_enabled && dev->ip_is_on) { > + disable_irq(dev->irq); > + > + for (i = 0; i < dev->nb_clocks; i++) > + clk_disable(dev->clk[i]); > + pm_runtime_put_sync(dev->device); > + dev->ip_is_on = false; > + } > + } > + > + return ret; > +} > + > +static const struct v4l2_ctrl_ops fsd_csis_ip_ctrl_ops = { > + .s_ctrl = fsd_csis_ip_s_ctrl, > +}; > + > +static const struct v4l2_ctrl_config fsd_csis_ip_set_nb_lane = { > + .ops = &fsd_csis_ip_ctrl_ops, > + .id = V4L2_CID_USER_FSD_CSIS_NO_OF_LANE, > + .name = "Set number of lanes for CSIS Rx controller", > + .type = V4L2_CTRL_TYPE_INTEGER, > + .min = 1, > + .max = 4, > + .step = 1, > + .def = 4, > +}; > + > +/* > + * fsd_csis_ctrl_notify() - get notified of controls of video device > + * @ctrl: pointer to control value passed by user > + * @priv: private data (pointer to struct fsd_csis_dev instance) > + * Return: None > + */ > +static void fsd_csis_ctrl_notify(struct v4l2_ctrl *ctrl, void *priv) > +{ > + struct fsd_csis_dev *dev = priv; > + > + switch (ctrl->id) { > + case V4L2_CID_USER_FSD_CSIS_NO_OF_LANE: > + dev->nb_data_lane = ctrl->val; > + if (!dev->stream_enabled) > + fsd_csis_set_num_of_datalane(dev, dev->nb_data_lane); > + break; > + } > +} > + > +/* > + * fsd_csis_async_complete() - complete binding and register sensor sub device > + * @notifier: v4l2 device notifier > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_async_complete(struct v4l2_async_notifier *notifier) > +{ > + struct fsd_csis_ctx *ctx = notifier_to_ctx(notifier); > + const struct fsd_csis_fmt *fmt; > + struct v4l2_mbus_framefmt mbus_fmt; > + int ret; > + > + ret = fsd_csis_subdev_get_format(ctx, &mbus_fmt); > + > + if (ret) { > + fsd_csis_ctx_err(ctx, "fsd_csis_subdev_get_format failed: %d\n", ret); > + return ret; > + } > + > + fmt = find_format_by_code(ctx, mbus_fmt.code); > + > + if (!fmt) { > + fsd_csis_ctx_err(ctx, "mubs code 0x%08X not found\n", mbus_fmt.code); > + return -EINVAL; > + } > + > + /* Save current subdev format */ > + v4l2_fill_pix_format(&ctx->v_fmt.fmt.pix, &mbus_fmt); > + ctx->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + ctx->v_fmt.fmt.pix.pixelformat = fmt->fourcc; > + ctx->v_fmt.fmt.pix.field = V4L2_FIELD_NONE; > + ctx->v_fmt.fmt.pix.colorspace = fmt->colorspace; > + ctx->v_fmt.fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; > + ctx->v_fmt.fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT; > + ctx->v_fmt.fmt.pix.xfer_func = V4L2_XFER_FUNC_SRGB; > + fsd_csis_format_size(ctx, fmt, &ctx->v_fmt); > + ctx->fmt = fmt; > + ctx->m_fmt = mbus_fmt; > + return 0; > +} > + > +/* > + * fsd_csis_fop_open() - open CSI v4l2 device > + * @filp: pointer to file structure > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_fop_open(struct file *filp) > +{ > + struct fsd_csis_ctx *ctx; > + int ret = -ENODEV; > + struct vb2_queue *q; > + > + ctx = video_drvdata(filp); > + > + if (ctx) { > + q = &ctx->vb_vidq; > + > + if (vb2_is_busy(q)) { > + fsd_csis_ctx_dbg(3, ctx, "device busy\n"); > + return -EBUSY; > + } > + ret = v4l2_fh_open(filp); > + > + if (ret) > + return ret; > + ret = fsd_csis_runtime_pm(ctx->dev, 1); > + } > + return ret; > +} > + > +/* > + * fsd_csis_fop_release() - release the file pertaining to CSI v4l2 device > + * @filp: pointer to file structure > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_fop_release(struct file *filp) > +{ > + struct fsd_csis_ctx *ctx; > + int ret; > + > + ret = vb2_fop_release(filp); > + > + if (ret) > + return ret; > + ctx = video_drvdata(filp); > + ret = fsd_csis_runtime_pm(ctx->dev, 0); > + return ret; > +} > + > +/* > + * Video device ioctls > + */ > +static const struct v4l2_ioctl_ops fsd_csis_ioctl_ops = { > + /* VIDIOC_QUERYCAP handler */ > + .vidioc_querycap = fsd_csis_querycap, > + > + /* VIDIOC_ENUM_FMT handlers */ > + .vidioc_enum_fmt_vid_cap = fsd_csis_enum_fmt_vid_cap, > + > + /* VIDIOC_G_FMT handlers */ > + .vidioc_g_fmt_vid_cap = fsd_csis_g_fmt_vid_cap, > + > + /* VIDIOC_S_FMT handlers */ > + .vidioc_s_fmt_vid_cap = fsd_csis_s_fmt_vid_cap, > + > + /* VIDIOC_TRY_FMT handlers */ > + .vidioc_try_fmt_vid_cap = fsd_csis_try_fmt_vid_cap, > + > + /* Buffer handlers */ > + .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, > + > + /* Stream on/off */ > + .vidioc_streamon = vb2_ioctl_streamon, > + .vidioc_streamoff = vb2_ioctl_streamoff, > + > + /* Input handling */ > + .vidioc_enum_input = fsd_csis_enum_input, > + .vidioc_g_input = fsd_csis_g_input, > + .vidioc_s_input = fsd_csis_s_input, > + > + /* Sliced VBI cap */ > + .vidioc_log_status = v4l2_ctrl_log_status, > + > + /* Debugging ioctls */ > + .vidioc_enum_framesizes = fsd_csis_enum_framesizes, > + .vidioc_enum_frameintervals = fsd_csis_enum_frameintervals, > + > + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, > + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, > +}; > + > +/* > + * V4L2 File operations > + */ > +static const struct v4l2_file_operations fsd_csis_fops = { > + .owner = THIS_MODULE, > + .read = vb2_fop_read, > + .poll = vb2_fop_poll, > + .unlocked_ioctl = video_ioctl2, > + .mmap = vb2_fop_mmap, > + .open = fsd_csis_fop_open, > + .release = fsd_csis_fop_release, > +}; > + > +static struct video_device fsd_csis_videodev = { > + .fops = &fsd_csis_fops, > + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE, No READWRITE please, that's deprecated for this kind of use case. > + .name = FSD_CSIS_MODULE_NAME, > + .minor = -1, > + .release = video_device_release_empty, > + .ioctl_ops = &fsd_csis_ioctl_ops, > +}; > + > +/* > + * fsd_csis_complete_ctx() - > + * @ctx: pointer to CSI context > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_complete_ctx(struct fsd_csis_ctx *ctx) > +{ > + struct video_device *vdev; > + struct vb2_queue *q; > + int ret; > + > + ret = v4l2_device_register_subdev_nodes(ctx->v4l2_dev); > + > + if (ret) > + v4l2_warn(ctx->v4l2_dev, "V4L2 register subdev nodes failed: %d\n", ret); > + > + ctx->timesperframe = fsd_csis_tpf_default; > + > + /* initialize locks */ > + mutex_init(&ctx->mutex); > + mutex_init(&ctx->mutex_buf); > + > + /* initialize vb2_queue */ > + q = &ctx->vb_vidq; > + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ | VB2_USERPTR; Same here, and no USERPTR either. > + q->drv_priv = ctx; > + q->buf_struct_size = sizeof(struct fsd_csis_buffer); > + q->ops = &fsd_csis_video_ops; > + q->mem_ops = &vb2_dma_contig_memops; > + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; > + q->lock = &ctx->mutex; > + q->min_buffers_needed = FSD_CSIS_NB_MIN_CH; > + q->dev = ctx->dev->device; > + dma_set_coherent_mask(ctx->dev->device, DMA_BIT_MASK(FSD_CSIS_DMA_COHERENT_MASK_SIZE)); > + > + ret = vb2_queue_init(q); > + > + if (ret) > + return ret; > + > + /* initialize video DMA queue */ > + INIT_LIST_HEAD(&ctx->vidq.active); > + > + vdev = &ctx->vdev; > + *vdev = fsd_csis_videodev; > + vdev->v4l2_dev = ctx->v4l2_dev; > + vdev->queue = q; > + video_set_drvdata(vdev, ctx); > + > + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; > + ret = video_register_device(vdev, VFL_TYPE_VIDEO, video_nr); > + > + if (ret) > + return ret; > + > + v4l2_info(ctx->v4l2_dev, "Video device registered as %s\n", video_device_node_name(vdev)); > + return ret; > +} > + > +/* > + * fsd_csis_async_bound() - > + * @notifier: > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_async_bound(struct v4l2_async_notifier *notifier, struct v4l2_subdev *subdev, > + struct v4l2_async_subdev *asd) > +{ > + struct fsd_csis_dev *dev = NULL; > + struct fsd_csis_ctx *ctx = notifier_to_ctx(notifier); > + const struct fsd_csis_fmt *fmt; > + struct v4l2_subdev_mbus_code_enum mbus_code; > + int i, j, k, ret = 0; > + > + dev = ctx->dev; > + > + /* each of dev->ctx have their own asd and sensor subdevs */ > + if (ctx->asd.match.fwnode == > + of_fwnode_handle(subdev->dev->of_node)) { > + ctx->sensor = subdev; > + } else { > + fsd_csis_ctx_err(ctx, "No matching sensor node for found!\n"); > + return -ENODEV; > + } > + > + v4l2_set_subdev_hostdata(subdev, ctx); > + > + v4l2_info(ctx->v4l2_dev, "Hooked sensor subdevice: %s to parent\n", subdev->name); > + > + /* Enumerate subdevice formates and enable matching csis formats */ > + ctx->num_active_fmt = 0; > + > + for (i = 0, j = 0; ret != -EINVAL; ++j) { > + memset(&mbus_code, 0, sizeof(mbus_code)); > + mbus_code.index = j; > + mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + ret = v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL, &mbus_code); > + > + if (ret) > + continue; > + > + for (k = 0; k < ARRAY_SIZE(fsd_csis_formats); k++) { > + fmt = &fsd_csis_formats[k]; > + > + if (mbus_code.code == fmt->code) { > + ctx->active_fmt[i] = fmt; > + ctx->num_active_fmt = ++i; > + break; > + } > + } > + } > + > + if (!i) > + fsd_csis_ctx_err(ctx, "No matching format found by subdev %s\n", subdev->name); > + ret = fsd_csis_complete_ctx(ctx); > + > + if (ret) { > + fsd_csis_ctx_err(ctx, "Failed to register video device for csis%d-%d\n", > + dev->id, ctx->virtual_channel); > + return ret; > + } > + > + return 0; > +} > + > +static const struct v4l2_async_notifier_operations fsd_csis_async_notifier_ops = { > + .bound = fsd_csis_async_bound, > + .complete = fsd_csis_async_complete, > +}; > + > +/* > + * of_get_next_port() - > + * @parent: struct device_node > + * Return: pointer to the device node on success, NULL value otherwise > + */ > +static struct device_node *of_get_next_port(const struct device_node *parent, > + struct device_node *prev) > +{ > + struct device_node *port = NULL; > + > + if (!parent) > + return NULL; > + > + if (!prev) { > + struct device_node *ports; > + /* > + * It's the first csis, we have to find a port subnode > + * within this node or within an optional 'ports' node. > + */ > + ports = of_get_child_by_name(parent, "ports"); > + > + if (ports) > + parent = ports; > + > + port = of_get_child_by_name(parent, "port"); > + /* release the 'ports' node */ > + of_node_put(ports); > + } else { > + struct device_node *ports; > + > + ports = of_get_parent(prev); > + > + if (!ports) > + return NULL; > + > + do { > + port = of_get_next_child(ports, prev); > + > + if (!port) { > + of_node_put(ports); > + return NULL; > + } > + prev = port; > + } while (!of_node_name_eq(port, "port")); > + of_node_put(ports); > + } > + return port; > +} > + > +/* > + * of_get_next_endpoint() - > + * @parent: pointer to struct device_node > + * Return: pointer to the device node on success, NULL value otherwise > + */ > +static struct device_node *of_get_next_endpoint(const struct device_node *parent, > + struct device_node *prev) > +{ > + struct device_node *ep = NULL; > + > + if (!parent) > + return NULL; > + > + do { > + ep = of_get_next_child(parent, prev); > + > + if (!ep) > + return NULL; > + prev = ep; > + } while (!of_node_name_eq(ep, "endpoint")); > + > + return ep; > +} > + > +/* > + * of_create_fsd_csis_context() - Parse the device node for local (csis port) > + * and remote endpoint (sensor node) properties. > + * Fill the sensor node properties into V4L2 endpoint descriptor > + * for later use > + * @ctx: pointer to CSI context > + * @inst: CSI instance virtual channel ID for which CSI context is to be > + * created > + * Return: 0 on success. error value otherwise > + */ > +static int of_create_fsd_csis_context(struct fsd_csis_ctx *ctx, int inst) > +{ > + struct device *device = ctx->dev->device; > + struct device_node *parent_node = NULL, *port = NULL, *ep_node = NULL, > + *remote_ep = NULL, *sensor_node = NULL; > + struct v4l2_fwnode_endpoint *endpoint; > + struct v4l2_async_subdev *asd; > + int ret = 0, i; > + unsigned int regval = 0x0; > + bool found_port = false; > + > + parent_node = device->of_node; > + endpoint = &ctx->endpoint; > + > + for (i = 0; i < FSD_CSIS_MAX_VC; i++) { > + port = of_get_next_port(parent_node, port); > + > + if (!port) { > + ret = -ENODEV; > + goto cleanup_exit; > + } > + > + of_property_read_u32(port, "reg", ®val); > + > + if (regval == inst) { > + found_port = true; > + break; > + } > + } > + > + if (!found_port) { > + ret = -ENODEV; > + fsd_csis_dbg(2, ctx->dev, "no matching port %d found\n", inst); > + goto cleanup_exit; > + } > + > + ep_node = of_get_next_endpoint(port, ep_node); > + > + if (!ep_node) { > + fsd_csis_err(ctx->dev, "get endpoint failed: %ld\n", PTR_ERR(port)); > + ret = -ENODEV; > + goto cleanup_exit; > + } > + > + sensor_node = of_graph_get_remote_port_parent(ep_node); > + > + if (!sensor_node) { > + fsd_csis_err(ctx->dev, "get sensor node failed: %ld\n", PTR_ERR(sensor_node)); > + ret = -ENODEV; > + goto cleanup_exit; > + } > + > + remote_ep = of_parse_phandle(ep_node, "remote-endpoint", 0); > + > + if (!remote_ep) { > + fsd_csis_err(ctx->dev, "get remote endpoint failed %ld\n", PTR_ERR(remote_ep)); > + ret = -ENODEV; > + goto cleanup_exit; > + } > + > + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(remote_ep), endpoint); > + > + if (ret) { > + fsd_csis_err(ctx->dev, "parse endpoint failed: %ld\n", PTR_ERR(remote_ep)); > + ret = -ENODEV; > + goto cleanup_exit; > + } > + > + /* Store virtual channel id */ > + ctx->virtual_channel = inst; > + > + asd = &ctx->asd; > + asd->match_type = V4L2_ASYNC_MATCH_FWNODE; > + asd->match.fwnode = of_fwnode_handle(sensor_node); > + > + v4l2_async_nf_init(&ctx->notifier); > + > + ret = __v4l2_async_nf_add_subdev(&ctx->notifier, asd); > + > + if (ret) { > + fsd_csis_err(ctx->dev, "add asd to notifier fail: %d", ret); > + goto cleanup_exit; > + } > + > + sensor_node = NULL; > + > +cleanup_exit: > + > + if (!remote_ep) > + of_node_put(remote_ep); > + > + if (!sensor_node) > + of_node_put(sensor_node); > + > + if (!ep_node) > + of_node_put(ep_node); > + > + if (!port) > + of_node_put(port); > + return ret; > +} > + > +/* > + * fsd_csis_create_context() - create CSI context for virtual channel > + * @dev: pointer to fsd_csis_dev structure > + * @inst: value of virtual channel > + * Return: pointer to CSI context structure on success, NULL value otherwise > + */ > +static struct fsd_csis_ctx *fsd_csis_create_context(struct fsd_csis_dev *dev, int inst) > +{ > + struct fsd_csis_ctx *ctx; > + int ret; > + > + ctx = devm_kzalloc(dev->device, sizeof(*ctx), GFP_KERNEL); > + > + if (!ctx) > + return NULL; > + ctx->dev = dev; > + ret = of_create_fsd_csis_context(ctx, inst); > + > + if (ret) > + goto free_ctx; > + > + ctx->v4l2_dev = &dev->v4l2_dev; > + ctx->notifier.ops = &fsd_csis_async_notifier_ops; > + ret = v4l2_async_nf_register(ctx->v4l2_dev, &ctx->notifier); > + > + if (ret < 0) { > + fsd_csis_ctx_err(ctx, "async notifer register failed: %d\n", ret); > + v4l2_async_nf_cleanup(&ctx->notifier); > + goto unregister_device; > + } > + > + ctx->dev->stream_enabled &= (~(1 << ctx->virtual_channel)); > + ctx->sequence = 0; > + return ctx; > + > +unregister_device: > + v4l2_device_unregister(ctx->v4l2_dev); > + > +free_ctx: > + devm_kfree(dev->device, ctx); > + return NULL; > +} > + > +/* > + * fsd_csis_delete_context() - delete the contextx instances > + * @dev: pointer to fds_csis_dev structure > + * Return: None > + */ > +static void fsd_csis_delete_context(struct fsd_csis_dev *dev) > +{ > + int i; > + struct fsd_csis_ctx *ctx; > + > + for (i = 0; i < FSD_CSIS_MAX_VC; i++) { > + ctx = dev->ctx[i]; > + > + if (ctx) { > + fsd_csis_ctx_dbg(3, ctx, "unregistering %s\n", > + video_device_node_name(&ctx->vdev)); > + v4l2_async_nf_unregister(&ctx->notifier); > + video_unregister_device(&ctx->vdev); > + cancel_work_sync(&dev->ctx[i]->csis_ctx_work); > + mutex_destroy(&ctx->mutex); > + mutex_destroy(&ctx->mutex_buf); > + devm_kfree(dev->device, ctx); > + } > + dev->ctx[i] = NULL; > + } > +} > + > +/* > + * fsd_csis_probe() - CSI driver probe method > + * @pdev: pointer to platform_device structure for CSI driver > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_probe(struct platform_device *pdev) > +{ > + struct fsd_csis_dev *dev; > + int i, ret = 0; > + unsigned int irq; > + char name[24]; > + struct resource *res; > + > + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); > + > + if (!dev) > + return -ENOMEM; > + > + /* save struct device information */ > + dev->device = &pdev->dev; > + dev->id = of_alias_get_id(pdev->dev.of_node, "csis"); > + dev->info = of_device_get_match_data(dev->device); > + > + /* Get Register and DMA resources, IRQ */ > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + > + if (!res) { > + dev_err(dev->device, "get register base failed\n"); > + return -ENODEV; > + } > + dev->base = devm_ioremap_resource(dev->device, res); > + > + if (IS_ERR(dev->base)) > + return PTR_ERR(dev->base); > + > + dev->sysreg_map = syscon_regmap_lookup_by_phandle(dev->device->of_node, "sysreg_csi"); > + > + if (IS_ERR(dev->sysreg_map)) { > + ret = PTR_ERR(dev->sysreg_map); > + dev_err(&pdev->dev, "sysreg map failed: %d\n", ret); > + return ret; > + } > + > + irq = platform_get_irq(pdev, 0); > + > + if (irq < 0) > + return irq; > + > + ret = devm_request_irq(dev->device, irq, fsd_csis_irq_handler, 0, > + dev_name(dev->device), dev); > + > + if (ret) { > + dev_err(dev->device, "IRQ %d get failed: %d\n", irq, ret); > + return ret; > + } > + > + for (i = 0; i < dev->info->nb_clocks; i++) { > + snprintf(name, sizeof(name), "csis-%s", dev->info->clk_names[i]); > + dev->clk[i] = devm_clk_get(dev->device, name); > + > + if (IS_ERR(dev->clk[i])) { > + ret = PTR_ERR(dev->clk[i]); > + dev_err(dev->device, "Clock %s get failed: %d\n", name, ret); > + return ret; > + } > + dev->nb_clocks++; > + pr_debug("%s clock added\n", name); > + } > + > + platform_set_drvdata(pdev, dev); > + mutex_init(&dev->mutex_csis_dma_reg); > + > + /* set pseudo v4l2 device name for use in printk */ > + v4l2_device_set_name(&dev->v4l2_dev, FSD_CSIS_MODULE_NAME, &drv_instance); > + ret = v4l2_device_register(dev->device, &dev->v4l2_dev); > + > + if (ret < 0) { > + v4l2_err(&dev->v4l2_dev, "register v4l2_device failed: %d\n", ret); > + return ret; > + } > + > + ret = v4l2_ctrl_handler_init(&dev->ctrl_handler, 1); > + > + if (ret) > + v4l2_err(&dev->v4l2_dev, "control handler init failed: %d\n", ret); > + > + v4l2_ctrl_new_custom(&dev->ctrl_handler, &fsd_csis_ip_set_nb_lane, NULL); > + > + if (dev->ctrl_handler.error) { > + ret = dev->ctrl_handler.error; > + v4l2_err(&dev->v4l2_dev, "add control for setting CSIS Rx lanes failed: %d\n", ret); > + goto unregister_device; > + } > + > + v4l2_ctrl_notify(v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_USER_FSD_CSIS_NO_OF_LANE), > + fsd_csis_ctrl_notify, dev); > + dev->v4l2_dev.ctrl_handler = &dev->ctrl_handler; > + v4l2_ctrl_handler_setup(&dev->ctrl_handler); > + > + for (i = 0; i < FSD_CSIS_MAX_VC; i++) { > + dev->ctx[i] = fsd_csis_create_context(dev, i); > + > + if (dev->ctx[i]) > + INIT_WORK(&dev->ctx[i]->csis_ctx_work, fsd_csis_irq_worker); > + } > + > + dev->ip_is_on = false; > + dev->lane_speed = FSD_CSIS_RX_BW; > + pm_runtime_enable(dev->device); > + ret = pm_runtime_resume_and_get(dev->device); > + > + if (ret) > + goto runtime_disable; > + pm_runtime_put_sync(dev->device); > + return 0; > + > +runtime_disable: > + pm_runtime_disable(dev->device); > + > +unregister_device: > + v4l2_device_unregister(&dev->v4l2_dev); > + fsd_csis_delete_context(dev); > + > + return ret; > +} > + > +/* > + * fsd_csis_remove() - CSI device remove mothod > + * @pdev: pointer to platform_device structure > + * Return: 0 on success. error value otherwise > + */ > +static int fsd_csis_remove(struct platform_device *pdev) > +{ > + struct fsd_csis_dev *dev = > + (struct fsd_csis_dev *)platform_get_drvdata(pdev); > + int ret; > + > + fsd_csis_disable(dev); > + ret = pm_runtime_resume_and_get(dev->device); > + > + v4l2_ctrl_handler_free(&dev->ctrl_handler); > + v4l2_device_unregister(&dev->v4l2_dev); > + > + fsd_csis_delete_context(dev); > + mutex_destroy(&dev->mutex_csis_dma_reg); > + > + if (ret >= 0) > + pm_runtime_put_sync(dev->device); > + pm_runtime_disable(&pdev->dev); > + > + return 0; > +} > + > +static void fsd_csis_shutdown(struct platform_device *pdev) > +{ > + struct fsd_csis_dev *dev = > + (struct fsd_csis_dev *)platform_get_drvdata(pdev); > + > + fsd_csis_disable_irqs(dev); > + fsd_csis_disable(dev); > +} > + > +static struct fsd_csis_dev_info fsd_csis_dev_info_v4_3 = { > + .version = FSD_CSIS_VERSION_4_3, > + .nb_clocks = 1, > + .clk_names = { "aclk" }, > +}; > + > +static const struct of_device_id fsd_csis_of_match[] = { > + { > + .compatible = "tesla,fsd-csis", > + .data = &fsd_csis_dev_info_v4_3, > + }, > + {}, > +}; > + > +MODULE_DEVICE_TABLE(of, fsd_csis_of_match); > + > +static struct platform_driver fsd_csis_driver = { > + .probe = fsd_csis_probe, > + .remove = fsd_csis_remove, > + .shutdown = fsd_csis_shutdown, > + .driver = { > + .name = FSD_CSIS_MODULE_NAME, > + .of_match_table = of_match_ptr(fsd_csis_of_match), > + }, > +}; > + > +module_platform_driver(fsd_csis_driver); > + > +MODULE_DESCRIPTION("FSD CSIS Driver"); > +MODULE_AUTHOR("Sathyakam M, <sathya@xxxxxxxxxxx>"); > +MODULE_LICENSE("GPL"); > +MODULE_VERSION(FSD_CSIS_MODULE_VERSION); > diff --git a/drivers/media/platform/fsd/fsd-csis.h b/drivers/media/platform/fsd/fsd-csis.h > new file mode 100644 > index 000000000000..b990da903f87 > --- /dev/null > +++ b/drivers/media/platform/fsd/fsd-csis.h > @@ -0,0 +1,785 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * FSD CSIS camera interface driver > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License version 2 as published by > + * the Free Software Foundation > + */ > + > +#ifndef _FSD_CSIS_H > +#define _FSD_CSIS_H > + > +/* Select D-PHY control values for FSD CSI Rx controller */ > +#if defined(CONFIG_FSD_CSI_2100_MEGA_BITS_PER_SEC) > + > +/* PHY control values for 2100 Mbps */ > +#define S_CLKSETTLECTL_VAL 0x00 > +#define S_HSSETTLECTL_VAL 0x2e > +#define FSD_CSIS_RX_BW 2100 > + > +#elif defined(CONFIG_FSD_CSI_1600_MEGA_BITS_PER_SEC) > + > +/* PHY control values 1600 Mbps */ > +#define S_CLKSETTLECTL_VAL 0x00 > +#define S_HSSETTLECTL_VAL 0x23 > +#define FSD_CSIS_RX_BW 1600 > + > +#elif defined(CONFIG_FSD_CSI_1500_MEGA_BITS_PER_SEC) > + > +/* PHY control values for 1500 Mbps */ > +#define S_CLKSETTLECTL_VAL 0x00 > +#define S_HSSETTLECTL_VAL 0x21 > +#define FSD_CSIS_RX_BW 1500 > + > +#elif defined(CONFIG_FSD_CSI_1000_MEGA_BITS_PER_SEC) > + > +/* PHY control values for 1000 Mbps */ > +#define S_CLKSETTLECTL_VAL 0x00 > +#define S_HSSETTLECTL_VAL 0x16 > +#define FSD_CSIS_RX_BW 1000 > + > +#else > + > +/* PHY control values for 800 Mbps and below */ > +#define S_CLKSETTLECTL_VAL 0x00 > +#define S_HSSETTLECTL_VAL 0x11 > +#define FSD_CSIS_RX_BW 800 > + > +#endif > + > +#define HSYNC_LINTV 0x20 > +#define CLKGATE_TRAIL_VAL 0x07 > +#define DMA_CLK_GATE_TRAIL 0x07 > + > +/* SYSREG_CSI offsets */ > +#define SW_RESETEN_DPHY 0x40c > + > +#define CSIS_SW_RESETEN_DPHY 0x1 > +#define CSIS_SW_RESETEN_DPHY_MASK(phy) BIT_MASK(phy) > + > +/* > + * CSI register offsets > + * (Refer to sf_csis_v6p00 sheet of SFR doc) > + */ > +#define CSIS_VERSION 0x0000 > +#define CSIS_CMN_CTRL 0x0004 > +#define CSIS_CLK_CTRL 0x0008 > +#define CSIS_INT_MSK0 0x0010 > +#define CSIS_INT_SRC0 0x0014 > +#define CSIS_INT_MSK1 0x0018 > +#define CSIS_INT_SRC1 0x001c > +#define PHY_STATUS 0x0020 > +#define PHY_CMN_CTRL 0x0024 > +#define PHY_BCTRL_L 0x0030 > +#define PHY_BCTRL_H 0x0034 > +#define PHY_SCTRL_L 0x0038 > +#define PHY_SCTRL_H 0x003c > +#define ISP_CONFIG_CH0 0x0040 > +#define ISP_RESOL_CH0 0x0044 > +#define ISP_SYNC_CH0 0x0048 > +#define ISP_CONFIG_CH1 0x0050 > +#define ISP_RESOL_CH1 0x0054 > +#define ISP_SYNC_CH1 0x0058 > +#define ISP_CONFIG_CH2 0x0060 > +#define ISP_RESOL_CH2 0x0064 > +#define ISP_SYNC_CH2 0x0068 > +#define ISP_CONFIG_CH3 0x0070 > +#define ISP_RESOL_CH3 0x0074 > +#define ISP_SYNC_CH3 0x0078 > +#define SDW_CONFIG_CH0 0x0080 > +#define SDW_RESOL_CH0 0x0084 > +#define SDW_SYNC_CH0 0x0088 > +#define SDW_CONFIG_CH1 0x0090 > +#define SDW_RESOL_CH1 0x0094 > +#define SDW_SYNC_CH1 0x0098 > +#define SDW_CONFIG_CH2 0x00a0 > +#define SDW_RESOL_CH2 0x00a4 > +#define SDW_SYNC_CH2 0x00a8 > +#define SDW_CONFIG_CH3 0x00b0 > +#define SDW_RESOL_CH3 0x00b4 > +#define SDW_SYNC_CH3 0x00b8 > +#define FRM_CNT_CH0 0x0100 > +#define FRM_CNT_CH1 0x0104 > +#define FRM_CNT_CH2 0x0108 > +#define FRM_CNT_CH3 0x010c > +#define LINE_INTR_CH0 0x0110 > +#define LINE_INTR_CH1 0x0114 > +#define LINE_INTR_CH2 0x0118 > +#define LINE_INTR_CH3 0x011c > +#define VC_PASSING 0x0120 > +#define DMA0_CTRL 0x1000 > +#define DMA0_FMT 0x1004 > +#define DMA0_SKIP 0x1008 > +#define DMA0_ADDR1 0x1010 > +#define DMA0_ADDR2 0x1014 > +#define DMA0_ADDR3 0x1018 > +#define DMA0_ADDR4 0x101c > +#define DMA0_ADDR5 0x1020 > +#define DMA0_ADDR6 0x1024 > +#define DMA0_ADDR7 0x1028 > +#define DMA0_ADDR8 0x102c > +#define DMA0_ACT_CTRL 0x1030 > +#define DMA0_ACT_FMT 0x1034 > +#define DMA0_ACT_SKIP 0x1038 > +#define DMA0_BYTE_CNT 0x1040 > +#define DMA1_CTRL 0x1100 > +#define DMA1_FMT 0x1104 > +#define DMA1_SKIP 0x1108 > +#define DMA1_ADDR1 0x1110 > +#define DMA1_ADDR2 0x1114 > +#define DMA1_ADDR3 0x1118 > +#define DMA1_ADDR4 0x111c > +#define DMA1_ADDR5 0x1120 > +#define DMA1_ADDR6 0x1124 > +#define DMA1_ADDR7 0x1128 > +#define DMA1_ADDR8 0x112c > +#define DMA1_ACT_CTRL 0x1130 > +#define DMA1_ACT_FMT 0x1134 > +#define DMA1_BYTE_CNT 0x1140 > +#define DMA2_CTRL 0x1200 > +#define DMA2_FMT 0x1204 > +#define DMA2_SKIP 0x1208 > +#define DMA2_ADDR1 0x1210 > +#define DMA2_ADDR2 0x1214 > +#define DMA2_ADDR3 0x1218 > +#define DMA2_ADDR4 0x121c > +#define DMA2_ADDR5 0x1220 > +#define DMA2_ADDR6 0x1224 > +#define DMA2_ADDR7 0x1228 > +#define DMA2_ADDR8 0x122c > +#define DMA2_ACT_CTRL 0x1230 > +#define DMA2_ACT_FMT 0x1234 > +#define DMA2_ACT_SKIP 0x1238 > +#define DMA2_BYTE_CNT 0x1240 > +#define DMA3_CTRL 0x1300 > +#define DMA3_FMT 0x1304 > +#define DMA3_SKIP 0x1308 > +#define DMA3_ADDR1 0x1310 > +#define DMA3_ADDR2 0x1314 > +#define DMA3_ADDR3 0x1318 > +#define DMA3_ADDR4 0x131c > +#define DMA3_ADDR5 0x1320 > +#define DMA3_ADDR6 0x1324 > +#define DMA3_ADDR7 0x1328 > +#define DMA3_ADDR8 0x132c > +#define DMA3_ACT_CTRL 0x1330 > +#define DMA3_ACT_FMT 0x1334 > +#define DMA3_ACT_SKIP 0x1338 > +#define DMA3_BYTE_CNT 0x1340 > +#define DMA_CMN_CTRL 0x1400 > +#define DMA_ERR_CODE 0x1404 > +#define DMA_CLK_CTRL 0x1408 > +#define DMA_AWUSER 0x140c > +#define DBG_AXIM_INFO 0x1440 > +#define DBG_TRXFIFO_INFO 0x1444 > +#define DBG_DMAFIFO_INFO 0x1448 > + > +/* > + * Register bit mask and set values > + * Mask is defined for each register field from most to lower significant bits > + * Register field set values are expressed in hex values > + */ > +/* CSIS_VERSION */ > +#define CSIS_VERSION_MASK GENMASK(31, 0) > + > +/* FSD CSI controller version 4.3 */ > +#define FSD_CSIS_VERSION_4_3 (0x04030002) > + > +/* CSIS_CMN_CTRL (CSIS Common Control) */ > +#define UPDATE_SHADOW_CH_MASK(ch) BIT_MASK(16 + (ch)) > +#define DESKEW_LEVEL_MASK GENMASK(15, 13) > +#define DESKEW_ENABLE_MASK BIT_MASK(12) > +#define INTERLEAVE_MODE_MASK GENMASK(11, 10) > +#define LANE_NUMBER_MASK GENMASK(9, 8) > +#define UPDATE_SHADOW_CTRL_MASK BIT_MASK(2) > +#define SW_RESET_MASK BIT_MASK(1) > +#define CSI_EN_MASK BIT_MASK(0) > + > +#define UPDATE_SHADOW 0x1 > +#define DESKEW_LEVEL 0x2 > +#define DESKEW_ENABLE 0x1 > +#define UPDATE_SHADOW_CTRL 0x1 > +#define SW_RESET 0x1 > +#define CSI_EN 0x1U > + > +/* CSIS_CLK_CTRL (CSIS Clock Control) */ > +#define CLKGATE_TRAIL_MASK(ch) GENMASK(19 + 4 * (ch), 16 + 4 * (ch)) > +#define CLKGATE_EN_MASK(ch) BIT_MASK(4 + (ch)) > + > +#define CLKGATE_EN 0x1 > + > +/* CSIS_INT_MSK0 (Interrupt Mask register 0) */ > +#define FRAMESTART_MASK GENMASK(27, 24) > +#define FRAMEEND_MASK GENMASK(23, 20) > +#define ERR_SOT_HS_MASK GENMASK(19, 16) > +#define ERR_LOST_FS_MASK GENMASK(15, 12) > +#define ERR_LOST_FE_MASK GENMASK(11, 8) > +#define ERR_OVER_MASK BIT_MASK(4) > +#define ERR_WRONG_CFG_MASK BIT_MASK(3) > +#define ERR_ECC_MASK BIT_MASK(2) > +#define ERR_CRC_MASK BIT_MASK(1) > +#define ERR_ID_MASK BIT_MASK(0) > +#define CSIS_INT_MSK0_ALL_MASK GENMASK(27, 0) > + > +#define FRAMESTART_CH_MASK(ch) BIT_MASK((ch) + 24) > +#define FRAMEEND_CH_MASK(ch) BIT_MASK((ch) + 20) > +#define ERR_SOT_HS_CH_MASK(ch) BIT_MASK((ch) + 16) > +#define ERR_LOST_FS_CH_MASK(ch) BIT_MASK((ch) + 12) > +#define ERR_LOST_FE_CH_MASK(ch) BIT_MASK((ch) + 8) > + > +#define FRAMESTART_ENABLE 0x1 > +#define FRAMEEND_ENABLE 0x1 > +#define ERR_SOT_HS_ENABLE 0x1 > +#define ERR_LOST_FS_ENABLE 0x1 > +#define ERR_LOST_FE_ENABLE 0x1 > + > +/* > + * Writing 1 will enable interrupt (Unmask) > + * Writing 0 will disable interrupt (mask) > + */ > +#define CSIS_INT_MSK0_ENABLE_ALL (~0) > +#define CSIS_INT_MSK0_MASK_ALL (0) > + > +/* CSIS_INT_SRC0 (Interrupt Source register 0) */ > +#define CSIS_INT_SRC0_ERR_ALL_MASK (GENMASK(19, 8) | GENMASK(4, 0)) > + > +/* > + * CSIS_INT_SRC1 (Interrupt Source register 1) > + * CSIS_INT_MSK1 (Interrupt Mask register 1) > + */ > +#define DMA_OTF_OVERLAP_MASK GENMASK(17, 14) > +#define DMA_ABORT_DONE_MASK BIT_MASK(13) > +#define DMA_ERROR_MASK BIT_MASK(12) > +#define DMA_FRM_END_MASK GENMASK(11, 8) > +#define DMA_FRM_START_MASK GENMASK(7, 4) > +#define LINE_END_MASK GENMASK(3, 0) > + > +#define DMA_OTF_OVERLAP_CH_MASK(ch) BIT_MASK((ch) + 14) > +#define DMA_FRM_END_CH_MASK(ch) BIT_MASK((ch) + 8) > +#define DMA_FRM_START_CH_MASK(ch) BIT_MASK((ch) + 4) > +#define LINE_END_CH_MASK(ch) BIT_MASK(ch) > + > +#define DMA_ABORT_ENABLE 0x1 > +#define DMA_ERROR_ENABLE 0x1 > +#define DMA_OTF_OVERLAP_ENABLE 0x1 > +#define DMA_FRM_END_ENABLE 0x1 > +#define DMA_FRM_START_ENABLE 0x1 > +#define LINE_END_CH_ENABLE 0x1 > + > +#define CSIS_INT_SRC1_ERR_ALL_MASK GENMASK(17, 12) > + > +/* > + * Writing 1 will enable interrupt (Unmask) > + * Writing 0 will disable interrupt (mask) > + */ > +#define CSIS_INT_MASK_ENABLE 0x1 > +#define CSIS_INT_MSK1_ENABLE_ALL (~0) > +#define CSIS_INT_MSK1_MASK_ALL (0) > + > +/* PHY_STATUS */ > +#define PHY_STATUS_ULPSDAT_MASK GENMASK(11, 8) > +#define PHY_STATUS_STOPSTATEDAT_MASK GENMASK(7, 4) > +#define PHY_STATUS_ULPSCLK_MASK BIT_MASK(1) > +#define PHY_STATUS_STOPSTATECLK_MASK BIT_MASK(0) > + > +/* PHY_CMN_CTRL (PHY common control) */ > +#define HSSETTLE_MASK GENMASK(31, 24) > +#define S_CLKSETTLE_MASK GENMASK(23, 22) > +#define S_BYTE_CLK_ENABLE_MASK BIT_MASK(21) > +#define S_DPDN_SWAP_CLK_MASK BIT_MASK(6) > +#define S_DPDN_SWAP_DAT_MASK BIT_MASK(5) > +#define ENABLE_DAT_MASK GENMASK(4, 1) > +#define ENABLE_CLK_MASK BIT_MASK(0) > + > +/* PHY BCTRL_L */ > +#define PHY_BCTRL_L_BPHYCTRL_MASK GENMASK(31, 0) > + > +/* PHY BCTRL_H */ > +#define PHY_BCTRL_H_BPHYCTRL_MASK GENMASK(31, 0) > + > +/* PHY SCTRL_L */ > +#define PHY_SCTRL_L_SPHYCTRL_MASK GENMASK(31, 0) > + > +/* PHY SCTRL_H */ > +#define SKEW_CAL_MAX_SKEW_CODE_CTRL_MASK GENMASK(7, 2) > +#define SKEW_CAL_EN_MASK BIT_MASK(1) > + > +#define SKEW_CAL_MAX_SKEW_CODE_CTRL 0x24 > +#define SKEW_CAL_EN 0x1 > + > +/* > + * ISP_CONFIG_CH0~3 (ISP configuration register CH0~3) > + * SDW_CONFIG_CH0~3 (Shadow configuration register of CH0~3) > + */ > +#define PIXEL_MODE_MASK GENMASK(13, 12) > +#define PARALLEL_MODE_MASK BIT_MASK(11) > +#define RGB_SWAP_MASK BIT_MASK(10) > +#define DATAFORMAT_MASK GENMASK(7, 2) > +#define VIRTUAL_CHANNEL_MASK GENMASK(1, 0) > + > +#define ISP_CONFIG_CH_OFFSET 0x10 > + > +/* > + * ISP_RESOL_CH0~3 (ISP Resolution register CH0~3) > + * SDW_RESOL_CH0~3 (Shadow resolution register of CH0~3) > + */ > +#define VRESOL_MASK GENMASK(31, 16) > +#define HRESOL_MASK GENMASK(15, 0) > + > +/* > + * ISP_SYNC_CH0!3 (ISP Sync register CH0~3) > + * SDW_SYNC_CH0~31 Shadow Sync register CH0~3 > + */ > +#define HSYNC_LINTV_MASK GENMASK(23, 18) > + > +/* FRM_CNT_CH0~3 (Frame counter of CH0~3) */ > +#define FRM_CNT_CH_MASK GENMASK(31, 0) > +#define FRM_CNT_CH_OFFSET 0x4 > + > +/* LINE_INTR_CH0~3 (Line interrupt configuration CH0~3) */ > +#define LINE_INTR_CH_MASK GENMASK(31, 0) > +#define LINE_INTR_CH_MUL 0x4 > + > +/* VC_PASSING (VC Passing configuration) */ > +#define VC_PASSING_MASK GENMASK(9, 8) > +#define VC_PASSING_ENABLE_MASK BIT_MASK(7) > +#define VC_PASSING_ENABLE 0x1 > + > +#define DMA_ADDR_OFFSET 0x100 > + > +/* DMA_CTRL (DMA0~3 Control) */ > +#define DMA_UPDT_SKIPPTR_MASK GENMASK(7, 5) > +#define DMA_UPDT_FRAMEPTR_MASK GENMASK(4, 2) > +#define DMA_UPDT_PTR_EN_MASK BIT_MASK(1) > +#define DMA_DISABLE_MASK BIT_MASK(0) > + > +#define DMA_DISABLE 0x1 > + > +/* DMA_FMT (DMA0~3 Output Format) */ > +#define DMA_PACK_MASK GENMASK(17, 16) > +#define DMA_DIM_MASK BIT_MASK(15) > +#define DMA_DUMP_MASK BIT_MASK(13) > +#define DMA_BYTESWAP_MASK BIT_MASK(12) > + > +enum FSD_CSIS_DMA_PACK { > + DMA_PACK_NORMAL, > + DMA_PACK_10, > + DMA_PACK_12, > + DMA_PACK_14, > + DMA_PACK_18, > + DMA_PACK_20, > +}; > + > +#define DMA_DIM_1D 0x1 > +#define DMA_DIM_2D 0x0 > +#define DMA_DUMP_OTF 0x1 > +#define DMA_DUMP_NORMAL 0x0 > +#define DMA_BYTESWAP_REVERSE 0x1 > +#define DMA_BYTESWAP_REGULAR 0x0 > + > +/* DMA_SKIP (DMA0~3 skip) */ > +#define DMA_SKIP_EN_MASK BIT_MASK(31) > +#define DMA_SKIP_TURNPTR_MASK GENMASK(18, 16) > +#define DMA_SKIP_SEQ_MASK GENMASK(7, 0) > + > +#define DMA_SKIP_ENABLE 0x1 > + > +/* DMA_ADDR (DMA0~3 Address) */ > +#define DMA_ADDR1_MASK GENMASK(31, 0) > + > +/* DMA_ACT_CTRL (DMA_0_3 ACT control) */ > +#define ACTIVE_DMA_ABORTED_MASK BIT_MASK(8) > +#define ACTIVE_DMA_SKIPPTR_MASK GENMASK(7, 5) > +#define ACTIVE_DMA_FRAMEPTR_MASK GENMASK(4, 2) > +#define ACTIVE_DMA_DISABLE_MASK BIT_MASK(0) > + > +/* DMA_ACT_FMT (DMA0~3 ACT format) */ > +#define ACTIVE_DMA_PACK_MASK GENMASK(17, 16) > +#define ACTIVE_DMA_DIM_MASK BIT_MASK(15) > +#define ACTIVE_DMA_DUMP_MASK BIT_MASK(13) > +#define ACTIVE_DMA_BYTESWAP_MASK BIT_MASK(12) > + > +/* DMA_ACT_SKIP (DMA0~3 ACT skip) */ > +#define ACTIVE_DMA_SKIP_EN_MASK BIT_MASK(31) > +#define ACTIVE_DMA_SKIP_TURNPTR_MASK GENMASK(18, 16) > +#define ACTIVE_DMA_SKIP_SEQ_MASK GENMASK(7, 0) > + > +/* DMA_FRM_BYTE_CNT (DMA0~3 Frame byte count) */ > +#define DMA_FRM_BYTE_CNT_MASK GENMASK(31, 0) > + > +/* DMA_CMN_CTRL (DMA Common control) */ > +#define DMA_ABORT_REQ_MASK BIT_MASK(0) > +#define DMA_FRM_LOCK_EN 1 > + > +/* DMA_ERR_CODE (DMA Error code) */ > +#define DMAFIFO_FULL_MASK BIT_MASK(5) > +#define TRXFIFO_FULL_MASK BIT_MASK(4) > +#define BRESP_ERROR_CH_MASK(ch) BIT_MASK(ch) > + > +/* DMA_CLK_CTRL (DMA Clock control) */ > +#define DMA_CLK_GATE_TRAIL_MASK GENMASK(4, 1) > +#define DMA_CLK_GATE_EN_MASK BIT_MASK(0) > + > +#define DMA_CLK_GATE_ENABLE 0x1 > + > +/* DMA_AWUSER (DMA AWUSER) */ > +#define DMA_AWUSER_MASK GENMASK(3, 0) > + > +/* DBG_AXIM_INFO (Debug AXIM Info) */ > +#define DBG_AXIM_WCNT_MASK GENMASK(11, 7) > +#define DBG_AXIM_AWCNT_MASK GENMASK(6, 2) > +#define DBG_AXIM_STATE_MASK GENMASK(1, 0) > + > +/* DBG_TRXFIFO_INFO (Debug TRXFIFO Info) */ > +#define TRXFIFO_MAX_WCNT_MASK GENMASK(31, 16) > +#define TRXFIFO_CUR_WCNT_MASK GENMASK(15, 0) > + > +/* DBG_DMAFIFO_INFO (Debug DMA FIFO Info) */ > +#define DMAFIFO_MAX_WCNT_MASK GENMASK(31, 16) > +#define DMAFIFO_CUR_WCNT_MASK GENMASK(15, 0) > + > +#define DMA_CLK_GATE_ENABLE 0x1 > + > +#define FSD_CSIS_NB_CSI_PER_PHY 4 > +#define FSD_CSIS_MAX_VC 4 > +#define FSD_CSIS_NB_CLOCK 1 > +#define FSD_CSIS_DMA_COHERENT_MASK_SIZE 32 > + > +#define FSD_CSIS_WMIN 48 > +#define FSD_CSIS_WMAX 1920 > +#define FSD_CSIS_HMIN 32 > +#define FSD_CSIS_HMAX 1200 > +#define FSD_CSIS_WALIGN 2 > +#define FSD_CSIS_HALIGN 0 > +#define FSD_CSIS_SALIGN 0 > + > +#define FSD_CSIS_NB_INPUT 1 > +#define FSD_CSIS_NB_MIN_CH 1 > +#define FSD_CSIS_NB_DMA_OUT_CH 8 > + > +/* There are ACLK, PCLK clocks for each CSI block */ > +#define MAX_FSD_CSIS_CLOKCS 2 > + > +#define DPHYON_DATA3 BIT(DATALANE3) > +#define DPHYON_DATA2 BIT(DATALANE2) > +#define DPHYON_DATA1 BIT(DATALANE1) > +#define DPHYON_DATA0 BIT(DATALANE0) > + > +/* PHY Common control registers */ > +#define S_BYTE_CLK_ENABLE 0x1 > +#define S_DPDN_SWAP_CLK_ENABLE 0x1 > +#define S_DPDN_SWAP_DAT_ENABLE 0x1 > +#define ENABLE_DAT(nb) ((1 << (nb)) - 1) > +#define ENABLE_CLK 0x1 > + > +/* > + * DMA Channel registers > + */ > +#define DMA_CH_OFFSET 0x100 > +#define DMA_FRAME_ADDR_OFFSET 0x4 > + > +/* > + * Frame Counter registers > + */ > +#define FRM_CNT_CH_OFFSET 0x4 > + > +/* > + * ISP configuration related registers > + */ > +#define ISP_CH_OFFSET 0x10 > + > +#define ISP_PIXEL_MODE_SINGLE 0x0 > +#define ISP_PIXEL_MODE_DUAL 0x1 > +#define ISP_PIXEL_MODE_QUAD 0x0 > +#define ISP_PIXEL_MODE_OCTA 0x3 > +#define ISP_CONFIG_RGB_SWAP 0x1 > +#define ISP_DATA_FORMAT_YUV420_8 0x18 > +#define ISP_DATA_FORMAT_YUV420_10 0x19 > +#define ISP_DATA_FORMAT_YUV420_8_LEGACY 0x1A > +#define ISP_DATA_FORMAT_YUV420_8_CSPS 0x1C > +#define ISP_DATA_FORMAT_YUV420_10_CSPS 0x1D > +#define ISP_DATA_FORMAT_YUV422_8 0x1E > +#define ISP_DATA_FORMAT_YUV422_10 0x1F > +#define ISP_DATA_FORMAT_RGB565 0x22 > +#define ISP_DATA_FORMAT_RGB666 0x23 > +#define ISP_DATA_FORMAT_RGB888 0x24 > +#define ISP_DATA_FORMAT_RAW6 0x28 > +#define ISP_DATA_FORMAT_RAW7 0x29 > +#define ISP_DATA_FORMAT_RAW8 0x2A > +#define ISP_DATA_FORMAT_RAW10 0x2B > +#define ISP_DATA_FORMAT_RAW12 0x2C > +#define ISP_DATA_FORMAT_RAW14 0x2D > +#define ISP_DATA_FORMAT_RAW16 0x2E > +#define ISP_DATA_FORMAT_RAW20 0x2F > +#define ISP_DATA_FORMAT_USER_DEFINED_1 0x30 > +#define ISP_DATA_FORMAT_USER_DEFINED_2 0x31 > +#define ISP_DATA_FORMAT_USER_DEFINED_3 0x32 > +#define ISP_DATA_FORMAT_USER_DEFINED_4 0x33 > +#define ISP_DATA_FORMAT_USER_DEFINED_5 0x34 > +#define ISP_DATA_FORMAT_USER_DEFINED_6 0x35 > +#define ISP_DATA_FORMAT_USER_DEFINED_7 0x36 > +#define ISP_DATA_FORMAT_USER_DEFINED_8 0x37 > + > +/* > + * fsd_csis_fmt - structure holding the formats supported in CSI instance > + * @name: string indicating name of format > + * @fourcc: fourcc value of this format > + * @colorspace: v4l2 colorspace for this format > + * @code: media bus code for this format > + * @depth: bits per pixel used for thsi format > + */ > +struct fsd_csis_fmt { > + char name[32]; > + u32 fourcc; > + u32 colorspace; > + u32 code; > + u32 depth; > +}; > + > +#define FSD_CSIS_MAX_FORMATS 20 > + > +/* > + * fsd_csis_buffer - buffer for one video frame > + * @vb: video buffer information for v4l2 > + * @list: list of buffers to be used in VB2 operations > + * @fmt: image format being used for this buffer > + * @sequence: number indicating sequence in stream > + */ > +struct fsd_csis_buffer { > + /* common v4l buffer stuff -- must be first */ > + struct vb2_v4l2_buffer vb; > + struct list_head list; > + const struct fsd_csis_fmt *fmt; > + unsigned long sequence; > +}; > + > +/* > + * csis_dmaqueue - DMA buffer queue of avalailable buffers for streaming > + * @active: list of buffers avalailable for DMA > + */ > +struct fsd_csis_dmaqueue { > + struct list_head active; > +}; > + > +enum { > + DPHY_MODE, > + CPHY_MODE > +}; > + > +enum FSD_CSIS_DATA { > + DATALANE0 = 0, > + DATALANE1, > + DATALANE2, > + DATALANE3 > +}; > + > +enum FSD_CSIS_INTERLEAVE { > + VC0_ONLY = 0, > + DT_ONLY, > + VC_ONLY, > + VC_DT_BOTH > +}; > + > +enum FSD_CSIS_PIXEL_MODE { > + SINGLE_PIXEL_MODE, > + DUAL_PIXEL_MODE, > + QUAD_PIXEL_MODE, > + OCTA_PIXEL_MODE > +}; > + > +enum FSD_CSIS_PARALLEL_MODE { > + FSD_CSIS_PARALLEL_MODE_OFF, > + FSD_CSIS_PARALLEL_MODE_32_BIT, > + FSD_CSIS_PARALLEL_MODE_64_BIT, > + FSD_CSIS_PARALLEL_MODE_128_BIT > +}; > + > +/* > + * fsd_csis_dev - CSI device structure. One for each CSI instance > + * @device: pointer to core device structure provided by platform_device > + * @info: device specific information (e.g. version) > + * @ctx: CSIS context describing the individual stream and device properties. > + * There is one context per virtual channel > + * @clk: CSIS clocks that need to be set for streaming > + * @v4l2_dev: V4L2 device instance for this CSIS I/F > + * @ctrl_handler: Control handler to set Number of lanes, and lane configuration > + * @mutex_csis_dma_reg: synchronization lock to update DMA addresses > + * @id: this CSI device id > + * @nb_data_lane: number of CSI data lanes in use for this CSI instance > + * @nb_clocks: number of clocks to be prepared for CSI enable > + * @base: base address of this CSI instance SFR > + * @phy_base: base address of DC-PHY interface of this CSI instance > + * @lane_speed: data rate at which CSI Rx lane is operating (in Mbps for D-PHY, Msps for C-PHY) > + * @irq: interrupt number for this CSI instance > + * @ip_is_on: boolean value indicating CSI instance is turned on > + * @csis_sysreg_base: SYSREG_CSI base to set DC-PHY reset > + * @stream_enabled: indicates if streaming is in progress > + */ > +struct fsd_csis_dev { > + struct device *device; > + const struct fsd_csis_dev_info *info; > + struct fsd_csis_ctx *ctx[FSD_CSIS_MAX_VC]; > + struct clk *clk[FSD_CSIS_NB_CLOCK]; > + struct v4l2_device v4l2_dev; > + struct v4l2_ctrl_handler ctrl_handler; > + /* lock for adding VB2 buffers for DMA */ > + struct mutex mutex_csis_dma_reg; > + unsigned int id; > + unsigned int nb_data_lane; > + unsigned int nb_clocks; > + void __iomem *base; > + void __iomem *phy_base; This seems unused. > + struct regmap *sysreg_map; > + unsigned int lane_speed; > + int irq; > + bool ip_is_on; > + unsigned int stream_enabled; > +}; > + > +/* > + * fsd_csis_ctx - CSI context information for stream in use > + * @dev: pointer to parent device structure containing this context > + * @mutex: VB2 Queue lock > + * @mutex_buf: synchrnization lock used between VB2 buffer operations and the DMA queue > + * @end_irq_worker: flag to allow IRQ worker thread to process stream buffers > + * @input: input number to use VIDIOC_S_INPUT/VIDIOC_G_INPUT ioctls > + * @v4l2_dev: v4l2 device instance for this context > + * @sensor: Sub device to interface with sensor (1 for each CSIS I/F Channel) > + * @vdev: video device node representing this stream > + * @endpoint: fwnode graph endpoint for this CSI port > + * @fh: handle for v4l2 file operations > + * @timesperframe: minimum and maximum fps > + * @vb_vidq: vb2 queue for this context > + * @asd: Asynchronous sub device instances to bind > + * @notifier: Notifier to bind sub device nodes > + * @virtual_channel: CSI Virtual Channel ID in use > + * @fmt: image format in use for this context > + * @v_fmt: Used to store current pixel format > + * @m_fmt: Used to store current mbus frame format > + * @active_fmt: array of formats as supported by CSI and image sensor > + * @num_active_fmt: number of active formats as given in active_fmt > + * @vidq: video buffer queue being used by CSI DMA > + * @frame: array of CSI buffers > + * @frame_addr: array of DMA addresses of the CSI buffers > + * @num_reqbufs: number of buffers as requested by user > + * @prev_dma_ptr: previous DMA frame counter value > + * @current_dma_ptr: present DMA frame counter value > + * @number_of_ready_bufs: number of vb2 buffers available to be added to active list > + * @prev_frame_counter: previous CSI frame counter value > + * @current_frame_counter: current CSI frame counter value > + * @csis_ctx_work: bottom half work queue structure used between > + * CSI interrupt handler and streaming operations > + * @sequence: number indicating sequence in stream > + */ > +struct fsd_csis_ctx { > + struct fsd_csis_dev *dev; > + /* lock for vb2_queue buffers */ > + struct mutex mutex; > + /** > + * lock to synchronize buffer access between worker thread > + * and buffer add/delete operations > + */ > + struct mutex mutex_buf; > + atomic_t end_irq_worker; > + unsigned int input; > + struct v4l2_device *v4l2_dev; > + struct v4l2_subdev *sensor; > + struct video_device vdev; > + struct v4l2_fwnode_endpoint endpoint; > + struct v4l2_fh fh; > + struct v4l2_fract timesperframe; > + struct vb2_queue vb_vidq; > + struct v4l2_async_subdev asd; > + struct v4l2_async_notifier notifier; > + unsigned int virtual_channel; > + const struct fsd_csis_fmt *fmt; > + struct v4l2_format v_fmt; > + struct v4l2_mbus_framefmt m_fmt; > + const struct fsd_csis_fmt *active_fmt[FSD_CSIS_MAX_FORMATS]; > + unsigned int num_active_fmt; > + struct fsd_csis_dmaqueue vidq; > + struct fsd_csis_buffer *frame[FSD_CSIS_NB_DMA_OUT_CH]; > + u64 frame_addr[FSD_CSIS_NB_DMA_OUT_CH]; > + u8 prev_dma_ptr; > + u8 current_dma_ptr; > + u8 number_of_ready_bufs; > + u32 prev_frame_counter; > + u32 current_frame_counter; > + unsigned long sequence; > + u32 dma_error; > + struct work_struct csis_ctx_work; > +}; > + > +/* > + * fsd_csis_dev_info - CSIS device information > + * @version: FSD CSIS IP version > + * @nb_clocks: number of clocks needed for the driver > + * @clk_names: clock names > + */ > +struct fsd_csis_dev_info { > + unsigned int version; > + unsigned int nb_clocks; > + const char *clk_names[MAX_FSD_CSIS_CLOKCS]; > +}; > + > +static inline unsigned int get_bits(unsigned int val, unsigned int mask) > +{ > + u32 value = val; > + > + value &= mask; > + value >>= (ffs(mask) - 1); > + return value; > +} > + > +static inline unsigned int set_bits(unsigned int val, unsigned int mask) > +{ > + u32 value = val; > + > + value <<= (ffs(mask) - 1); > + value &= mask; > + return value; > +} > + > +#define reset_bits(mask) (~(mask)) > + > +static inline unsigned char fsd_csis_current_dma_ptr(struct fsd_csis_ctx *ctx) > +{ > + unsigned int dma_act_ctrl = 0; > + > + dma_act_ctrl = readl(ctx->dev->base + DMA0_ACT_CTRL + DMA_CH_OFFSET * ctx->virtual_channel); > + return get_bits(dma_act_ctrl, ACTIVE_DMA_FRAMEPTR_MASK); > +} > + > +static inline unsigned int fsd_csis_current_frame_counter(struct fsd_csis_ctx *ctx) > +{ > + return readl(ctx->dev->base + FRM_CNT_CH0 + FRM_CNT_CH_OFFSET * ctx->virtual_channel); > +} > + > +#define ctx_stream_enabled(ctx) ((ctx)->dev->stream_enabled & \ > + (1 << (ctx)->virtual_channel)) > + > +#define fsd_csis_dbg(level, dev, fmt, arg...) \ > + v4l2_dbg(level, debug, &(dev)->v4l2_dev, fmt, ##arg) > + > +#define fsd_csis_warn(dev, fmt, arg...) \ > + v4l2_warn(&(dev)->v4l2_dev, fmt, ##arg) > + > +#define fsd_csis_info(dev, fmt, arg...) \ > + v4l2_info(&(dev)->v4l2_dev, fmt, ##arg) > + > +#define fsd_csis_err(dev, fmt, arg...) \ > + v4l2_err(&(dev)->v4l2_dev, fmt, ##arg) > + > +#define fsd_csis_ctx_dbg(level, ctx, fmt, arg...) \ > + v4l2_dbg(level, debug, (ctx)->v4l2_dev, fmt, ##arg) > + > +#define fsd_csis_ctx_info(ctx, fmt, arg...) \ > + v4l2_info((ctx)->v4l2_dev, fmt, ##arg) > + > +#define fsd_csis_ctx_err(ctx, fmt, arg...) \ > + v4l2_err((ctx)->v4l2_dev, fmt, ##arg) Let's not reinvent the wheel, and use dev_dbg(), dev_info(), ... explicitly in the code instead. > + > +#define bytes_per_line(width, bpp) DIV_ROUND_UP((width) * (bpp), 8) > + Any reason for all these functions and macros to be in the header instead of the .c file ? > +#endif /* _FSD_CSIS_H */ > diff --git a/include/uapi/linux/fsd-csis.h b/include/uapi/linux/fsd-csis.h > new file mode 100644 > index 000000000000..ea90f805ad96 > --- /dev/null > +++ b/include/uapi/linux/fsd-csis.h > @@ -0,0 +1,19 @@ > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > +/* > + * FSD MIPI CSI2 Rx controller - User-space API > + */ > +#ifndef __LINUX_FSD_CSIS_H_ > +#define __LINUX_FSD_CSIS_H_ > + > +#include <linux/ioctl.h> > +#include <linux/types.h> > +#include <linux/v4l2-controls.h> > + > +/* > + * Custom controls > + * > + * V4L2_CID_USER_FSD_CSIS_NO_OF_LANE: Set number of D-PHY data lanes (1~4) > + */ > +#define V4L2_CID_USER_FSD_CSIS_NO_OF_LANE (V4L2_CID_USER_FSD_CSIS_BASE + 0) > + > +#endif /* __LINUX_FSD_CSIS_H_ */ > diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h > index b5e7d082b8ad..e9b1dc242cb1 100644 > --- a/include/uapi/linux/v4l2-controls.h > +++ b/include/uapi/linux/v4l2-controls.h > @@ -231,6 +231,11 @@ enum v4l2_colorfx { > */ > #define V4L2_CID_USER_DW100_BASE (V4L2_CID_USER_BASE + 0x1190) > > +/* The base for the fsd CSI driver controls. > + * We reserve 16 controls for this driver. > + */ > +#define V4L2_CID_USER_FSD_CSIS_BASE (V4L2_CID_USER_BASE + 0x10a0) > + > /* MPEG-class control IDs */ > /* The MPEG controls are applicable to all codec controls > * and the 'MPEG' part of the define is historical */ -- Regards, Laurent Pinchart