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. + + +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 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) + +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 +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. + +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; +} + +/* + * 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, + .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; + 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; + 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) + +#define bytes_per_line(width, bpp) DIV_ROUND_UP((width) * (bpp), 8) + +#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 */ -- 2.17.1