This is a V4L2 camera interface driver for i.MX5/6. See Documentation/video4linux/imx_camera.txt and device tree binding documentation at Documentation/devicetree/bindings/media/imx.txt. Signed-off-by: Steve Longerbeam <steve_longerbeam@xxxxxxxxxx> Signed-off-by: Dmitry Eremin-Solenikov <dmitry_eremin@xxxxxxxxxx> Signed-off-by: Jiada Wang <jiada_wang@xxxxxxxxxx> Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@xxxxxxxxxx> --- Documentation/devicetree/bindings/media/imx.txt | 426 ++++ Documentation/video4linux/imx_camera.txt | 243 ++ drivers/staging/media/Kconfig | 2 + drivers/staging/media/Makefile | 1 + drivers/staging/media/imx/Kconfig | 23 + drivers/staging/media/imx/Makefile | 1 + drivers/staging/media/imx/capture/Kconfig | 3 + drivers/staging/media/imx/capture/Makefile | 5 + drivers/staging/media/imx/capture/imx-camif.c | 2496 +++++++++++++++++++++ drivers/staging/media/imx/capture/imx-camif.h | 281 +++ drivers/staging/media/imx/capture/imx-csi.c | 195 ++ drivers/staging/media/imx/capture/imx-ic-prpenc.c | 660 ++++++ drivers/staging/media/imx/capture/imx-of.c | 354 +++ drivers/staging/media/imx/capture/imx-of.h | 18 + drivers/staging/media/imx/capture/imx-smfc.c | 505 +++++ drivers/staging/media/imx/capture/imx-vdic.c | 994 ++++++++ include/media/imx.h | 15 + include/uapi/Kbuild | 1 + include/uapi/linux/v4l2-controls.h | 4 + include/uapi/media/Kbuild | 2 + include/uapi/media/imx.h | 22 + 21 files changed, 6251 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/imx.txt create mode 100644 Documentation/video4linux/imx_camera.txt create mode 100644 drivers/staging/media/imx/Kconfig create mode 100644 drivers/staging/media/imx/Makefile create mode 100644 drivers/staging/media/imx/capture/Kconfig create mode 100644 drivers/staging/media/imx/capture/Makefile create mode 100644 drivers/staging/media/imx/capture/imx-camif.c create mode 100644 drivers/staging/media/imx/capture/imx-camif.h create mode 100644 drivers/staging/media/imx/capture/imx-csi.c create mode 100644 drivers/staging/media/imx/capture/imx-ic-prpenc.c create mode 100644 drivers/staging/media/imx/capture/imx-of.c create mode 100644 drivers/staging/media/imx/capture/imx-of.h create mode 100644 drivers/staging/media/imx/capture/imx-smfc.c create mode 100644 drivers/staging/media/imx/capture/imx-vdic.c create mode 100644 include/media/imx.h create mode 100644 include/uapi/media/Kbuild create mode 100644 include/uapi/media/imx.h diff --git a/Documentation/devicetree/bindings/media/imx.txt b/Documentation/devicetree/bindings/media/imx.txt new file mode 100644 index 0000000..1f2ca4b --- /dev/null +++ b/Documentation/devicetree/bindings/media/imx.txt @@ -0,0 +1,426 @@ +Freescale i.MX Video Capture + +Video Capture node +------------------ + +This is the imx video capture host interface node. The host node is an IPU +client and uses the register-level primitives of the IPU, so it does +not require reg or interrupt properties. Only a compatible property +and a list of IPU CSI port phandles is required. + +Required properties: +- compatible : "fsl,imx-video-capture"; +- ports : a list of CSI port phandles this device will control + +Optional properties: +- fim : child node that sets boot-time behavior of the + Frame Interval Monitor; + +fim child node +-------------- + +This is an optional child node of the video capture node. It can +be used to modify the default control values for the video capture +Frame Interval Monitor. Refer to Documentation/video4linux/imx_camera.txt +for more info on the Frame Interval Monitor. + +Optional properties: +- enable : enable (1) or disable (0) the FIM; +- num-avg : how many frame intervals the FIM will average; +- num-skip : how many frames the FIM will skip after a video + capture restart before beginning to sample frame + intervals; +- tolerance-range : a range of tolerances for the averaged frame + interval error, specified as <min max>, in usec. + The FIM will signal a frame interval error if + min < error < max. If the max is <= min, then + tolerance range is disabled (interval error if + error > min). +- input-capture-channel: an input capture channel and channel flags, + specified as <chan flags>. The channel number + must be 0 or 1. The flags can be + IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, or + IRQ_TYPE_EDGE_BOTH, and specify which input + capture signal edge will trigger the event. If + an input capture channel is specified, the FIM + will use this method to measure frame intervals + instead of via the EOF interrupt. The input capture + method is much preferred over EOF as it is not + subject to interrupt latency errors. However it + requires routing the VSYNC or FIELD output + signals of the camera sensor to one of the + i.MX input capture pads (SD1_DAT0, SD1_DAT1), + which also gives up support for SD1. + + +mipi_csi2 node +-------------- + +This is the device node for the MIPI CSI-2 Receiver, required for MIPI +CSI-2 sensors. + +Required properties: +- compatible : "fsl,imx-mipi-csi2"; +- reg : physical base address and length of the register set; +- clocks : the MIPI CSI-2 receiver requires three clocks: hsi_tx + (the DPHY clock), video_27m, and eim_sel; +- clock-names : must contain "dphy_clk", "cfg_clk", "pix_clk"; + +Optional properties: +- interrupts : must contain two level-triggered interrupts, + in order: 100 and 101; + + +Device tree nodes of the image sensors' controlled directly by the imx +camera host interface driver must be child nodes of their corresponding +I2C bus controller node. The data link of these image sensors must be +specified using the common video interfaces bindings, defined in +video-interfaces.txt. + +Video capture is supported with the following imx-based reference +platforms: + + +SabreLite with OV5642 +--------------------- + +The OV5642 module is connected to the parallel bus input on the internal +video mux to IPU1 CSI0. It's i2c bus connects to i2c bus 2, so the ov5642 +sensor node must be a child of i2c2. + +OV5642 Required properties: +- compatible : "ovti,ov5642"; +- clocks : the OV5642 system clock (cko2, 200); +- clock-names : must be "xclk"; +- reg : must be 0x3c; +- xclk : the system clock frequency, must be 24000000; +- reset-gpios : must be <&gpio1 8 0>; +- pwdn-gpios : must be <&gpio1 6 0>; + +OV5642 Endpoint Required properties: +- remote-endpoint : must connect to parallel sensor interface input endpoint + on ipu1_csi0 video mux (ipu1_csi0_mux_from_parallel_sensor). +- bus-width : must be 8; +- hsync-active : must be 1; +- vsync-active : must be 1; + +The following is an example devicetree video capture configuration for +SabreLite: + +/ { + ipucap0: ipucap@0 { + compatible = "fsl,imx-video-capture"; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ipu1_csi0>; + ports = <&ipu1_csi0>; + status = "okay"; + }; +}; + +&ipu1_csi0_from_ipu1_csi0_mux { + bus-width = <8>; + data-shift = <12>; /* Lines 19:12 used */ + hsync-active = <1>; + vync-active = <1>; +}; + +&ipu1_csi0_mux_from_parallel_sensor { + remote-endpoint = <&ov5642_to_ipu1_csi0_mux>; +}; + +&ipu1_csi0_mux { + status = "okay"; +}; + +&i2c2 { + camera: ov5642@3c { + compatible = "ovti,ov5642"; + clocks = <&clks 200>; + clock-names = "xclk"; + reg = <0x3c>; + xclk = <24000000>; + reset-gpios = <&gpio1 8 0>; + pwdn-gpios = <&gpio1 6 0>; + gp-gpios = <&gpio1 16 0>; + + port { + ov5642_to_ipu1_csi0_mux: endpoint { + remote-endpoint = <&ipu1_csi0_mux_from_parallel_sensor>; + bus-width = <8>; + hsync-active = <1>; + vsync-active = <1>; + }; + }; + }; +}; + + +SabreAuto with ADV7180 +---------------------- + +On the SabreAuto, an on-board ADV7180 SD decoder is connected to the +parallel bus input on the internal video mux to IPU1 CSI0. + +Two analog video inputs are routed to the ADV7180 on the SabreAuto, +composite on Ain1, and composite on Ain3. Those inputs are defined +via inputs and input-names properties under the ipu1_csi0_mux parallel +sensor input endpoint (ipu1_csi0_mux_from_parallel_sensor). + +Regulators and port expanders are required for the ADV7180 (power pin +is via port expander gpio on i2c3). The reset pin to the port expander +chip (MAX7310) is controlled by a gpio, so a reset-gpios property must +be defined under the port expander node to control it. + +The sabreauto uses a steering pin to select between the SDA signal on +i2c3 bus, and a data-in pin for an SPI NOR chip. i2cmux can be used to +control this steering pin. Idle state of the i2cmux selects SPI NOR. +This is not classic way to use i2cmux, since one side of the mux selects +something other than an i2c bus, but it works and is probably the cleanest +solution. Note that if one thread is attempting to access SPI NOR while +another thread is accessing i2c3, the SPI NOR access will fail since the +i2cmux has selected the SDA pin rather than SPI NOR data-in. This couldn't +be avoided in any case, the board is not designed to allow concurrent +i2c3 and SPI NOR functions (and the default device-tree does not enable +SPI NOR anyway). + +Endpoint ipu1_csi0_mux_from_parallel_sensor Optional Properties: +- inputs : list of input mux values, must be 0x00 followed by + 0x02 on SabreAuto; +- input-names : names of the inputs; + +ADV7180 Required properties: +- compatible : "adi,adv7180"; +- reg : must be 0x21; + +ADV7180 Optional properties: +- DOVDD-supply : DOVDD regulator supply; +- AVDD-supply : AVDD regulator supply; +- DVDD-supply : DVDD regulator supply; +- PVDD-supply : PVDD regulator supply; +- pwdn-gpio : gpio to control ADV7180 power pin, must be + <&port_exp_b 2 0> on SabreAuto; +- interrupts : interrupt from ADV7180, must be <27 0x8> on SabreAuto; +- interrupt-parent : must be <&gpio1> on SabreAuto; + +ADV7180 Endpoint Required properties: +- remote-endpoint : must connect to parallel sensor interface input endpoint + on ipu1_csi0 video mux (ipu1_csi0_mux_from_parallel_sensor). +- bus-width : must be 8; + + +The following is an example devicetree video capture configuration for +SabreAuto: + +/ { + i2cmux { + i2c@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + camera: adv7180@21 { + compatible = "adi,adv7180"; + reg = <0x21>; + pwdn-gpio = <&port_exp_b 2 0>; + interrupt-parent = <&gpio1>; + interrupts = <27 0x8>; + + port { + adv7180_to_ipu1_csi0_mux: endpoint { + remote-endpoint = <&ipu1_csi0_mux_from_parallel_sensor>; + bus-width = <8>; + }; + }; + }; + + port_exp_b: gpio_pca953x@32 { + compatible = "maxim,max7310"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x32>; + reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>; + }; + + }; + }; + + ipucap0: ipucap@0 { + compatible = "fsl,imx-video-capture"; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ipu1_csi0>; + ports = <&ipu1_csi0>; + status = "okay"; + + fim { + enable = <1>; + tolerance-range = <20 0>; + num-avg = <1>; + input-capture-channel = <0 IRQ_TYPE_EDGE_RISING>; + }; + }; +}; + +&ipu1_csi0_from_ipu1_csi0_mux { + bus-width = <8>; +}; + +&ipu1_csi0_mux_from_parallel_sensor { + remote-endpoint = <&adv7180_to_ipu1_csi0_mux>; + inputs = <0x00 0x02>; + input-names = "ADV7180 Composite on Ain1", "ADV7180 Composite on Ain3"; +}; + +&ipu1_csi0_mux { + status = "okay"; +}; + +/* input capture requires the input capture pin */ +&gpt { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_gpt_input_capture0>; +}; + +/* enabling input capture requires disabling SDHC1 */ +&usdhc1 { + status = "disabled"; +}; + + + +SabreSD Quad with OV5642 and MIPI CSI-2 OV5640 +---------------------------------------------- + +On the imx6q SabreSD, two camera sensors are supported: a parallel interface +OV5642 on IPU1 CSI0, and a MIPI CSI-2 OV5640 on IPU1 CSI1 on MIPI virtual +channel 1. The OV5642 connects to i2c bus 1 (i2c1) and the OV5640 to i2c +bus 2 (i2c2). + +The mipi_csi2 receiver node must be enabled and its input endpoint connected +via remote-endpoint to the OV5640 MIPI CSI-2 endpoint. + +OV5642 properties are as described above on SabreLite. + +OV5640 Required properties: +- compatible : "ovti,ov5640_mipi"; +- clocks : the OV5640 system clock (cko, 201); +- clock-names : must be "xclk"; +- reg : must be 0x3c; +- xclk : the system clock frequency, must be 24000000; +- reset-gpios : must be <&gpio1 20 1>; +- pwdn-gpios : must be <&gpio1 19 0>; + +OV5640 Optional properties: +- DOVDD-supply : DOVDD regulator supply; +- AVDD-supply : AVDD regulator supply; +- DVDD-supply : DVDD regulator supply; + +OV5640 MIPI CSI-2 Endpoint Required properties: +- remote-endpoint : must connect to mipi_csi receiver input endpoint + (mipi_csi_from_mipi_sensor). +- reg : must be 1; /* virtual channel 1 */ +- data-lanes : must be <0 1>; +- clock-lanes : must be <2>; + + +The following is an example devicetree video capture configuration for +SabreSD: + +/ { + ipucap0: ipucap@0 { + compatible = "fsl,imx-video-capture"; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ipu1_csi0>; + ports = <&ipu1_csi0>, <&ipu1_csi1>; + status = "okay"; + }; +}; + +&i2c1 { + camera: ov5642@3c { + compatible = "ovti,ov5642"; + clocks = <&clks 201>; + clock-names = "xclk"; + reg = <0x3c>; + xclk = <24000000>; + DOVDD-supply = <&vgen4_reg>; /* 1.8v */ + AVDD-supply = <&vgen5_reg>; /* 2.8v, rev C board is VGEN3 + rev B board is VGEN5 */ + DVDD-supply = <&vgen2_reg>; /* 1.5v*/ + pwdn-gpios = <&gpio1 16 GPIO_ACTIVE_LOW>; /* SD1_DAT0 */ + reset-gpios = <&gpio1 17 GPIO_ACTIVE_HIGH>; /* SD1_DAT1 */ + + port { + ov5642_to_ipu1_csi0_mux: endpoint { + remote-endpoint = <&ipu1_csi0_mux_from_parallel_sensor>; + bus-width = <8>; + hsync-active = <1>; + vsync-active = <1>; + }; + }; + }; +}; + +&i2c2 { + mipi_camera: ov5640@3c { + compatible = "ovti,ov5640_mipi"; + reg = <0x3c>; + clocks = <&clks 201>; + clock-names = "xclk"; + xclk = <24000000>; + DOVDD-supply = <&vgen4_reg>; /* 1.8v */ + AVDD-supply = <&vgen5_reg>; /* 2.8v, rev C board is VGEN3 + rev B board is VGEN5 */ + DVDD-supply = <&vgen2_reg>; /* 1.5v*/ + pwdn-gpios = <&gpio1 19 GPIO_ACTIVE_HIGH>; /* SD1_DAT2 */ + reset-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; /* SD1_CLK */ + + port { + #address-cells = <1>; + #size-cells = <0>; + + ov5640_to_mipi_csi: endpoint@1 { + reg = <1>; /* virtual channel 1 */ + remote-endpoint = <&mipi_csi_from_mipi_sensor>; + data-lanes = <0 1>; + clock-lanes = <2>; + }; + }; + }; +}; + +ipu1_csi0_from_ipu1_csi0_mux { + bus-width = <8>; + data-shift = <12>; /* Lines 19:12 used */ + hsync-active = <1>; + vsync-active = <1>; +}; + +&ipu1_csi0_mux_from_parallel_sensor { + remote-endpoint = <&ov5642_to_ipu1_csi0_mux>; +}; + +&ipu1_csi0_mux { + status = "okay"; +}; + +&mipi_csi { + status = "okay"; +}; + +/* Incoming port from sensor */ +&mipi_csi_from_mipi_sensor { + remote-endpoint = <&ov5640_to_mipi_csi>; + data-lanes = <0 1>; + clock-lanes = <2>; +}; + +&ipu1_csi1_from_mipi_vc1 { + data-lanes = <0 1>; + clock-lanes = <2>; +}; diff --git a/Documentation/video4linux/imx_camera.txt b/Documentation/video4linux/imx_camera.txt new file mode 100644 index 0000000..1d391c2 --- /dev/null +++ b/Documentation/video4linux/imx_camera.txt @@ -0,0 +1,243 @@ + i.MX Video Capture Driver + ========================== + +Introduction +------------ + +The Freescale i.MX5/6 contains an Image Processing Unit (IPU), which +handles the flow of image frames to and from capture devices and +display devices. + +For image capture, the IPU contains the following subunits: + +- Image DMA Controller (IDMAC) +- Camera Serial Interface (CSI) +- Image Converter (IC) +- Sensor Multi-FIFO Controller (SMFC) +- Image Rotator (IRT) +- Video De-Interlace Controller (VDIC) + +The IDMAC is the DMA controller for transfer of image frames to and from +memory. Various dedicated DMA channels exist for both video capture and +display paths. + +The CSI is the frontend capture unit that interfaces directly with +capture devices over Parallel, BT.656, and MIPI CSI-2 busses. + +The IC handles color-space conversion, resizing, and rotation +operations. + +The SMFC is used to send image frames directly to memory, bypassing the +IC. The SMFC is used when no color-space conversion or resizing is +required, i.e. the requested V4L2 formats and color-space are identical +to raw frames from the capture device. + +The IRT carries out 90 and 270 degree image rotation operations. + +Finally, the VDIC handles the conversion of interlaced video to +progressive, with support for different motion compensation modes (low +and high). + +For more info, refer to the latest versions of the i.MX5/6 reference +manuals listed under References. + + +Features +-------- + +Some of the features of this driver include: + +- Supports parallel, BT.565, and MIPI CSI-2 interfaces. + +- Multiple subdev sensors can be registered and controlled by a single + interface driver instance. Input enumeration will list every registered + sensor's inputs and input names, and setting an input will switch to + a different sensor if the input index is handled by a different sensor. + +- Simultaneous streaming from two separate sensors is possible with two + interface driver instances, each instance controlling a different + sensor. This is currently possible with the SabreSD reference board + with OV5642 and MIPI CSI-2 OV5640 sensors. + +- Scaling, color-space conversion, and image rotation. + +- Many pixel formats supported (RGB, packed and planar YUV, partial + planar YUV). + +- Full device-tree support using OF graph bindings. + +- Analog decoder input video source hot-swap support (during streaming) + via decoder status change subdev notification. + +- MMAP, USERPTR, and DMABUF importer/exporter buffers supported. + +- Motion compensated de-interlacing using the VDIC, with three + motion compensation modes: low, medium, and high motion. The mode is + specified with a custom control. + +- Includes a Frame Interval Monitor (FIM) that can correct vertical sync + problems with the ADV718x video decoders. See below for a description + of the FIM. + + +Usage Notes +----------- + +The i.MX capture driver is a standardized driver that supports the +following community V4L2 tools: + +- v4l2-ctl +- v4l2-cap +- v4l2src gstreamer plugin + + +The following platforms have been tested: + + +SabreLite with parallel-interface OV5642 +---------------------------------------- + +This platform requires the OmniVision OV5642 module with a parallel +camera interface from Boundary Devices for the SabreLite +(http://boundarydevices.com/products/nit6x_5mp/). + +There is a pin conflict between OV5642 and ethernet devices on this +platform, so by default video capture is disabled in the device tree. To +enable video capture, edit arch/arm/boot/dts/imx6qdl-sabrelite.dtsi and +uncomment the macro __OV5642_CAPTURE__. + + +SabreAuto with ADV7180 decoder +------------------------------ + +This platform accepts Composite Video analog inputs on Ain1 (connector +J42) and Ain3 (connector J43). + +To switch to Ain1: + +# v4l2-ctl -i0 + +To switch to Ain3: + +# v4l2-ctl -i1 + + +Frame Interval Monitor +---------------------- + +The adv718x decoders can occasionally send corrupt fields during +NTSC/PAL signal re-sync (too little or too many video lines). When +this happens, the IPU triggers a mechanism to re-establish vertical +sync by adding 1 dummy line every frame, which causes a rolling effect +from image to image, and can last a long time before a stable image is +recovered. Or sometimes the mechanism doesn't work at all, causing a +permanent split image (one frame contains lines from two consecutive +captured images). + +From experiment it was found that during image rolling, the frame +intervals (elapsed time between two EOF's) drop below the nominal +value for the current standard, by about one frame time (60 usec), +and remain at that value until rolling stops. + +While the reason for this observation isn't known (the IPU dummy +line mechanism should show an increase in the intervals by 1 line +time every frame, not a fixed value), we can use it to detect the +corrupt fields using a frame interval monitor. If the FIM detects a +bad frame interval, the camera interface driver restarts IPU capture +which corrects the rolling/split image. + +Custom controls exist to tweak some dials for FIM. If one of these +controls is changed during streaming, the FIM will be reset and will +continue at the new settings. + +- V4L2_CID_IMX_FIM_ENABLE + +Enable/disable the FIM. + +- V4L2_CID_IMX_FIM_NUM + +How many frame interval errors to average before comparing against the nominal +frame interval reported by the sensor. This can reduce noise from interrupt +latency. + +- V4L2_CID_IMX_FIM_TOLERANCE_MIN + +If the averaged intervals fall outside nominal by this amount, in +microseconds, streaming will be restarted. + +- V4L2_CID_IMX_FIM_TOLERANCE_MAX + +If any interval errors are higher than this value, those error samples +are discarded and do not enter into the average. This can be used to +discard really high interval errors that might be due to very high +system load, causing excessive interrupt latencies. + +- V4L2_CID_IMX_FIM_NUM_SKIP + +How many frames to skip after a FIM reset or stream restart before +FIM begins to average intervals. It has been found that there are +always a few bad frame intervals after stream restart, so this is +used to skip those frames to prevent endless restarts. + +Finally, all the defaults for these controls can be modified via a +device tree child node of the capture node, see +Documentation/devicetree/bindings/media/imx.txt. + + +SabreSD with MIPI CSI-2 OV5640 +------------------------------ + +The default device tree for SabreSD includes endpoints for both the +parallel OV5642 and the MIPI CSI-2 OV5640, but as of this writing only +the MIPI CSI-2 OV5640 has been tested. The OV5640 module connects to +MIPI connector J5 (sorry I don't have the compatible module part number +or URL). + +Inputs are registered for both the OV5642 and OV5640, and by default the +OV5642 is selected. To switch to the OV5640: + +# v4l2-ctl -i1 + + +Known Issues +------------ + +1. When using 90 or 270 degree rotation control at capture resolutions + near the IC resizer limit of 1024x1024, and combined with planar + pixel formats (YUV420, YUV422p), frame capture will often fail with + no end-of-frame interrupts from the IDMAC channel. To work around + this, use lower resolution and/or packed formats (YUYV, RGB3, etc.) + when 90 or 270 rotations are needed. + +2. Simple IDMAC interleaving using the ILO field in the IDMAC cpmem + doesn't work when combined with the 16-bit planar pixel formats + (YUV422P and NV16). This looks like a silicon bug, and there is + no satisfactory replies to queries about it from Freescale. So + the driver works around the issue by forcing the format to the + 12-bit planar versions (YUV420 and NV12) when simple interleaving + is used and the sensor sends interlaced fields (ADV718x). Another + option to workaround the issue is to use motion compensation when + combined with YUV422P or NV16. + +File list +--------- + +drivers/staging/media/imx/capture/ +include/media/imx.h +include/uapi/media/imx.h + +References +---------- + +[1] "i.MX 6Dual/6Quad Applications Processor Reference Manual" +[2] "i.MX 6Solo/6DualLite Applications Processor Reference Manual" + + +Authors +------- +Steve Longerbeam <steve_longerbeam@xxxxxxxxxx> +Dmitry Eremin-Solenikov <dmitry_eremin@xxxxxxxxxx> +Jiada Wang <jiada_wang@xxxxxxxxxx> +Vladimir Zapolskiy <vladimir_zapolskiy@xxxxxxxxxx> + +Copyright (C) 2012-2016 Mentor Graphics Inc. diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index ee91868..1ebcd1d 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -25,6 +25,8 @@ source "drivers/staging/media/cxd2099/Kconfig" source "drivers/staging/media/davinci_vpfe/Kconfig" +source "drivers/staging/media/imx/Kconfig" + source "drivers/staging/media/omap4iss/Kconfig" source "drivers/staging/media/tw686x-kh/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 8c05d0a..9f00aea 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_I2C_BCM2048) += bcm2048/ obj-$(CONFIG_DVB_CXD2099) += cxd2099/ +obj-$(CONFIG_VIDEO_IMX) += imx/ obj-$(CONFIG_LIRC_STAGING) += lirc/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig new file mode 100644 index 0000000..65e1645 --- /dev/null +++ b/drivers/staging/media/imx/Kconfig @@ -0,0 +1,23 @@ +config VIDEO_IMX + tristate "i.MX5/6 V4L2 devices" + depends on VIDEO_V4L2 && ARCH_MXC && IMX_IPUV3_CORE + default y + ---help--- + Say yes here to enable support for video4linux drivers for + the i.MX5/6 SOC. + +config VIDEO_IMX_CAMERA + tristate "i.MX5/6 Camera Interface driver" + depends on VIDEO_IMX && VIDEO_DEV && I2C + select VIDEOBUF2_DMA_CONTIG + default y + ---help--- + A video4linux capture driver for i.MX5/6 SOC. Some of the + features of this driver include MIPI CSI-2 sensor support, + hardware scaling, colorspace conversion, and rotation, + simultaneous capture from separate sensors, dmabuf + importer/exporter, and full devicetree support. + +if VIDEO_IMX_CAMERA +source "drivers/staging/media/imx/capture/Kconfig" +endif diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile new file mode 100644 index 0000000..7c97629 --- /dev/null +++ b/drivers/staging/media/imx/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VIDEO_IMX_CAMERA) += capture/ diff --git a/drivers/staging/media/imx/capture/Kconfig b/drivers/staging/media/imx/capture/Kconfig new file mode 100644 index 0000000..ee2cbab --- /dev/null +++ b/drivers/staging/media/imx/capture/Kconfig @@ -0,0 +1,3 @@ +menu "i.MX5/6 Camera Sub devices" + +endmenu diff --git a/drivers/staging/media/imx/capture/Makefile b/drivers/staging/media/imx/capture/Makefile new file mode 100644 index 0000000..5c965f9 --- /dev/null +++ b/drivers/staging/media/imx/capture/Makefile @@ -0,0 +1,5 @@ +imx-camera-objs := imx-camif.o imx-ic-prpenc.o imx-of.o \ + imx-smfc.o imx-vdic.o + +obj-$(CONFIG_VIDEO_IMX_CAMERA) += imx-camera.o +obj-$(CONFIG_VIDEO_IMX_CAMERA) += imx-csi.o diff --git a/drivers/staging/media/imx/capture/imx-camif.c b/drivers/staging/media/imx/capture/imx-camif.c new file mode 100644 index 0000000..0276426 --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-camif.c @@ -0,0 +1,2496 @@ +/* + * Video Camera Capture driver for Freescale i.MX5/6 SOC + * + * Copyright (c) 2012-2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pinctrl/consumer.h> +#include <linux/of_platform.h> +#include <linux/mxc_icap.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-of.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <video/imx-ipu-v3.h> +#include <media/imx.h> +#include "imx-camif.h" +#include "imx-of.h" + +/* + * Min/Max supported width and heights. + */ +#define MIN_W 176 +#define MIN_H 144 +#define MAX_W 8192 +#define MAX_H 4096 +#define MAX_W_IC 1024 +#define MAX_H_IC 1024 +#define MAX_W_VDIC 968 +#define MAX_H_VDIC 2048 + +#define H_ALIGN 3 /* multiple of 8 */ +#define S_ALIGN 1 /* multiple of 2 */ + +#define DEVICE_NAME "imx-camera" + +/* In bytes, per queue */ +#define VID_MEM_LIMIT SZ_64M + +static struct vb2_ops imxcam_qops; + +static inline struct imxcam_dev *sd2dev(struct v4l2_subdev *sd) +{ + return container_of(sd->v4l2_dev, struct imxcam_dev, v4l2_dev); +} + +static inline struct imxcam_dev *notifier2dev(struct v4l2_async_notifier *n) +{ + return container_of(n, struct imxcam_dev, subdev_notifier); +} + +static inline struct imxcam_dev *fim2dev(struct imxcam_fim *fim) +{ + return container_of(fim, struct imxcam_dev, fim); +} + +static inline struct imxcam_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct imxcam_ctx, fh); +} + +static inline bool is_io_ctx(struct imxcam_ctx *ctx) +{ + return ctx == ctx->dev->io_ctx; +} + +/* forward references */ +static void imxcam_bump_restart_timer(struct imxcam_ctx *ctx); + +/* Supported user and sensor pixel formats */ +static struct imxcam_pixfmt imxcam_pixformats[] = { + { + .name = "RGB565", + .fourcc = V4L2_PIX_FMT_RGB565, + .codes = {MEDIA_BUS_FMT_RGB565_2X8_LE}, + .bpp = 16, + }, { + .name = "RGB24", + .fourcc = V4L2_PIX_FMT_RGB24, + .codes = {MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_RGB888_2X12_LE}, + .bpp = 24, + }, { + .name = "BGR24", + .fourcc = V4L2_PIX_FMT_BGR24, + .bpp = 24, + }, { + .name = "RGB32", + .fourcc = V4L2_PIX_FMT_RGB32, + .codes = {MEDIA_BUS_FMT_ARGB8888_1X32}, + .bpp = 32, + }, { + .name = "BGR32", + .fourcc = V4L2_PIX_FMT_BGR32, + .bpp = 32, + }, { + .name = "4:2:2 packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .codes = {MEDIA_BUS_FMT_YUYV8_2X8, MEDIA_BUS_FMT_YUYV8_1X16}, + .bpp = 16, + }, { + .name = "4:2:2 packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .codes = {MEDIA_BUS_FMT_UYVY8_2X8, MEDIA_BUS_FMT_UYVY8_1X16}, + .bpp = 16, + }, { + .name = "4:2:0 planar, YUV", + .fourcc = V4L2_PIX_FMT_YUV420, + .bpp = 12, + .y_depth = 8, + }, { + .name = "4:2:0 planar, YVU", + .fourcc = V4L2_PIX_FMT_YVU420, + .bpp = 12, + .y_depth = 8, + }, { + .name = "4:2:2 planar, YUV", + .fourcc = V4L2_PIX_FMT_YUV422P, + .bpp = 16, + .y_depth = 8, + }, { + .name = "4:2:0 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV12, + .bpp = 12, + .y_depth = 8, + }, { + .name = "4:2:2 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV16, + .bpp = 16, + .y_depth = 8, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(imxcam_pixformats) + +static struct imxcam_pixfmt *imxcam_get_format(u32 fourcc, u32 code) +{ + struct imxcam_pixfmt *fmt, *ret = NULL; + int i, j; + + for (i = 0; i < NUM_FORMATS; i++) { + fmt = &imxcam_pixformats[i]; + + if (fourcc && fmt->fourcc == fourcc) { + ret = fmt; + goto out; + } + + for (j = 0; fmt->codes[j]; j++) { + if (fmt->codes[j] == code) { + ret = fmt; + goto out; + } + } + } +out: + return ret; +} + +/* Support functions */ + +/* find the sensor that is handling this input index */ +static struct imxcam_sensor * +find_sensor_by_input_index(struct imxcam_dev *dev, int input_idx) +{ + struct imxcam_sensor *sensor; + int i; + + for (i = 0; i < dev->num_sensors; i++) { + sensor = &dev->sensor_list[i]; + if (!sensor->sd) + continue; + + if (input_idx >= sensor->input.first && + input_idx <= sensor->input.last) + break; + } + + return (i < dev->num_sensors) ? sensor : NULL; +} + +/* + * Set all the video muxes required to receive data from the + * current sensor. + */ +static int imxcam_set_video_muxes(struct imxcam_dev *dev) +{ + struct imxcam_sensor *sensor = dev->sensor; + int i, ret; + + for (i = 0; i < IMXCAM_MAX_VIDEOMUX; i++) { + if (sensor->vidmux_input[i] < 0) + continue; + dev_dbg(dev->dev, "%s: vidmux %d, input %d\n", + sensor->sd->name, i, sensor->vidmux_input[i]); + ret = v4l2_subdev_call(dev->vidmux_list[i], video, s_routing, + sensor->vidmux_input[i], 0, 0); + if (ret) + return ret; + } + + return 0; +} + +/* + * Query sensor and update signal lock status. Returns true if lock + * status has changed. + */ +static bool update_signal_lock_status(struct imxcam_dev *dev) +{ + bool locked, changed; + u32 status; + int ret; + + ret = v4l2_subdev_call(dev->sensor->sd, video, g_input_status, &status); + if (ret) + return false; + + locked = ((status & (V4L2_IN_ST_NO_SIGNAL | V4L2_IN_ST_NO_SYNC)) == 0); + changed = (dev->signal_locked != locked); + dev->signal_locked = locked; + + return changed; +} + +/* + * Return true if the VDIC deinterlacer is needed. We need the VDIC + * if the sensor is transmitting fields, and userland is requesting + * motion compensation (rather than simple weaving). + */ +static bool need_vdic(struct imxcam_dev *dev, + struct v4l2_mbus_framefmt *sf) +{ + return dev->motion != MOTION_NONE && V4L2_FIELD_HAS_BOTH(sf->field); +} + +/* + * Return true if sensor format currently meets the VDIC + * restrictions: + * o the full-frame resolution to the VDIC must be at or below 968x2048. + * o the pixel format to the VDIC must be YUV422 + */ +static bool can_use_vdic(struct imxcam_dev *dev, + struct v4l2_mbus_framefmt *sf) +{ + return sf->width <= MAX_W_VDIC && + sf->height <= MAX_H_VDIC && + (sf->code == MEDIA_BUS_FMT_UYVY8_2X8 || + sf->code == MEDIA_BUS_FMT_UYVY8_1X16 || + sf->code == MEDIA_BUS_FMT_YUYV8_2X8 || + sf->code == MEDIA_BUS_FMT_YUYV8_1X16); +} + +/* + * Return true if the current capture parameters require the use of + * the Image Converter. We need the IC for scaling, colorspace conversion, + * and rotation. + */ +static bool need_ic(struct imxcam_dev *dev, + struct v4l2_mbus_framefmt *sf, + struct v4l2_format *uf, + struct v4l2_rect *crop) +{ + struct v4l2_pix_format *user_fmt = &uf->fmt.pix; + enum ipu_color_space sensor_cs, user_cs; + bool ret; + + sensor_cs = ipu_mbus_code_to_colorspace(sf->code); + user_cs = ipu_pixelformat_to_colorspace(user_fmt->pixelformat); + + ret = (user_fmt->width != crop->width || + user_fmt->height != crop->height || + user_cs != sensor_cs || + dev->rot_mode != IPU_ROTATE_NONE); + + return ret; +} + +/* + * Return true if user and sensor formats currently meet the IC + * restrictions: + * o the parallel CSI bus cannot be 16-bit wide. + * o the endpoint id of the CSI this sensor connects to must be 0 + * (for MIPI CSI2, the endpoint id is the virtual channel number, + * and only VC0 can pass through the IC). + * o the resizer output size must be at or below 1024x1024. + */ +static bool can_use_ic(struct imxcam_dev *dev, + struct v4l2_mbus_framefmt *sf, + struct v4l2_format *uf) +{ + struct imxcam_sensor *sensor = dev->sensor; + + return (sensor->ep.bus_type == V4L2_MBUS_CSI2 || + sensor->ep.bus.parallel.bus_width < 16) && + sensor->csi_ep.base.id == 0 && + uf->fmt.pix.width <= MAX_W_IC && + uf->fmt.pix.height <= MAX_H_IC; +} + +/* + * Adjusts passed width and height to meet IC resizer limits. + */ +static void adjust_to_resizer_limits(struct imxcam_dev *dev, + struct v4l2_format *uf, + struct v4l2_rect *crop) +{ + u32 *width, *height; + + if (uf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + width = &uf->fmt.pix.width; + height = &uf->fmt.pix.height; + } else { + width = &uf->fmt.win.w.width; + height = &uf->fmt.win.w.height; + } + + /* output of resizer can't be above 1024x1024 */ + *width = min_t(__u32, *width, MAX_W_IC); + *height = min_t(__u32, *height, MAX_H_IC); + + /* resizer cannot downsize more than 4:1 */ + if (ipu_rot_mode_is_irt(dev->rot_mode)) { + *height = max_t(__u32, *height, crop->width / 4); + *width = max_t(__u32, *width, crop->height / 4); + } else { + *width = max_t(__u32, *width, crop->width / 4); + *height = max_t(__u32, *height, crop->height / 4); + } +} + +static void adjust_user_fmt(struct imxcam_dev *dev, + struct v4l2_mbus_framefmt *sf, + struct v4l2_format *uf, + struct v4l2_rect *crop) +{ + struct imxcam_pixfmt *fmt; + + /* + * Make sure resolution is within IC resizer limits + * if we need the Image Converter. + */ + if (need_ic(dev, sf, uf, crop)) + adjust_to_resizer_limits(dev, uf, crop); + + /* + * Force the resolution to match crop window if + * we can't use the Image Converter. + */ + if (!can_use_ic(dev, sf, uf)) { + uf->fmt.pix.width = crop->width; + uf->fmt.pix.height = crop->height; + } + + fmt = imxcam_get_format(uf->fmt.pix.pixelformat, 0); + + uf->fmt.pix.bytesperline = (uf->fmt.pix.width * fmt->bpp) >> 3; + uf->fmt.pix.sizeimage = uf->fmt.pix.height * uf->fmt.pix.bytesperline; +} + +/* + * calculte the default active crop window, given a sensor frame and + * video standard. This crop window will be stored to dev->crop_defrect. + */ +static void calc_default_crop(struct imxcam_dev *dev, + struct v4l2_rect *rect, + struct v4l2_mbus_framefmt *sf, + v4l2_std_id std) +{ + rect->width = sf->width; + rect->height = sf->height; + rect->top = 0; + rect->left = 0; + + /* + * FIXME: For NTSC standards, top must be set to an + * offset of 13 lines to match fixed CCIR programming + * in the IPU. + */ + if (std != V4L2_STD_UNKNOWN && (std & V4L2_STD_525_60)) + rect->top = 13; + + /* adjust crop window to h/w alignment restrictions */ + rect->width &= ~0x7; +} + +static int update_sensor_std(struct imxcam_dev *dev) +{ + return v4l2_subdev_call(dev->sensor->sd, video, querystd, + &dev->current_std); +} + +static void update_fim(struct imxcam_dev *dev) +{ + struct imxcam_fim *fim = &dev->fim; + + if (dev->sensor_tpf.denominator == 0) { + fim->enabled = false; + return; + } + + fim->nominal = DIV_ROUND_CLOSEST( + 1000 * 1000 * dev->sensor_tpf.numerator, + dev->sensor_tpf.denominator); +} + +static int update_sensor_fmt(struct imxcam_dev *dev) +{ + struct v4l2_subdev_format fmt; + struct v4l2_streamparm parm; + struct v4l2_rect crop; + int ret; + + update_sensor_std(dev); + + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.pad = 0; + + ret = v4l2_subdev_call(dev->sensor->sd, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + dev->sensor_fmt = fmt.format; + + parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = v4l2_subdev_call(dev->sensor->sd, video, g_parm, &parm); + if (ret) + memset(&dev->sensor_tpf, 0, sizeof(dev->sensor_tpf)); + else + dev->sensor_tpf = parm.parm.capture.timeperframe; + update_fim(dev); + + ret = v4l2_subdev_call(dev->sensor->sd, video, g_mbus_config, + &dev->mbus_cfg); + if (ret) + return ret; + + dev->sensor_pixfmt = imxcam_get_format(0, dev->sensor_fmt.code); + + /* get new sensor default crop window */ + calc_default_crop(dev, &crop, &dev->sensor_fmt, dev->current_std); + + /* and update crop bounds */ + dev->crop_bounds.top = dev->crop_bounds.left = 0; + dev->crop_bounds.width = crop.width + (u32)crop.left; + dev->crop_bounds.height = crop.height + (u32)crop.top; + + /* + * reset the user crop window to defrect if defrect has changed, + * or if user crop is not initialized yet. + */ + if (dev->crop_defrect.width != crop.width || + dev->crop_defrect.left != crop.left || + dev->crop_defrect.height != crop.height || + dev->crop_defrect.top != crop.top || + !dev->crop.width || !dev->crop.height) { + dev->crop_defrect = crop; + dev->crop = dev->crop_defrect; + } + + return 0; +} + +/* + * Turn current sensor power on/off according to power_count. + */ +static int sensor_set_power(struct imxcam_dev *dev, int on) +{ + struct imxcam_sensor *sensor = dev->sensor; + struct v4l2_subdev *sd = sensor->sd; + int ret; + + if (on && sensor->power_count++ > 0) + return 0; + else if (!on && (sensor->power_count == 0 || + --sensor->power_count > 0)) + return 0; + + if (on) { + /* power-on the csi2 receiver */ + if (sensor->ep.bus_type == V4L2_MBUS_CSI2 && dev->csi2_sd) { + ret = v4l2_subdev_call(dev->csi2_sd, core, s_power, + true); + if (ret) + goto out; + } + + ret = v4l2_subdev_call(sd, core, s_power, true); + if (ret && ret != -ENOIOCTLCMD) + goto csi2_off; + } else { + v4l2_subdev_call(sd, core, s_power, false); + if (sensor->ep.bus_type == V4L2_MBUS_CSI2 && dev->csi2_sd) + v4l2_subdev_call(dev->csi2_sd, core, s_power, false); + } + + return 0; + +csi2_off: + if (sensor->ep.bus_type == V4L2_MBUS_CSI2 && dev->csi2_sd) + v4l2_subdev_call(dev->csi2_sd, core, s_power, false); +out: + sensor->power_count--; + return ret; +} + +static void reset_fim(struct imxcam_dev *dev, bool curval) +{ + struct imxcam_fim *fim = &dev->fim; + struct v4l2_ctrl *en = fim->ctrl[FIM_CL_ENABLE]; + struct v4l2_ctrl *num = fim->ctrl[FIM_CL_NUM]; + struct v4l2_ctrl *skip = fim->ctrl[FIM_CL_NUM_SKIP]; + struct v4l2_ctrl *tol_min = fim->ctrl[FIM_CL_TOLERANCE_MIN]; + struct v4l2_ctrl *tol_max = fim->ctrl[FIM_CL_TOLERANCE_MAX]; + unsigned long flags; + + spin_lock_irqsave(&dev->irqlock, flags); + + if (curval) { + fim->enabled = en->cur.val; + fim->num_avg = num->cur.val; + fim->num_skip = skip->cur.val; + fim->tolerance_min = tol_min->cur.val; + fim->tolerance_max = tol_max->cur.val; + } else { + fim->enabled = en->val; + fim->num_avg = num->val; + fim->num_skip = skip->val; + fim->tolerance_min = tol_min->val; + fim->tolerance_max = tol_max->val; + } + + /* disable tolerance range if max <= min */ + if (fim->tolerance_max <= fim->tolerance_min) + fim->tolerance_max = 0; + + fim->counter = -fim->num_skip; + fim->sum = 0; + + spin_unlock_irqrestore(&dev->irqlock, flags); +} + +/* + * Monitor an averaged frame interval. If the average deviates too much + * from the sensor's nominal frame rate, return -EIO. The frame intervals + * are averaged in order to quiet noise from (presumably random) interrupt + * latency. + */ +static int frame_interval_monitor(struct imxcam_fim *fim, struct timespec *ts) +{ + unsigned long interval, error, error_avg; + struct imxcam_dev *dev = fim2dev(fim); + struct timespec diff; + int ret = 0; + + if (++fim->counter <= 0) + goto out_update_ts; + + diff = timespec_sub(*ts, fim->last_ts); + interval = diff.tv_sec * 1000 * 1000 + diff.tv_nsec / 1000; + error = abs(interval - fim->nominal); + + if (fim->tolerance_max && error >= fim->tolerance_max) { + dev_dbg(dev->dev, + "FIM: %lu ignored, out of tolerance bounds\n", + error); + fim->counter--; + goto out_update_ts; + } + + fim->sum += error; + + if (fim->counter == fim->num_avg) { + error_avg = DIV_ROUND_CLOSEST(fim->sum, fim->num_avg); + + if (error_avg > fim->tolerance_min) + ret = -EIO; + + dev_dbg(dev->dev, "FIM: error: %lu usec%s\n", + error_avg, ret ? " (!!!)" : ""); + + fim->counter = 0; + fim->sum = 0; + } + +out_update_ts: + fim->last_ts = *ts; + return ret; +} + +/* + * Called by the encode and vdic subdevs in their EOF interrupt + * handlers with the irqlock held. This way of measuring frame + * intervals is subject to errors introduced by interrupt latency. + */ +static int fim_eof_handler(struct imxcam_dev *dev, struct timeval *now) +{ + struct imxcam_fim *fim = &dev->fim; + struct timespec ts; + + if (!fim->enabled) + return 0; + + ts.tv_sec = now->tv_sec; + ts.tv_nsec = now->tv_usec * 1000; + + return frame_interval_monitor(fim, &ts); +} + +/* + * Input Capture method of measuring frame intervals. Not subject + * to interrupt latency. + */ +static void fim_input_capture_handler(int channel, void *dev_id, + struct timespec *now) +{ + struct imxcam_fim *fim = dev_id; + struct imxcam_dev *dev = fim2dev(fim); + struct imxcam_ctx *ctx; + unsigned long flags; + + if (!fim->enabled) + return; + + if (!frame_interval_monitor(fim, now)) + return; + + spin_lock_irqsave(&dev->notify_lock, flags); + ctx = dev->io_ctx; + if (ctx && !ctx->stop && !atomic_read(&dev->pending_restart)) + imxcam_bump_restart_timer(ctx); + spin_unlock_irqrestore(&dev->notify_lock, flags); +} + +static int fim_request_input_capture(struct imxcam_dev *dev) +{ + struct imxcam_fim *fim = &dev->fim; + + if (fim->icap_channel < 0) + return 0; + + return mxc_request_input_capture(fim->icap_channel, + fim_input_capture_handler, + fim->icap_flags, fim); +} + +static void fim_free_input_capture(struct imxcam_dev *dev) +{ + struct imxcam_fim *fim = &dev->fim; + + if (fim->icap_channel < 0) + return; + + mxc_free_input_capture(fim->icap_channel, fim); +} + +/* + * Turn current sensor and CSI streaming on/off according to stream_count. + */ +static int sensor_set_stream(struct imxcam_dev *dev, int on) +{ + struct imxcam_sensor *sensor = dev->sensor; + int ret; + + if (on && sensor->stream_count++ > 0) + return 0; + else if (!on && (sensor->stream_count == 0 || + --sensor->stream_count > 0)) + return 0; + + if (on) { + ret = v4l2_subdev_call(sensor->sd, video, s_stream, true); + if (ret && ret != -ENOIOCTLCMD) + goto out; + + if (dev->sensor->ep.bus_type == V4L2_MBUS_CSI2 && dev->csi2_sd) { + ret = v4l2_subdev_call(dev->csi2_sd, video, s_stream, + true); + if (ret) + goto sensor_off; + } + + ret = v4l2_subdev_call(sensor->csi_sd, video, s_stream, true); + if (ret) + goto csi2_off; + + ret = fim_request_input_capture(dev); + if (ret) + goto csi_off; + } else { + fim_free_input_capture(dev); + v4l2_subdev_call(sensor->csi_sd, video, s_stream, false); + if (dev->sensor->ep.bus_type == V4L2_MBUS_CSI2 && dev->csi2_sd) + v4l2_subdev_call(dev->csi2_sd, video, s_stream, false); + v4l2_subdev_call(sensor->sd, video, s_stream, false); + } + + return 0; + +csi_off: + v4l2_subdev_call(sensor->csi_sd, video, s_stream, false); +csi2_off: + if (dev->sensor->ep.bus_type == V4L2_MBUS_CSI2 && dev->csi2_sd) + v4l2_subdev_call(dev->csi2_sd, video, s_stream, false); +sensor_off: + v4l2_subdev_call(sensor->sd, video, s_stream, false); +out: + sensor->stream_count--; + return ret; +} + +/* + * Start the encoder for buffer streaming. There must be at least two + * frames in the vb2 queue. + */ +static int start_encoder(struct imxcam_dev *dev) +{ + struct v4l2_subdev *streaming_sd; + int ret; + + if (dev->encoder_on) + return 0; + + if (dev->using_vdic) + streaming_sd = dev->vdic_sd; + else if (dev->using_ic) + streaming_sd = dev->prpenc_sd; + else + streaming_sd = dev->smfc_sd; + + ret = v4l2_subdev_call(streaming_sd, video, s_stream, 1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "encoder stream on failed\n"); + return ret; + } + + dev->encoder_on = true; + return 0; +} + +/* + * Stop the encoder. + */ +static int stop_encoder(struct imxcam_dev *dev) +{ + struct v4l2_subdev *streaming_sd; + int ret; + + if (!dev->encoder_on) + return 0; + + if (dev->using_vdic) + streaming_sd = dev->vdic_sd; + else if (dev->using_ic) + streaming_sd = dev->prpenc_sd; + else + streaming_sd = dev->smfc_sd; + + /* encoder/vdic off */ + ret = v4l2_subdev_call(streaming_sd, video, s_stream, 0); + if (ret) + v4l2_err(&dev->v4l2_dev, "encoder stream off failed\n"); + + dev->encoder_on = false; + return ret; +} + +/* + * Start/Stop streaming. + */ +static int set_stream(struct imxcam_ctx *ctx, bool on) +{ + struct imxcam_dev *dev = ctx->dev; + int ret = 0; + + if (on) { + if (atomic_read(&dev->status_change)) { + update_signal_lock_status(dev); + update_sensor_fmt(dev); + atomic_set(&dev->status_change, 0); + v4l2_info(&dev->v4l2_dev, "at stream on: %s, %s\n", + v4l2_norm_to_name(dev->current_std), + dev->signal_locked ? + "signal locked" : "no signal"); + } + + atomic_set(&dev->pending_restart, 0); + + dev->using_ic = + (need_ic(dev, &dev->sensor_fmt, &dev->user_fmt, + &dev->crop) && + can_use_ic(dev, &dev->sensor_fmt, &dev->user_fmt)); + + dev->using_vdic = need_vdic(dev, &dev->sensor_fmt) && + can_use_vdic(dev, &dev->sensor_fmt); + + reset_fim(dev, true); + + /* + * If there are two or more frames in the queue, we can start + * the encoder now. Otherwise the encoding will start once + * two frames have been queued. + */ + if (!list_empty(&ctx->ready_q) && + !list_is_singular(&ctx->ready_q)) + ret = start_encoder(dev); + } else { + ret = stop_encoder(dev); + } + + return ret; +} + +/* + * Restart work handler. This is called in three cases during active + * streaming. + * + * o NFB4EOF errors + * o A decoder's signal lock status or autodetected video standard changes + * o End-of-Frame timeouts + */ +static void restart_work_handler(struct work_struct *w) +{ + struct imxcam_ctx *ctx = container_of(w, struct imxcam_ctx, + restart_work); + struct imxcam_dev *dev = ctx->dev; + + mutex_lock(&dev->mutex); + + /* this can happen if we are releasing the io context */ + if (!is_io_ctx(ctx)) + goto out_unlock; + + if (!vb2_is_streaming(&dev->buffer_queue)) + goto out_unlock; + + if (!ctx->stop) { + v4l2_warn(&dev->v4l2_dev, "restarting\n"); + set_stream(ctx, false); + set_stream(ctx, true); + } + +out_unlock: + mutex_unlock(&dev->mutex); +} + +/* + * Stop work handler. Not currently needed but keep around. + */ +static void stop_work_handler(struct work_struct *w) +{ + struct imxcam_ctx *ctx = container_of(w, struct imxcam_ctx, + stop_work); + struct imxcam_dev *dev = ctx->dev; + + mutex_lock(&dev->mutex); + + if (vb2_is_streaming(&dev->buffer_queue)) { + v4l2_err(&dev->v4l2_dev, "stopping\n"); + vb2_streamoff(&dev->buffer_queue, V4L2_BUF_TYPE_VIDEO_CAPTURE); + } + + mutex_unlock(&dev->mutex); +} + +/* + * Restart timer function. Schedules a restart. + */ +static void imxcam_restart_timeout(unsigned long data) +{ + struct imxcam_ctx *ctx = (struct imxcam_ctx *)data; + + schedule_work(&ctx->restart_work); +} + +/* + * bump the restart timer and set the pending restart flag. + * notify_lock must be held when calling. + */ +static void imxcam_bump_restart_timer(struct imxcam_ctx *ctx) +{ + struct imxcam_dev *dev = ctx->dev; + + mod_timer(&ctx->restart_timer, jiffies + + msecs_to_jiffies(IMXCAM_RESTART_DELAY)); + atomic_set(&dev->pending_restart, 1); +} + +/* Controls */ +static int imxcam_set_rotation(struct imxcam_dev *dev, + int rotation, bool hflip, bool vflip) +{ + enum ipu_rotate_mode rot_mode; + int ret; + + ret = ipu_degrees_to_rot_mode(&rot_mode, rotation, + hflip, vflip); + if (ret) + return ret; + + if (rot_mode != dev->rot_mode) { + /* can't change rotation mid-streaming */ + if (vb2_is_streaming(&dev->buffer_queue)) { + v4l2_err(&dev->v4l2_dev, + "%s: not allowed while streaming\n", + __func__); + return -EBUSY; + } + + if (rot_mode != IPU_ROTATE_NONE && + !can_use_ic(dev, &dev->sensor_fmt, &dev->user_fmt)) { + v4l2_err(&dev->v4l2_dev, + "%s: current format does not allow rotation\n", + __func__); + return -EINVAL; + } + } + + dev->rot_mode = rot_mode; + dev->rotation = rotation; + dev->hflip = hflip; + dev->vflip = vflip; + + return 0; +} + +static int imxcam_set_motion(struct imxcam_dev *dev, + enum ipu_motion_sel motion) +{ + if (motion != dev->motion) { + /* can't change motion setting mid-streaming */ + if (vb2_is_streaming(&dev->buffer_queue)) { + v4l2_err(&dev->v4l2_dev, + "%s: not allowed while streaming\n", + __func__); + return -EBUSY; + } + + if (motion != MOTION_NONE && + !can_use_vdic(dev, &dev->sensor_fmt)) { + v4l2_err(&dev->v4l2_dev, + "sensor format does not allow deinterlace\n"); + return -EINVAL; + } + } + + dev->motion = motion; + return 0; +} + +static int imxcam_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imxcam_dev *dev = container_of(ctrl->handler, + struct imxcam_dev, ctrl_hdlr); + enum ipu_motion_sel motion; + bool hflip, vflip; + int rotation; + + rotation = dev->rotation; + hflip = dev->hflip; + vflip = dev->vflip; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + hflip = (ctrl->val == 1); + break; + case V4L2_CID_VFLIP: + vflip = (ctrl->val == 1); + break; + case V4L2_CID_ROTATE: + rotation = ctrl->val; + break; + case V4L2_CID_IMX_MOTION: + motion = ctrl->val; + return imxcam_set_motion(dev, motion); + case V4L2_CID_IMX_FIM_ENABLE: + reset_fim(dev, false); + return 0; + default: + v4l2_err(&dev->v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + + return imxcam_set_rotation(dev, rotation, hflip, vflip); +} + +static const struct v4l2_ctrl_ops imxcam_ctrl_ops = { + .s_ctrl = imxcam_s_ctrl, +}; + +static const struct v4l2_ctrl_config imxcam_std_ctrl[] = { + { + .id = V4L2_CID_HFLIP, + .name = "Horizontal Flip", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .def = 0, + .min = 0, + .max = 1, + .step = 1, + }, { + .id = V4L2_CID_VFLIP, + .name = "Vertical Flip", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .def = 0, + .min = 0, + .max = 1, + .step = 1, + }, { + .id = V4L2_CID_ROTATE, + .name = "Rotation", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = 0, + .min = 0, + .max = 270, + .step = 90, + }, +}; + +#define IMXCAM_NUM_STD_CONTROLS ARRAY_SIZE(imxcam_std_ctrl) + +static const struct v4l2_ctrl_config imxcam_custom_ctrl[] = { + { + .ops = &imxcam_ctrl_ops, + .id = V4L2_CID_IMX_MOTION, + .name = "Motion Compensation", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = MOTION_NONE, + .min = MOTION_NONE, + .max = HIGH_MOTION, + .step = 1, + }, +}; + +#define IMXCAM_NUM_CUSTOM_CONTROLS ARRAY_SIZE(imxcam_custom_ctrl) + +static const struct v4l2_ctrl_config imxcam_fim_ctrl[] = { + [FIM_CL_ENABLE] = { + .ops = &imxcam_ctrl_ops, + .id = V4L2_CID_IMX_FIM_ENABLE, + .name = "FIM Enable", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .def = FIM_CL_ENABLE_DEF, + .min = 0, + .max = 1, + .step = 1, + }, + [FIM_CL_NUM] = { + .ops = &imxcam_ctrl_ops, + .id = V4L2_CID_IMX_FIM_NUM, + .name = "FIM Num Average", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = FIM_CL_NUM_DEF, + .min = 1, /* no averaging */ + .max = 64, /* average 64 frames */ + .step = 1, + }, + [FIM_CL_TOLERANCE_MIN] = { + .ops = &imxcam_ctrl_ops, + .id = V4L2_CID_IMX_FIM_TOLERANCE_MIN, + .name = "FIM Tolerance Min", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = FIM_CL_TOLERANCE_MIN_DEF, + .min = 2, + .max = 200, + .step = 1, + }, + [FIM_CL_TOLERANCE_MAX] = { + .ops = &imxcam_ctrl_ops, + .id = V4L2_CID_IMX_FIM_TOLERANCE_MAX, + .name = "FIM Tolerance Max", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = FIM_CL_TOLERANCE_MAX_DEF, + .min = 0, + .max = 500, + .step = 1, + }, + [FIM_CL_NUM_SKIP] = { + .ops = &imxcam_ctrl_ops, + .id = V4L2_CID_IMX_FIM_NUM_SKIP, + .name = "FIM Num Skip", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = FIM_CL_NUM_SKIP_DEF, + .min = 1, /* skip 1 frame */ + .max = 256, /* skip 256 frames */ + .step = 1, + }, +}; + +/* + * the adv7182 has the most controls with 27, so add 32 + * on top of our own + */ +#define IMXCAM_NUM_CONTROLS (IMXCAM_NUM_STD_CONTROLS + \ + IMXCAM_NUM_CUSTOM_CONTROLS + \ + FIM_NUM_CONTROLS + 32) + +static int imxcam_init_controls(struct imxcam_dev *dev) +{ + struct v4l2_ctrl_handler *hdlr = &dev->ctrl_hdlr; + struct imxcam_fim *fim = &dev->fim; + const struct v4l2_ctrl_config *c; + struct v4l2_ctrl_config fim_c; + int i, ret; + + v4l2_ctrl_handler_init(hdlr, IMXCAM_NUM_CONTROLS); + + for (i = 0; i < IMXCAM_NUM_STD_CONTROLS; i++) { + c = &imxcam_std_ctrl[i]; + + v4l2_ctrl_new_std(hdlr, &imxcam_ctrl_ops, + c->id, c->min, c->max, c->step, c->def); + } + + for (i = 0; i < IMXCAM_NUM_CUSTOM_CONTROLS; i++) { + c = &imxcam_custom_ctrl[i]; + + v4l2_ctrl_new_custom(hdlr, c, NULL); + } + + for (i = 0; i < FIM_NUM_CONTROLS; i++) { + fim_c = imxcam_fim_ctrl[i]; + fim_c.def = fim->of_defaults[i]; + fim->ctrl[i] = v4l2_ctrl_new_custom(hdlr, &fim_c, NULL); + } + + if (hdlr->error) { + ret = hdlr->error; + v4l2_ctrl_handler_free(hdlr); + return ret; + } + + v4l2_ctrl_cluster(FIM_NUM_CONTROLS, fim->ctrl); + + dev->v4l2_dev.ctrl_handler = hdlr; + dev->vfd->ctrl_handler = hdlr; + + return 0; +} + +/* + * Video ioctls follow + */ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strncpy(cap->driver, DEVICE_NAME, sizeof(cap->driver) - 1); + strncpy(cap->card, DEVICE_NAME, sizeof(cap->card) - 1); + cap->bus_info[0] = 0; + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct imxcam_pixfmt *fmt; + + if (f->index >= NUM_FORMATS) + return -EINVAL; + + fmt = &imxcam_pixformats[f->index]; + strncpy(f->description, fmt->name, sizeof(f->description) - 1); + f->pixelformat = fmt->fourcc; + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + + f->fmt.pix = dev->user_fmt.fmt.pix; + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_format format; + struct imxcam_pixfmt *fmt; + unsigned int width_align; + struct v4l2_rect crop; + int ret; + + fmt = imxcam_get_format(f->fmt.pix.pixelformat, 0); + if (!fmt) { + v4l2_err(&dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + /* + * simple IDMAC interleaving using ILO field doesn't work + * when combined with the 16-bit planar formats (YUV422P + * and NV16). This looks like a silicon bug, no satisfactory + * replies to queries about it from Freescale. So workaround + * the issue by forcing the formats to the 12-bit planar versions. + */ + if (V4L2_FIELD_HAS_BOTH(dev->sensor_fmt.field) && + dev->motion == MOTION_NONE) { + switch (fmt->fourcc) { + case V4L2_PIX_FMT_YUV422P: + v4l2_info(&dev->v4l2_dev, + "ILO workaround: YUV422P forced to YUV420\n"); + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + break; + case V4L2_PIX_FMT_NV16: + v4l2_info(&dev->v4l2_dev, + "ILO workaround: NV16 forced to NV12\n"); + f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; + break; + default: + break; + } + fmt = imxcam_get_format(f->fmt.pix.pixelformat, 0); + } + + /* + * We have to adjust the width such that the physaddrs and U and + * U and V plane offsets are multiples of 8 bytes as required by + * the IPU DMA Controller. For the planar formats, this corresponds + * to a pixel alignment of 16. For all the packed formats, 8 is + * good enough. + * + * For height alignment, we have to ensure that the heights + * are multiples of 8 lines, to satisfy the requirement of the + * IRT (the IRT performs rotations on 8x8 blocks at a time). + */ + width_align = ipu_pixelformat_is_planar(fmt->fourcc) ? 4 : 3; + + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + width_align, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + + format.which = V4L2_SUBDEV_FORMAT_TRY; + format.pad = 0; + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, 0); + ret = v4l2_subdev_call(dev->sensor->sd, pad, set_fmt, &pad_cfg, &format); + if (ret) + return ret; + + fmt = imxcam_get_format(0, pad_cfg.try_fmt.code); + if (!fmt) { + v4l2_err(&dev->v4l2_dev, + "Sensor mbus format (0x%08x) invalid\n", + pad_cfg.try_fmt.code); + return -EINVAL; + } + + /* + * calculate what the optimal crop window will be for this + * sensor format and make any user format adjustments. + */ + calc_default_crop(dev, &crop, &pad_cfg.try_fmt, dev->current_std); + adjust_user_fmt(dev, &pad_cfg.try_fmt, f, &crop); + + /* this driver only delivers progressive frames to userland */ + f->fmt.pix.field = V4L2_FIELD_NONE; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct v4l2_subdev_format format; + int ret; + + if (vb2_is_busy(&dev->buffer_queue)) { + v4l2_err(&dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + format.pad = 0; + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, 0); + ret = v4l2_subdev_call(dev->sensor->sd, pad, set_fmt, NULL, &format); + if (ret) { + v4l2_err(&dev->v4l2_dev, "%s set_fmt failed\n", __func__); + return ret; + } + + ret = update_sensor_fmt(dev); + if (ret) + return ret; + + dev->user_fmt = *f; + dev->user_pixfmt = imxcam_get_format(f->fmt.pix.pixelformat, 0); + + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct imxcam_pixfmt *fmt; + struct v4l2_format uf; + + fmt = imxcam_get_format(fsize->pixel_format, 0); + if (!fmt) + return -EINVAL; + + if (fsize->index) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = MIN_W; + fsize->stepwise.step_width = + ipu_pixelformat_is_planar(fmt->fourcc) ? 16 : 8; + fsize->stepwise.min_height = MIN_H; + fsize->stepwise.step_height = 1 << H_ALIGN; + + uf = dev->user_fmt; + uf.fmt.pix.pixelformat = fmt->fourcc; + + if (need_ic(dev, &dev->sensor_fmt, &uf, &dev->crop)) { + fsize->stepwise.max_width = MAX_W_IC; + fsize->stepwise.max_height = MAX_H_IC; + } else { + fsize->stepwise.max_width = MAX_W; + fsize->stepwise.max_height = MAX_H; + } + + return 0; +} + +static int vidioc_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct imxcam_pixfmt *fmt; + struct v4l2_subdev_frame_interval_enum fie = { + .index = fival->index, + .pad = 0, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + fmt = imxcam_get_format(fival->pixel_format, 0); + if (!fmt) + return -EINVAL; + + fie.code = fmt->codes[0]; + + ret = v4l2_subdev_call(dev->sensor->sd, pad, enum_frame_interval, + NULL, &fie); + if (ret) + return ret; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + return 0; +} + +static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + int ret; + + ret = update_sensor_std(dev); + if (!ret) + *std = dev->current_std; + return ret; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + + *std = dev->current_std; + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + int ret; + + if (vb2_is_busy(&dev->buffer_queue)) + return -EBUSY; + + ret = v4l2_subdev_call(dev->sensor->sd, video, s_std, std); + if (ret < 0) + return ret; + + dev->current_std = std; + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct imxcam_sensor_input *sinput; + struct imxcam_sensor *sensor; + int sensor_input; + + /* find the sensor that is handling this input */ + sensor = find_sensor_by_input_index(dev, input->index); + if (!sensor) + return -EINVAL; + + sinput = &sensor->input; + sensor_input = input->index - sinput->first; + + input->type = V4L2_INPUT_TYPE_CAMERA; + input->capabilities = sinput->caps[sensor_input]; + strncpy(input->name, sinput->name[sensor_input], sizeof(input->name)); + + if (input->index == dev->current_input) { + v4l2_subdev_call(sensor->sd, video, g_input_status, &input->status); + update_sensor_std(dev); + input->std = dev->current_std; + } else { + input->status = V4L2_IN_ST_NO_SIGNAL; + input->std = V4L2_STD_UNKNOWN; + } + + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + + *index = dev->current_input; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int index) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct imxcam_sensor_input *sinput; + struct imxcam_sensor *sensor; + int ret, sensor_input, fim_actv; + + if (index == dev->current_input) + return 0; + + /* find the sensor that is handling this input */ + sensor = find_sensor_by_input_index(dev, index); + if (!sensor) + return -EINVAL; + + if (dev->sensor != sensor) { + /* + * don't allow switching sensors if there are queued buffers + * or there are other users of the current sensor besides us. + */ + if (vb2_is_busy(&dev->buffer_queue) || + dev->sensor->power_count > 1) + return -EBUSY; + + v4l2_info(&dev->v4l2_dev, "switching to sensor %s\n", + sensor->sd->name); + + /* power down current sensor before enabling new one */ + ret = sensor_set_power(dev, 0); + if (ret) + v4l2_warn(&dev->v4l2_dev, "sensor power off failed\n"); + + /* set new sensor and the video mux(es) in the pipeline to it */ + dev->sensor = sensor; + ret = imxcam_set_video_muxes(dev); + if (ret) + v4l2_warn(&dev->v4l2_dev, "set video muxes failed\n"); + + /* + * turn on FIM if ADV718x is selected else turn off FIM + * for other sensors. + */ + if (strncasecmp(sensor->sd->name, "adv718", 6) == 0) + fim_actv = 1; + else + fim_actv = 0; + v4l2_ctrl_s_ctrl(dev->fim.ctrl[FIM_CL_ENABLE], fim_actv); + + /* power-on the new sensor */ + ret = sensor_set_power(dev, 1); + if (ret) + v4l2_warn(&dev->v4l2_dev, "sensor power on failed\n"); + } + + /* finally select the sensor's input */ + sinput = &sensor->input; + sensor_input = index - sinput->first; + ret = v4l2_subdev_call(sensor->sd, video, s_routing, + sinput->value[sensor_input], 0, 0); + + dev->current_input = index; + + /* + * Status update required if there is a change + * of inputs + */ + atomic_set(&dev->status_change, 1); + + return 0; +} + +static int vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return v4l2_subdev_call(dev->sensor->sd, video, g_parm, a); +} + +static int vidioc_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return v4l2_subdev_call(dev->sensor->sd, video, s_parm, a); +} + +static int vidioc_g_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE: + /* + * compose windows are not supported in this driver, + * compose window is same as user buffers from s_fmt. + */ + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = dev->user_fmt.fmt.pix.width; + sel->r.height = dev->user_fmt.fmt.pix.height; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r = dev->crop_bounds; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = dev->crop_defrect; + break; + case V4L2_SEL_TGT_CROP: + sel->r = dev->crop; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vidioc_s_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct v4l2_rect *bounds = &dev->crop_bounds; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + if (vb2_is_busy(&dev->buffer_queue)) + return -EBUSY; + + /* make sure crop window is within bounds */ + if (sel->r.top < 0 || sel->r.left < 0 || + sel->r.left + sel->r.width > bounds->width || + sel->r.top + sel->r.height > bounds->height) + return -EINVAL; + + /* + * FIXME: the IPU currently does not setup the CCIR code + * registers properly to handle arbitrary vertical crop + * windows. So return error if the sensor bus is BT.656 + * and user is asking to change vertical cropping. + */ + if (dev->sensor->ep.bus_type == V4L2_MBUS_BT656 && + (sel->r.top != dev->crop.top || + sel->r.height != dev->crop.height)) { + v4l2_err(&dev->v4l2_dev, + "vertical crop is not supported for this sensor!\n"); + return -EINVAL; + } + + /* adjust crop window to h/w alignment restrictions */ + sel->r.width &= ~0x7; + sel->r.left &= ~0x3; + + dev->crop = sel->r; + + /* + * Crop window has changed, we need to adjust the user + * width/height to meet new IC resizer restrictions or to + * match the new crop window if the IC can't be used. + */ + adjust_user_fmt(dev, &dev->sensor_fmt, &dev->user_fmt, + &dev->crop); + + return 0; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct vb2_queue *vq = &dev->buffer_queue; + unsigned long flags; + int ret; + + if (vb2_is_busy(vq) || (dev->io_ctx && !is_io_ctx(ctx))) + return -EBUSY; + + ctx->alloc_ctx = vb2_dma_contig_init_ctx(dev->dev); + if (IS_ERR(ctx->alloc_ctx)) { + v4l2_err(&dev->v4l2_dev, "failed to alloc vb2 context\n"); + return PTR_ERR(ctx->alloc_ctx); + } + + INIT_LIST_HEAD(&ctx->ready_q); + INIT_WORK(&ctx->restart_work, restart_work_handler); + INIT_WORK(&ctx->stop_work, stop_work_handler); + __init_timer(&ctx->restart_timer, TIMER_IRQSAFE); + ctx->restart_timer.data = (unsigned long)ctx; + ctx->restart_timer.function = imxcam_restart_timeout; + + vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + vq->drv_priv = ctx; + vq->buf_struct_size = sizeof(struct imxcam_buffer); + vq->ops = &imxcam_qops; + vq->mem_ops = &vb2_dma_contig_memops; + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(vq); + if (ret) { + v4l2_err(&dev->v4l2_dev, "vb2_queue_init failed\n"); + goto alloc_ctx_free; + } + + ret = vb2_reqbufs(vq, reqbufs); + if (ret) { + v4l2_err(&dev->v4l2_dev, "vb2_reqbufs failed\n"); + goto alloc_ctx_free; + } + + spin_lock_irqsave(&dev->notify_lock, flags); + dev->io_ctx = ctx; + spin_unlock_irqrestore(&dev->notify_lock, flags); + + return 0; + +alloc_ctx_free: + vb2_dma_contig_cleanup_ctx(ctx->alloc_ctx); + return ret; +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + return vb2_querybuf(vq, buf); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!is_io_ctx(ctx)) + return -EBUSY; + + return vb2_qbuf(vq, buf); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!is_io_ctx(ctx)) + return -EBUSY; + + return vb2_dqbuf(vq, buf, file->f_flags & O_NONBLOCK); +} + +static int vidioc_expbuf(struct file *file, void *priv, + struct v4l2_exportbuffer *eb) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!is_io_ctx(ctx)) + return -EBUSY; + + return vb2_expbuf(vq, eb); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!is_io_ctx(ctx)) + return -EBUSY; + + return vb2_streamon(vq, type); +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!is_io_ctx(ctx)) + return -EBUSY; + + return vb2_streamoff(vq, type); +} + +static const struct v4l2_ioctl_ops imxcam_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + + .vidioc_querystd = vidioc_querystd, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, + + .vidioc_g_selection = vidioc_g_selection, + .vidioc_s_selection = vidioc_s_selection, + + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_expbuf = vidioc_expbuf, + + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +}; + +/* + * Queue operations + */ + +static int imxcam_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct imxcam_ctx *ctx = vb2_get_drv_priv(vq); + struct imxcam_dev *dev = ctx->dev; + unsigned int count = *nbuffers; + u32 sizeimage = dev->user_fmt.fmt.pix.sizeimage; + + if (vq->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + while (sizeimage * count > VID_MEM_LIMIT) + count--; + + *nplanes = 1; + *nbuffers = count; + sizes[0] = sizeimage; + + alloc_ctxs[0] = ctx->alloc_ctx; + + dprintk(dev, "get %d buffer(s) of size %d each.\n", count, sizeimage); + + return 0; +} + +static int imxcam_buf_init(struct vb2_buffer *vb) +{ + struct imxcam_buffer *buf = to_imxcam_vb(vb); + + INIT_LIST_HEAD(&buf->list); + return 0; +} + +static int imxcam_buf_prepare(struct vb2_buffer *vb) +{ + struct imxcam_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct imxcam_dev *dev = ctx->dev; + + if (vb2_plane_size(vb, 0) < dev->user_fmt.fmt.pix.sizeimage) { + v4l2_err(&dev->v4l2_dev, + "data will not fit into plane (%lu < %lu)\n", + vb2_plane_size(vb, 0), + (long)dev->user_fmt.fmt.pix.sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, dev->user_fmt.fmt.pix.sizeimage); + + return 0; +} + +static void imxcam_buf_queue(struct vb2_buffer *vb) +{ + struct imxcam_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct imxcam_dev *dev = ctx->dev; + struct imxcam_buffer *buf = to_imxcam_vb(vb); + unsigned long flags; + bool kickstart; + + spin_lock_irqsave(&dev->irqlock, flags); + + list_add_tail(&buf->list, &ctx->ready_q); + + /* kickstart DMA chain if we have two frames in active q */ + kickstart = (vb2_is_streaming(vb->vb2_queue) && + !(list_empty(&ctx->ready_q) || + list_is_singular(&ctx->ready_q))); + + spin_unlock_irqrestore(&dev->irqlock, flags); + + if (kickstart) + start_encoder(dev); +} + +static void imxcam_lock(struct vb2_queue *vq) +{ + struct imxcam_ctx *ctx = vb2_get_drv_priv(vq); + struct imxcam_dev *dev = ctx->dev; + + mutex_lock(&dev->mutex); +} + +static void imxcam_unlock(struct vb2_queue *vq) +{ + struct imxcam_ctx *ctx = vb2_get_drv_priv(vq); + struct imxcam_dev *dev = ctx->dev; + + mutex_unlock(&dev->mutex); +} + +static int imxcam_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct imxcam_ctx *ctx = vb2_get_drv_priv(vq); + struct imxcam_buffer *buf, *tmp; + int ret; + + if (vb2_is_streaming(vq)) + return 0; + + ctx->stop = false; + + ret = set_stream(ctx, true); + if (ret) + goto return_bufs; + + return 0; + +return_bufs: + list_for_each_entry_safe(buf, tmp, &ctx->ready_q, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED); + } + return ret; +} + +static void imxcam_stop_streaming(struct vb2_queue *vq) +{ + struct imxcam_ctx *ctx = vb2_get_drv_priv(vq); + struct imxcam_dev *dev = ctx->dev; + struct imxcam_buffer *frame; + unsigned long flags; + + if (!vb2_is_streaming(vq)) + return; + + /* + * signal that streaming is being stopped, so that the + * restart_work_handler() will skip unnecessary stream + * restarts, and to stop kicking the restart timer. + */ + ctx->stop = true; + + set_stream(ctx, false); + + spin_lock_irqsave(&dev->irqlock, flags); + + /* release all active buffers */ + while (!list_empty(&ctx->ready_q)) { + frame = list_entry(ctx->ready_q.next, + struct imxcam_buffer, list); + list_del(&frame->list); + vb2_buffer_done(&frame->vb, VB2_BUF_STATE_ERROR); + } + + spin_unlock_irqrestore(&dev->irqlock, flags); +} + +static struct vb2_ops imxcam_qops = { + .queue_setup = imxcam_queue_setup, + .buf_init = imxcam_buf_init, + .buf_prepare = imxcam_buf_prepare, + .buf_queue = imxcam_buf_queue, + .wait_prepare = imxcam_unlock, + .wait_finish = imxcam_lock, + .start_streaming = imxcam_start_streaming, + .stop_streaming = imxcam_stop_streaming, +}; + +/* + * File operations + */ +static int imxcam_open(struct file *file) +{ + struct imxcam_dev *dev = video_drvdata(file); + struct imxcam_ctx *ctx; + int ret; + + if (mutex_lock_interruptible(&dev->mutex)) + return -ERESTARTSYS; + + if (!dev->sensor || !dev->sensor->sd) { + v4l2_err(&dev->v4l2_dev, "no subdevice registered\n"); + ret = -ENODEV; + goto unlock; + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto unlock; + } + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + v4l2_fh_add(&ctx->fh); + + ret = sensor_set_power(dev, 1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "sensor power on failed\n"); + goto ctx_free; + } + + /* update the sensor's current lock status and format */ + update_signal_lock_status(dev); + update_sensor_fmt(dev); + + mutex_unlock(&dev->mutex); + return 0; + +ctx_free: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +unlock: + mutex_unlock(&dev->mutex); + return ret; +} + +static int imxcam_release(struct file *file) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + unsigned long flags; + int ret = 0; + + mutex_lock(&dev->mutex); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + if (is_io_ctx(ctx)) { + vb2_queue_release(&dev->buffer_queue); + vb2_dma_contig_cleanup_ctx(ctx->alloc_ctx); + + spin_lock_irqsave(&dev->notify_lock, flags); + /* cancel any pending or scheduled restart timer */ + del_timer_sync(&ctx->restart_timer); + dev->io_ctx = NULL; + spin_unlock_irqrestore(&dev->notify_lock, flags); + + /* + * cancel any scheduled restart work, we have to release + * the dev->mutex in case it has already been scheduled. + */ + mutex_unlock(&dev->mutex); + cancel_work_sync(&ctx->restart_work); + mutex_lock(&dev->mutex); + } + + if (!dev->sensor || !dev->sensor->sd) { + v4l2_warn(&dev->v4l2_dev, "lost the slave?\n"); + goto free_ctx; + } + + ret = sensor_set_power(dev, 0); + if (ret) + v4l2_err(&dev->v4l2_dev, "sensor power off failed\n"); + +free_ctx: + kfree(ctx); + mutex_unlock(&dev->mutex); + return ret; +} + +static unsigned int imxcam_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct vb2_queue *vq = &dev->buffer_queue; + int ret; + + if (mutex_lock_interruptible(&dev->mutex)) + return -ERESTARTSYS; + + ret = vb2_poll(vq, file, wait); + + mutex_unlock(&dev->mutex); + return ret; +} + +static int imxcam_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct imxcam_ctx *ctx = file2ctx(file); + struct imxcam_dev *dev = ctx->dev; + struct vb2_queue *vq = &dev->buffer_queue; + int ret; + + if (mutex_lock_interruptible(&dev->mutex)) + return -ERESTARTSYS; + + ret = vb2_mmap(vq, vma); + + mutex_unlock(&dev->mutex); + return ret; +} + +static const struct v4l2_file_operations imxcam_fops = { + .owner = THIS_MODULE, + .open = imxcam_open, + .release = imxcam_release, + .poll = imxcam_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = imxcam_mmap, +}; + +static struct video_device imxcam_videodev = { + .name = DEVICE_NAME, + .fops = &imxcam_fops, + .ioctl_ops = &imxcam_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_RX, + .tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, +}; + +/* + * Handle notifications from the subdevs. + */ +static void imxcam_subdev_notification(struct v4l2_subdev *sd, + unsigned int notification, + void *arg) +{ + struct imxcam_dev *dev; + struct imxcam_ctx *ctx; + struct v4l2_event *ev; + unsigned long flags; + + if (!sd) + return; + + dev = sd2dev(sd); + + spin_lock_irqsave(&dev->notify_lock, flags); + + ctx = dev->io_ctx; + + switch (notification) { + case IMXCAM_NFB4EOF_NOTIFY: + if (ctx && !ctx->stop) + imxcam_bump_restart_timer(ctx); + break; + case IMXCAM_FRAME_INTERVAL_NOTIFY: + if (ctx && !ctx->stop && !atomic_read(&dev->pending_restart)) + imxcam_bump_restart_timer(ctx); + break; + case IMXCAM_EOF_TIMEOUT_NOTIFY: + if (ctx && !ctx->stop) { + /* + * cancel a running restart timer since we are + * restarting now anyway + */ + del_timer_sync(&ctx->restart_timer); + /* and restart now */ + schedule_work(&ctx->restart_work); + } + break; + case V4L2_DEVICE_NOTIFY_EVENT: + ev = (struct v4l2_event *)arg; + if (ev && ev->type == V4L2_EVENT_SOURCE_CHANGE) { + atomic_set(&dev->status_change, 1); + if (ctx && !ctx->stop) { + v4l2_warn(&dev->v4l2_dev, + "decoder status change\n"); + imxcam_bump_restart_timer(ctx); + } + /* send decoder status events to userspace */ + v4l2_event_queue(dev->vfd, ev); + } + break; + } + + spin_unlock_irqrestore(&dev->notify_lock, flags); +} + + +static void imxcam_unregister_sync_subdevs(struct imxcam_dev *dev) +{ + if (!IS_ERR_OR_NULL(dev->smfc_sd)) + v4l2_device_unregister_subdev(dev->smfc_sd); + + if (!IS_ERR_OR_NULL(dev->prpenc_sd)) + v4l2_device_unregister_subdev(dev->prpenc_sd); + + if (!IS_ERR_OR_NULL(dev->vdic_sd)) + v4l2_device_unregister_subdev(dev->vdic_sd); +} + +static int imxcam_register_sync_subdevs(struct imxcam_dev *dev) +{ + int ret; + + dev->smfc_sd = imxcam_smfc_init(dev); + if (IS_ERR(dev->smfc_sd)) + return PTR_ERR(dev->smfc_sd); + + dev->prpenc_sd = imxcam_ic_prpenc_init(dev); + if (IS_ERR(dev->prpenc_sd)) + return PTR_ERR(dev->prpenc_sd); + + dev->vdic_sd = imxcam_vdic_init(dev); + if (IS_ERR(dev->vdic_sd)) + return PTR_ERR(dev->vdic_sd); + + ret = v4l2_device_register_subdev(&dev->v4l2_dev, dev->smfc_sd); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to register subdev %s\n", + dev->smfc_sd->name); + goto unreg; + } + v4l2_info(&dev->v4l2_dev, "Registered subdev %s\n", dev->smfc_sd->name); + + ret = v4l2_device_register_subdev(&dev->v4l2_dev, dev->prpenc_sd); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to register subdev %s\n", + dev->prpenc_sd->name); + goto unreg; + } + v4l2_info(&dev->v4l2_dev, "Registered subdev %s\n", dev->prpenc_sd->name); + + ret = v4l2_device_register_subdev(&dev->v4l2_dev, dev->vdic_sd); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to register subdev %s\n", + dev->vdic_sd->name); + goto unreg; + } + v4l2_info(&dev->v4l2_dev, "Registered subdev %s\n", dev->vdic_sd->name); + + return 0; + +unreg: + imxcam_unregister_sync_subdevs(dev); + return ret; +} + +/* async subdev bound notifier */ +static int imxcam_subdev_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct imxcam_dev *dev = notifier2dev(notifier); + struct imxcam_sensor_input *sinput; + struct imxcam_sensor *sensor; + int i, ret = -EINVAL; + + if (dev->csi2_asd && + sd->dev->of_node == dev->csi2_asd->match.of.node) { + dev->csi2_sd = sd; + ret = 0; + goto out; + } + + for (i = 0; i < dev->num_csi; i++) { + if (dev->csi_asd[i] && + sd->dev->of_node == dev->csi_asd[i]->match.of.node) { + dev->csi_list[i] = sd; + ret = 0; + goto out; + } + } + + for (i = 0; i < dev->num_vidmux; i++) { + if (dev->vidmux_asd[i] && + sd->dev->of_node == dev->vidmux_asd[i]->match.of.node) { + dev->vidmux_list[i] = sd; + ret = 0; + goto out; + } + } + + for (i = 0; i < dev->num_sensors; i++) { + sensor = &dev->sensor_list[i]; + if (sensor->asd && + sd->dev->of_node == sensor->asd->match.of.node) { + sensor->sd = sd; + + /* set sensor input names if needed */ + sinput = &sensor->input; + for (i = 0; i < sinput->num; i++) { + if (strlen(sinput->name[i])) + continue; + snprintf(sinput->name[i], + sizeof(sinput->name[i]), + "%s-%d", sd->name, i); + } + + ret = 0; + break; + } + } + +out: + if (ret) + v4l2_warn(&dev->v4l2_dev, "Received unknown subdev %s\n", + sd->name); + else + v4l2_info(&dev->v4l2_dev, "Registered subdev %s\n", sd->name); + + return ret; +} + +/* async subdev complete notifier */ +static int imxcam_probe_complete(struct v4l2_async_notifier *notifier) +{ + struct imxcam_dev *dev = notifier2dev(notifier); + struct imxcam_sensor *sensor; + int i, j, ret; + + /* assign CSI subdevs to every sensor */ + for (i = 0; i < dev->num_sensors; i++) { + sensor = &dev->sensor_list[i]; + for (j = 0; j < dev->num_csi; j++) { + if (sensor->csi_np == dev->csi_asd[j]->match.of.node) { + sensor->csi_sd = dev->csi_list[j]; + break; + } + } + if (j >= dev->num_csi) { + v4l2_err(&dev->v4l2_dev, + "Failed to find a CSI for sensor %s\n", + sensor->sd->name); + return -ENODEV; + } + } + + /* make default sensor the first in list */ + dev->sensor = &dev->sensor_list[0]; + + /* setup our controls */ + ret = v4l2_ctrl_handler_setup(&dev->ctrl_hdlr); + if (ret) + goto free_ctrls; + + ret = video_register_device(dev->vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto free_ctrls; + } + + ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev); + if (ret) + goto unreg; + + /* set video mux(es) in the pipeline to this sensor */ + ret = imxcam_set_video_muxes(dev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to set video muxes\n"); + goto unreg; + } + + dev->v4l2_dev.notify = imxcam_subdev_notification; + + v4l2_info(&dev->v4l2_dev, "Device registered as /dev/video%d\n", + dev->vfd->num); + + return 0; + +unreg: + video_unregister_device(dev->vfd); +free_ctrls: + v4l2_ctrl_handler_free(&dev->ctrl_hdlr); + return ret; +} + +static int imxcam_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct imxcam_dev *dev; + struct video_device *vfd; + struct pinctrl *pinctrl; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->dev = &pdev->dev; + mutex_init(&dev->mutex); + spin_lock_init(&dev->irqlock); + spin_lock_init(&dev->notify_lock); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + return ret; + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = imxcam_videodev; + vfd->lock = &dev->mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + video_set_drvdata(vfd, dev); + snprintf(vfd->name, sizeof(vfd->name), "%s", imxcam_videodev.name); + dev->vfd = vfd; + + platform_set_drvdata(pdev, dev); + + /* Get any pins needed */ + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + + /* setup some defaults */ + dev->user_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dev->user_fmt.fmt.pix.width = 640; + dev->user_fmt.fmt.pix.height = 480; + dev->user_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + dev->user_fmt.fmt.pix.bytesperline = (640 * 12) >> 3; + dev->user_fmt.fmt.pix.sizeimage = + (480 * dev->user_fmt.fmt.pix.bytesperline); + dev->user_pixfmt = + imxcam_get_format(dev->user_fmt.fmt.pix.pixelformat, 0); + dev->current_std = V4L2_STD_UNKNOWN; + + dev->sensor_set_stream = sensor_set_stream; + + ret = imxcam_of_parse(dev, node); + if (ret) + goto unreg_dev; + + if (dev->fim.icap_channel < 0) + dev->fim.eof = fim_eof_handler; + + /* init our controls */ + ret = imxcam_init_controls(dev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "init controls failed\n"); + goto unreg_dev; + } + + ret = imxcam_register_sync_subdevs(dev); + if (ret) + goto unreg_dev; + + /* prepare the async subdev notifier and register it */ + dev->subdev_notifier.subdevs = dev->async_ptrs; + dev->subdev_notifier.bound = imxcam_subdev_bound; + dev->subdev_notifier.complete = imxcam_probe_complete; + ret = v4l2_async_notifier_register(&dev->v4l2_dev, + &dev->subdev_notifier); + if (ret) + goto unreg_dev; + + return 0; + +unreg_dev: + v4l2_device_unregister(&dev->v4l2_dev); + return ret; +} + +static int imxcam_remove(struct platform_device *pdev) +{ + struct imxcam_dev *dev = + (struct imxcam_dev *)platform_get_drvdata(pdev); + + v4l2_info(&dev->v4l2_dev, "Removing " DEVICE_NAME "\n"); + v4l2_ctrl_handler_free(&dev->ctrl_hdlr); + v4l2_async_notifier_unregister(&dev->subdev_notifier); + video_unregister_device(dev->vfd); + imxcam_unregister_sync_subdevs(dev); + v4l2_device_unregister(&dev->v4l2_dev); + + return 0; +} + +static const struct of_device_id imxcam_dt_ids[] = { + { .compatible = "fsl,imx-video-capture" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imxcam_dt_ids); + +static struct platform_driver imxcam_pdrv = { + .probe = imxcam_probe, + .remove = imxcam_remove, + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + .of_match_table = imxcam_dt_ids, + }, +}; + +module_platform_driver(imxcam_pdrv); + +MODULE_DESCRIPTION("i.MX5/6 v4l2 capture driver"); +MODULE_AUTHOR("Mentor Graphics Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/imx/capture/imx-camif.h b/drivers/staging/media/imx/capture/imx-camif.h new file mode 100644 index 0000000..6babbfe --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-camif.h @@ -0,0 +1,281 @@ +/* + * Video Capture driver for Freescale i.MX5/6 SOC + * + * Copyright (c) 2012-2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef _IMX_CAMIF_H +#define _IMX_CAMIF_H + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, 1, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + +/* + * These numbers are somewhat arbitrary, but we need at least: + * - 1 mipi-csi2 receiver subdev + * - 2 video-mux subdevs + * - 3 sensor subdevs (2 parallel, 1 mipi-csi2) + * - 4 CSI subdevs + */ +#define IMXCAM_MAX_SUBDEVS 16 +#define IMXCAM_MAX_SENSORS 8 +#define IMXCAM_MAX_VIDEOMUX 4 +#define IMXCAM_MAX_CSI 4 + +/* + * How long before no EOF interrupts cause a stream restart, or a buffer + * dequeue timeout, in msec. The dequeue timeout should be longer than + * the EOF timeout. + */ +#define IMXCAM_EOF_TIMEOUT 1000 +#define IMXCAM_DQ_TIMEOUT 5000 + +/* + * How long to delay a restart on ADV718x status changes or NFB4EOF, + * in msec. + */ +#define IMXCAM_RESTART_DELAY 200 + +/* + * Internal subdev notifications + */ +#define IMXCAM_NFB4EOF_NOTIFY _IO('6', 0) +#define IMXCAM_EOF_TIMEOUT_NOTIFY _IO('6', 1) +#define IMXCAM_FRAME_INTERVAL_NOTIFY _IO('6', 2) + +/* + * Frame Interval Monitor Control Indexes and default values + */ +enum { + FIM_CL_ENABLE = 0, + FIM_CL_NUM, + FIM_CL_TOLERANCE_MIN, + FIM_CL_TOLERANCE_MAX, + FIM_CL_NUM_SKIP, + FIM_NUM_CONTROLS, +}; + +#define FIM_CL_ENABLE_DEF 0 /* FIM disabled by default */ +#define FIM_CL_NUM_DEF 8 /* average 8 frames */ +#define FIM_CL_NUM_SKIP_DEF 8 /* skip 8 frames after restart */ +#define FIM_CL_TOLERANCE_MIN_DEF 50 /* usec */ +#define FIM_CL_TOLERANCE_MAX_DEF 0 /* no max tolerance (unbounded) */ + +struct imxcam_buffer { + struct vb2_buffer vb; /* v4l buffer must be first */ + struct list_head list; +}; + +static inline struct imxcam_buffer *to_imxcam_vb(struct vb2_buffer *vb) +{ + return container_of(vb, struct imxcam_buffer, vb); +} + +struct imxcam_pixfmt { + char *name; + u32 fourcc; + u32 codes[4]; + int bpp; /* total bpp */ + int y_depth; /* depth of first Y plane for planar formats */ +}; + +struct imxcam_dma_buf { + void *virt; + dma_addr_t phys; + unsigned long len; +}; + +/* + * A sensor's inputs parsed from v4l2_of_endpoint nodes in devicetree + */ +#define IMXCAM_MAX_INPUTS 16 + +struct imxcam_sensor_input { + /* input values passed to s_routing */ + u32 value[IMXCAM_MAX_INPUTS]; + /* input capabilities (V4L2_IN_CAP_*) */ + u32 caps[IMXCAM_MAX_INPUTS]; + /* input names */ + char name[IMXCAM_MAX_INPUTS][32]; + + /* number of inputs */ + int num; + /* first and last input indexes from imxcam perspective */ + int first; + int last; +}; + +struct imxcam_sensor { + struct v4l2_subdev *sd; + struct v4l2_async_subdev *asd; + struct v4l2_of_endpoint ep; /* sensor's endpoint info */ + + /* csi node and subdev this sensor is connected to */ + struct device_node *csi_np; + struct v4l2_subdev *csi_sd; + struct v4l2_of_endpoint csi_ep; /* parsed endpoint info of csi port */ + + struct imxcam_sensor_input input; + + /* input indeces of all video-muxes required to access this sensor */ + int vidmux_input[IMXCAM_MAX_VIDEOMUX]; + + int power_count; /* power use counter */ + int stream_count; /* stream use counter */ +}; + +struct imxcam_ctx; +struct imxcam_dev; + +/* frame interval monitor */ +struct imxcam_fim { + /* control cluster */ + struct v4l2_ctrl *ctrl[FIM_NUM_CONTROLS]; + + /* default ctrl values parsed from device tree */ + u32 of_defaults[FIM_NUM_CONTROLS]; + + /* current control values */ + bool enabled; + int num_avg; + int num_skip; + unsigned long tolerance_min; /* usec */ + unsigned long tolerance_max; /* usec */ + + int counter; + struct timespec last_ts; + unsigned long sum; /* usec */ + unsigned long nominal; /* usec */ + + /* + * input capture method of measuring FI (channel and flags + * from device tree) + */ + int icap_channel; + int icap_flags; + + /* + * otherwise, the EOF method of measuring FI, called by + * streaming subdevs from eof irq + */ + int (*eof)(struct imxcam_dev *dev, struct timeval *now); +}; + +struct imxcam_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + struct device *dev; + + struct mutex mutex; + spinlock_t irqlock; + spinlock_t notify_lock; + + /* buffer queue used in videobuf2 */ + struct vb2_queue buffer_queue; + + /* v4l2 controls */ + struct v4l2_ctrl_handler ctrl_hdlr; + int rotation; /* degrees */ + bool hflip; + bool vflip; + enum ipu_motion_sel motion; + + /* derived from rotation, hflip, vflip controls */ + enum ipu_rotate_mode rot_mode; + + struct imxcam_fim fim; + + /* the format from sensor and from userland */ + struct v4l2_format user_fmt; + struct imxcam_pixfmt *user_pixfmt; + struct v4l2_mbus_framefmt sensor_fmt; + struct v4l2_fract sensor_tpf; + struct imxcam_pixfmt *sensor_pixfmt; + struct v4l2_mbus_config mbus_cfg; + + /* + * the crop rectangle (from s_crop) specifies the crop dimensions + * and position over the raw capture frame boundaries. + */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop; + + /* misc status */ + int current_input; /* the current input */ + v4l2_std_id current_std; /* current video standard */ + atomic_t status_change; /* sensor status change */ + atomic_t pending_restart; /* a restart is pending */ + bool signal_locked; /* sensor signal lock */ + bool encoder_on; /* encode is on */ + bool using_ic; /* IC is being used for encode */ + bool using_vdic; /* VDIC is used for encode */ + bool vdic_direct; /* VDIC is using the direct + CSI->VDIC pipeline */ + + /* master descriptor list for async subdev registration */ + struct v4l2_async_subdev async_desc[IMXCAM_MAX_SUBDEVS]; + struct v4l2_async_subdev *async_ptrs[IMXCAM_MAX_SUBDEVS]; + + /* for async subdev registration */ + struct v4l2_async_notifier subdev_notifier; + + /* camera sensor subdev list */ + struct imxcam_sensor sensor_list[IMXCAM_MAX_SENSORS]; + struct imxcam_sensor *sensor; /* the current active sensor */ + int num_sensor_inputs; + int num_sensors; + + /* mipi-csi2 receiver subdev */ + struct v4l2_subdev *csi2_sd; + struct v4l2_async_subdev *csi2_asd; + + /* CSI subdev list */ + struct v4l2_subdev *csi_list[IMXCAM_MAX_CSI]; + struct v4l2_async_subdev *csi_asd[IMXCAM_MAX_CSI]; + int num_csi; + + /* video-mux subdev list */ + struct v4l2_subdev *vidmux_list[IMXCAM_MAX_VIDEOMUX]; + struct v4l2_async_subdev *vidmux_asd[IMXCAM_MAX_VIDEOMUX]; + int num_vidmux; + + /* synchronous prpenc, smfc, and vdic subdevs */ + struct v4l2_subdev *smfc_sd; + struct v4l2_subdev *prpenc_sd; + struct v4l2_subdev *vdic_sd; + + int (*sensor_set_stream)(struct imxcam_dev *dev, int on); + + /* + * the current open context that is doing IO (there can only + * be one allowed IO context at a time). + */ + struct imxcam_ctx *io_ctx; +}; + +struct imxcam_ctx { + struct v4l2_fh fh; + struct imxcam_dev *dev; + + struct vb2_alloc_ctx *alloc_ctx; + + /* streaming buffer queue */ + struct list_head ready_q; + + /* stream stop and restart handling */ + struct work_struct restart_work; + struct work_struct stop_work; + struct timer_list restart_timer; + bool stop; /* streaming is stopping */ +}; + +struct v4l2_subdev *imxcam_smfc_init(struct imxcam_dev *dev); +struct v4l2_subdev *imxcam_ic_prpenc_init(struct imxcam_dev *dev); +struct v4l2_subdev *imxcam_vdic_init(struct imxcam_dev *dev); + +#endif /* _IMX_CAMIF_H */ diff --git a/drivers/staging/media/imx/capture/imx-csi.c b/drivers/staging/media/imx/capture/imx-csi.c new file mode 100644 index 0000000..23973a6 --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-csi.c @@ -0,0 +1,195 @@ +/* + * V4L2 Capture CSI Subdev for Freescale i.MX5/6 SOC + * + * Copyright (c) 2014-2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-of.h> +#include <media/v4l2-ctrls.h> +#include <video/imx-ipu-v3.h> +#include "imx-camif.h" + +struct csi_priv { + struct device *dev; + struct imxcam_dev *camif; + struct v4l2_subdev sd; + struct ipu_soc *ipu; + struct ipu_csi *csi; +}; + +static inline struct csi_priv *sd_to_dev(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csi_priv, sd); +} + +/* + * Update the CSI whole sensor and active windows, and initialize + * the CSI interface and muxes. + */ +static void csi_setup(struct csi_priv *priv) +{ + struct imxcam_dev *camif = priv->camif; + int vc_num = camif->sensor->csi_ep.base.id; + bool is_csi2 = camif->sensor->ep.bus_type == V4L2_MBUS_CSI2; + enum ipu_csi_dest dest; + + ipu_csi_set_window(priv->csi, &camif->crop); + ipu_csi_init_interface(priv->csi, &camif->mbus_cfg, + &camif->sensor_fmt); + if (is_csi2) + ipu_csi_set_mipi_datatype(priv->csi, vc_num, + &camif->sensor_fmt); + + /* select either parallel or MIPI-CSI2 as input to our CSI */ + ipu_csi_set_src(priv->csi, vc_num, is_csi2); + + /* set CSI destination */ + if (camif->using_vdic && camif->vdic_direct) + dest = IPU_CSI_DEST_VDIC; + else if (camif->using_ic && !camif->using_vdic) + dest = IPU_CSI_DEST_IC; + else + dest = IPU_CSI_DEST_IDMAC; + ipu_csi_set_dest(priv->csi, dest); + + ipu_csi_dump(priv->csi); +} + +static void csi_put_ipu_resources(struct csi_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->csi)) + ipu_csi_put(priv->csi); + priv->csi = NULL; +} + +static int csi_get_ipu_resources(struct csi_priv *priv) +{ + struct imxcam_dev *camif = priv->camif; + int csi_id = camif->sensor->csi_ep.base.port; + + priv->ipu = dev_get_drvdata(priv->dev->parent); + + priv->csi = ipu_csi_get(priv->ipu, csi_id); + if (IS_ERR(priv->csi)) { + v4l2_err(&priv->sd, "failed to get CSI %d\n", csi_id); + return PTR_ERR(priv->csi); + } + + return 0; +} + +static int csi_start(struct csi_priv *priv) +{ + int err; + + err = csi_get_ipu_resources(priv); + if (err) + return err; + + csi_setup(priv); + + err = ipu_csi_enable(priv->csi); + if (err) { + v4l2_err(&priv->sd, "CSI enable error: %d\n", err); + goto out_put_ipu; + } + + return 0; + +out_put_ipu: + csi_put_ipu_resources(priv); + return err; +} + +static int csi_stop(struct csi_priv *priv) +{ + ipu_csi_disable(priv->csi); + + csi_put_ipu_resources(priv); + + return 0; +} + +static int csi_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + + if (!sd->v4l2_dev || !sd->v4l2_dev->dev) + return -ENODEV; + + /* get imxcam host device */ + priv->camif = dev_get_drvdata(sd->v4l2_dev->dev); + + return enable ? csi_start(priv) : csi_stop(priv); +} + +static struct v4l2_subdev_video_ops csi_video_ops = { + .s_stream = csi_s_stream, +}; + +static struct v4l2_subdev_ops csi_subdev_ops = { + .video = &csi_video_ops, +}; + +static int imxcam_csi_probe(struct platform_device *pdev) +{ + struct csi_priv *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, &priv->sd); + + priv->dev = &pdev->dev; + + v4l2_subdev_init(&priv->sd, &csi_subdev_ops); + v4l2_set_subdevdata(&priv->sd, priv); + priv->sd.dev = &pdev->dev; + priv->sd.owner = THIS_MODULE; + strlcpy(priv->sd.name, "imx-camera-csi", sizeof(priv->sd.name)); + + return v4l2_async_register_subdev(&priv->sd); +} + +static int imxcam_csi_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct csi_priv *priv = sd_to_dev(sd); + + v4l2_async_unregister_subdev(&priv->sd); + v4l2_device_unregister_subdev(sd); + + return 0; +} + +static const struct platform_device_id imxcam_csi_ids[] = { + { .name = "imx-ipuv3-csi" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, imxcam_csi_ids); + +static struct platform_driver imxcam_csi_driver = { + .probe = imxcam_csi_probe, + .remove = imxcam_csi_remove, + .id_table = imxcam_csi_ids, + .driver = { + .name = "imx-ipuv3-csi", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(imxcam_csi_driver); + +MODULE_AUTHOR("Mentor Graphics Inc."); +MODULE_DESCRIPTION("i.MX CSI subdev driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ipuv3-csi"); diff --git a/drivers/staging/media/imx/capture/imx-ic-prpenc.c b/drivers/staging/media/imx/capture/imx-ic-prpenc.c new file mode 100644 index 0000000..f0bae79 --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-ic-prpenc.c @@ -0,0 +1,660 @@ +/* + * V4L2 Capture Encoder Subdev for Freescale i.MX5/6 SOC + * + * This subdevice handles capture of video frames from the CSI, which + * routed directly to the Image Converter preprocess encode task, for + * resizing, colorspace conversion, and rotation. + * + * Copyright (c) 2012-2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pinctrl/consumer.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-of.h> +#include <media/v4l2-ctrls.h> +#include <video/imx-ipu-v3.h> +#include <media/imx.h> +#include "imx-camif.h" + +struct prpenc_priv { + struct imxcam_dev *dev; + struct v4l2_subdev sd; + + struct ipu_soc *ipu; + struct ipuv3_channel *enc_ch; + struct ipuv3_channel *enc_rot_in_ch; + struct ipuv3_channel *enc_rot_out_ch; + struct ipu_ic *ic_enc; + struct ipu_smfc *smfc; + + struct v4l2_mbus_framefmt inf; /* input sensor format */ + struct v4l2_pix_format outf; /* output user format */ + enum ipu_color_space in_cs; /* input colorspace */ + enum ipu_color_space out_cs; /* output colorspace */ + + /* active (undergoing DMA) buffers, one for each IPU buffer */ + struct imxcam_buffer *active_frame[2]; + + struct imxcam_dma_buf rot_buf[2]; + struct imxcam_dma_buf underrun_buf; + int buf_num; + + struct timer_list eof_timeout_timer; + int eof_irq; + int nfb4eof_irq; + + bool last_eof; /* waiting for last EOF at stream off */ + struct completion last_eof_comp; +}; + +static void prpenc_put_ipu_resources(struct prpenc_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->ic_enc)) + ipu_ic_put(priv->ic_enc); + priv->ic_enc = NULL; + + if (!IS_ERR_OR_NULL(priv->enc_ch)) + ipu_idmac_put(priv->enc_ch); + priv->enc_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->enc_rot_in_ch)) + ipu_idmac_put(priv->enc_rot_in_ch); + priv->enc_rot_in_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->enc_rot_out_ch)) + ipu_idmac_put(priv->enc_rot_out_ch); + priv->enc_rot_out_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->smfc)) + ipu_smfc_put(priv->smfc); + priv->smfc = NULL; +} + +static int prpenc_get_ipu_resources(struct prpenc_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct v4l2_subdev *csi_sd = dev->sensor->csi_sd; + int ret; + + priv->ipu = dev_get_drvdata(csi_sd->dev->parent); + + priv->ic_enc = ipu_ic_get(priv->ipu, IC_TASK_ENCODER); + if (IS_ERR(priv->ic_enc)) { + v4l2_err(&priv->sd, "failed to get IC ENC\n"); + ret = PTR_ERR(priv->ic_enc); + goto out; + } + + priv->enc_ch = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_IC_PRP_ENC_MEM); + if (IS_ERR(priv->enc_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + IPUV3_CHANNEL_IC_PRP_ENC_MEM); + ret = PTR_ERR(priv->enc_ch); + goto out; + } + + priv->enc_rot_in_ch = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_MEM_ROT_ENC); + if (IS_ERR(priv->enc_rot_in_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + IPUV3_CHANNEL_MEM_ROT_ENC); + ret = PTR_ERR(priv->enc_rot_in_ch); + goto out; + } + + priv->enc_rot_out_ch = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_ROT_ENC_MEM); + if (IS_ERR(priv->enc_rot_out_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + IPUV3_CHANNEL_ROT_ENC_MEM); + ret = PTR_ERR(priv->enc_rot_out_ch); + goto out; + } + + return 0; +out: + prpenc_put_ipu_resources(priv); + return ret; +} + +static irqreturn_t prpenc_eof_interrupt(int irq, void *dev_id) +{ + struct prpenc_priv *priv = dev_id; + struct imxcam_dev *dev = priv->dev; + struct imxcam_ctx *ctx = dev->io_ctx; + struct imxcam_buffer *frame; + struct ipuv3_channel *channel; + enum vb2_buffer_state state; + struct timeval cur_timeval; + u64 cur_time_ns; + unsigned long flags; + dma_addr_t phys; + + spin_lock_irqsave(&dev->irqlock, flags); + + cur_time_ns = ktime_get_ns(); + cur_timeval = ns_to_timeval(cur_time_ns); + + /* timestamp and return the completed frame */ + frame = priv->active_frame[priv->buf_num]; + if (frame) { + frame->vb.timestamp = cur_time_ns; + state = (dev->signal_locked && + !atomic_read(&dev->pending_restart)) ? + VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR; + vb2_buffer_done(&frame->vb, state); + } + + if (priv->last_eof) { + complete(&priv->last_eof_comp); + priv->active_frame[priv->buf_num] = NULL; + priv->last_eof = false; + goto unlock; + } + + if (dev->fim.eof && dev->fim.eof(dev, &cur_timeval)) + v4l2_subdev_notify(&priv->sd, IMXCAM_FRAME_INTERVAL_NOTIFY, + NULL); + + /* bump the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + + if (!list_empty(&ctx->ready_q)) { + frame = list_entry(ctx->ready_q.next, + struct imxcam_buffer, list); + phys = vb2_dma_contig_plane_dma_addr(&frame->vb, 0); + list_del(&frame->list); + priv->active_frame[priv->buf_num] = frame; + } else { + phys = priv->underrun_buf.phys; + priv->active_frame[priv->buf_num] = NULL; + } + + channel = (ipu_rot_mode_is_irt(dev->rot_mode)) ? + priv->enc_rot_out_ch : priv->enc_ch; + + if (ipu_idmac_buffer_is_ready(channel, priv->buf_num)) + ipu_idmac_clear_buffer(channel, priv->buf_num); + + ipu_cpmem_set_buffer(channel, priv->buf_num, phys); + ipu_idmac_select_buffer(channel, priv->buf_num); + + priv->buf_num ^= 1; + +unlock: + spin_unlock_irqrestore(&dev->irqlock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t prpenc_nfb4eof_interrupt(int irq, void *dev_id) +{ + struct prpenc_priv *priv = dev_id; + + v4l2_err(&priv->sd, "NFB4EOF\n"); + + /* + * It has been discovered that with rotation, stream off + * creates a single NFB4EOF event which is 100% repeatable. So + * scheduling a restart here causes an endless NFB4EOF-->restart + * cycle. The error itself seems innocuous, capture is not adversely + * affected. + * + * So don't schedule a restart on NFB4EOF error. If the source + * of the NFB4EOF event on disable is ever found, it can + * be re-enabled, but is probably not necessary. Detecting the + * interrupt (and clearing the irq status in the IPU) seems to + * be enough. + */ + + return IRQ_HANDLED; +} + +/* + * EOF timeout timer function. + */ +static void prpenc_eof_timeout(unsigned long data) +{ + struct prpenc_priv *priv = (struct prpenc_priv *)data; + + v4l2_err(&priv->sd, "EOF timeout\n"); + + v4l2_subdev_notify(&priv->sd, IMXCAM_EOF_TIMEOUT_NOTIFY, NULL); +} + +static void prpenc_free_dma_buf(struct prpenc_priv *priv, + struct imxcam_dma_buf *buf) +{ + struct imxcam_dev *dev = priv->dev; + + if (buf->virt) + dma_free_coherent(dev->dev, buf->len, buf->virt, buf->phys); + + buf->virt = NULL; + buf->phys = 0; +} + +static int prpenc_alloc_dma_buf(struct prpenc_priv *priv, + struct imxcam_dma_buf *buf, + int size) +{ + struct imxcam_dev *dev = priv->dev; + + prpenc_free_dma_buf(priv, buf); + + buf->len = PAGE_ALIGN(size); + buf->virt = dma_alloc_coherent(dev->dev, buf->len, &buf->phys, + GFP_DMA | GFP_KERNEL); + if (!buf->virt) { + v4l2_err(&priv->sd, "failed to alloc dma buffer\n"); + return -ENOMEM; + } + + return 0; +} + +static void prpenc_setup_channel(struct prpenc_priv *priv, + struct ipuv3_channel *channel, + enum ipu_rotate_mode rot_mode, + dma_addr_t addr0, dma_addr_t addr1, + bool rot_swap_width_height) +{ + struct imxcam_dev *dev = priv->dev; + u32 width, height, stride; + unsigned int burst_size; + struct ipu_image image; + + if (rot_swap_width_height) { + width = priv->outf.height; + height = priv->outf.width; + } else { + width = priv->outf.width; + height = priv->outf.height; + } + + stride = dev->user_pixfmt->y_depth ? + (width * dev->user_pixfmt->y_depth) >> 3 : + (width * dev->user_pixfmt->bpp) >> 3; + + ipu_cpmem_zero(channel); + + memset(&image, 0, sizeof(image)); + image.pix.width = image.rect.width = width; + image.pix.height = image.rect.height = height; + image.pix.bytesperline = stride; + image.pix.pixelformat = priv->outf.pixelformat; + image.phys0 = addr0; + image.phys1 = addr1; + ipu_cpmem_set_image(channel, &image); + + if (channel == priv->enc_rot_in_ch || + channel == priv->enc_rot_out_ch) { + burst_size = 8; + ipu_cpmem_set_block_mode(channel); + } else { + burst_size = (width & 0xf) ? 8 : 16; + } + + ipu_cpmem_set_burstsize(channel, burst_size); + + if (rot_mode) + ipu_cpmem_set_rotation(channel, rot_mode); + + if (V4L2_FIELD_HAS_BOTH(priv->inf.field) && channel == priv->enc_ch) + ipu_cpmem_interlaced_scan(channel, stride); + + ipu_ic_task_idma_init(priv->ic_enc, channel, width, height, + burst_size, rot_mode); + ipu_cpmem_set_axi_id(channel, 1); + + ipu_idmac_set_double_buffer(channel, true); +} + +static int prpenc_setup_rotation(struct prpenc_priv *priv, + dma_addr_t phys0, dma_addr_t phys1) +{ + struct imxcam_dev *dev = priv->dev; + int ret; + + ret = prpenc_alloc_dma_buf(priv, &priv->underrun_buf, + priv->outf.sizeimage); + if (ret) { + v4l2_err(&priv->sd, "failed to alloc underrun_buf, %d\n", ret); + return ret; + } + + ret = prpenc_alloc_dma_buf(priv, &priv->rot_buf[0], + priv->outf.sizeimage); + if (ret) { + v4l2_err(&priv->sd, "failed to alloc rot_buf[0], %d\n", ret); + goto free_underrun; + } + ret = prpenc_alloc_dma_buf(priv, &priv->rot_buf[1], + priv->outf.sizeimage); + if (ret) { + v4l2_err(&priv->sd, "failed to alloc rot_buf[1], %d\n", ret); + goto free_rot0; + } + + ret = ipu_ic_task_init(priv->ic_enc, + priv->inf.width, priv->inf.height, + priv->outf.height, priv->outf.width, + priv->in_cs, priv->out_cs); + if (ret) { + v4l2_err(&priv->sd, "ipu_ic_task_init failed, %d\n", ret); + goto free_rot1; + } + + /* init the IC ENC-->MEM IDMAC channel */ + prpenc_setup_channel(priv, priv->enc_ch, + IPU_ROTATE_NONE, + priv->rot_buf[0].phys, + priv->rot_buf[1].phys, + true); + + /* init the MEM-->IC ENC ROT IDMAC channel */ + prpenc_setup_channel(priv, priv->enc_rot_in_ch, + dev->rot_mode, + priv->rot_buf[0].phys, + priv->rot_buf[1].phys, + true); + + /* init the destination IC ENC ROT-->MEM IDMAC channel */ + prpenc_setup_channel(priv, priv->enc_rot_out_ch, + IPU_ROTATE_NONE, + phys0, phys1, + false); + + /* now link IC ENC-->MEM to MEM-->IC ENC ROT */ + ipu_idmac_link(priv->enc_ch, priv->enc_rot_in_ch); + + /* enable the IC */ + ipu_ic_enable(priv->ic_enc); + + /* set buffers ready */ + ipu_idmac_select_buffer(priv->enc_ch, 0); + ipu_idmac_select_buffer(priv->enc_ch, 1); + ipu_idmac_select_buffer(priv->enc_rot_out_ch, 0); + ipu_idmac_select_buffer(priv->enc_rot_out_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->enc_ch); + ipu_idmac_enable_channel(priv->enc_rot_in_ch); + ipu_idmac_enable_channel(priv->enc_rot_out_ch); + + /* and finally enable the IC PRPENC task */ + ipu_ic_task_enable(priv->ic_enc); + + return 0; + +free_rot1: + prpenc_free_dma_buf(priv, &priv->rot_buf[1]); +free_rot0: + prpenc_free_dma_buf(priv, &priv->rot_buf[0]); +free_underrun: + prpenc_free_dma_buf(priv, &priv->underrun_buf); + return ret; +} + +static int prpenc_setup_norotation(struct prpenc_priv *priv, + dma_addr_t phys0, dma_addr_t phys1) +{ + struct imxcam_dev *dev = priv->dev; + int ret; + + ret = prpenc_alloc_dma_buf(priv, &priv->underrun_buf, + priv->outf.sizeimage); + if (ret) { + v4l2_err(&priv->sd, "failed to alloc underrun_buf, %d\n", ret); + return ret; + } + + ret = ipu_ic_task_init(priv->ic_enc, + priv->inf.width, priv->inf.height, + priv->outf.width, priv->outf.height, + priv->in_cs, priv->out_cs); + if (ret) { + v4l2_err(&priv->sd, "ipu_ic_task_init failed, %d\n", ret); + goto free_underrun; + } + + /* init the IC PRP-->MEM IDMAC channel */ + prpenc_setup_channel(priv, priv->enc_ch, dev->rot_mode, + phys0, phys1, false); + + ipu_cpmem_dump(priv->enc_ch); + ipu_ic_dump(priv->ic_enc); + ipu_dump(priv->ipu); + + ipu_ic_enable(priv->ic_enc); + + /* set buffers ready */ + ipu_idmac_select_buffer(priv->enc_ch, 0); + ipu_idmac_select_buffer(priv->enc_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->enc_ch); + + /* enable the IC ENCODE task */ + ipu_ic_task_enable(priv->ic_enc); + + return 0; + +free_underrun: + prpenc_free_dma_buf(priv, &priv->underrun_buf); + return ret; +} + +static int prpenc_start(struct prpenc_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct imxcam_ctx *ctx = dev->io_ctx; + int csi_id = dev->sensor->csi_ep.base.port; + struct imxcam_buffer *frame, *tmp; + dma_addr_t phys[2] = {0}; + int i = 0, ret; + + ret = prpenc_get_ipu_resources(priv); + if (ret) + return ret; + + list_for_each_entry_safe(frame, tmp, &ctx->ready_q, list) { + phys[i] = vb2_dma_contig_plane_dma_addr(&frame->vb, 0); + list_del(&frame->list); + priv->active_frame[i++] = frame; + if (i >= 2) + break; + } + + priv->inf = dev->sensor_fmt; + priv->inf.width = dev->crop.width; + priv->inf.height = dev->crop.height; + priv->in_cs = ipu_mbus_code_to_colorspace(priv->inf.code); + + priv->outf = dev->user_fmt.fmt.pix; + priv->out_cs = ipu_pixelformat_to_colorspace(priv->outf.pixelformat); + + priv->buf_num = 0; + + /* init EOF completion waitq */ + init_completion(&priv->last_eof_comp); + priv->last_eof = false; + + /* set IC to receive from CSI */ + ipu_ic_set_src(priv->ic_enc, csi_id, false); + + if (ipu_rot_mode_is_irt(dev->rot_mode)) + ret = prpenc_setup_rotation(priv, phys[0], phys[1]); + else + ret = prpenc_setup_norotation(priv, phys[0], phys[1]); + if (ret) + goto out_put_ipu; + + priv->nfb4eof_irq = ipu_idmac_channel_irq(priv->ipu, + priv->enc_ch, + IPU_IRQ_NFB4EOF); + ret = devm_request_irq(dev->dev, priv->nfb4eof_irq, + prpenc_nfb4eof_interrupt, 0, + "imxcam-enc-nfb4eof", priv); + if (ret) { + v4l2_err(&priv->sd, + "Error registering encode NFB4EOF irq: %d\n", ret); + goto out_put_ipu; + } + + if (ipu_rot_mode_is_irt(dev->rot_mode)) + priv->eof_irq = ipu_idmac_channel_irq( + priv->ipu, priv->enc_rot_out_ch, IPU_IRQ_EOF); + else + priv->eof_irq = ipu_idmac_channel_irq( + priv->ipu, priv->enc_ch, IPU_IRQ_EOF); + + ret = devm_request_irq(dev->dev, priv->eof_irq, + prpenc_eof_interrupt, 0, + "imxcam-enc-eof", priv); + if (ret) { + v4l2_err(&priv->sd, + "Error registering encode eof irq: %d\n", ret); + goto out_free_nfb4eof_irq; + } + + /* sensor stream on */ + ret = dev->sensor_set_stream(dev, 1); + if (ret) { + v4l2_err(&priv->sd, "sensor stream on failed\n"); + goto out_free_eof_irq; + } + + /* start the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + + return 0; + +out_free_eof_irq: + devm_free_irq(dev->dev, priv->eof_irq, priv); +out_free_nfb4eof_irq: + devm_free_irq(dev->dev, priv->nfb4eof_irq, priv); +out_put_ipu: + prpenc_put_ipu_resources(priv); + for (i = 0; i < 2; i++) { + frame = priv->active_frame[i]; + vb2_buffer_done(&frame->vb, VB2_BUF_STATE_QUEUED); + } + return ret; +} + +static int prpenc_stop(struct prpenc_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct imxcam_buffer *frame; + unsigned long flags; + int i, ret; + + /* mark next EOF interrupt as the last before stream off */ + spin_lock_irqsave(&dev->irqlock, flags); + priv->last_eof = true; + spin_unlock_irqrestore(&dev->irqlock, flags); + + /* + * and then wait for interrupt handler to mark completion. + */ + ret = wait_for_completion_timeout(&priv->last_eof_comp, + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + if (ret == 0) + v4l2_warn(&priv->sd, "wait last encode EOF timeout\n"); + + /* sensor stream off */ + ret = dev->sensor_set_stream(dev, 0); + if (ret) + v4l2_warn(&priv->sd, "sensor stream off failed\n"); + + devm_free_irq(dev->dev, priv->eof_irq, priv); + devm_free_irq(dev->dev, priv->nfb4eof_irq, priv); + + /* disable IC tasks and the channels */ + ipu_ic_task_disable(priv->ic_enc); + + ipu_idmac_disable_channel(priv->enc_ch); + if (ipu_rot_mode_is_irt(dev->rot_mode)) { + ipu_idmac_disable_channel(priv->enc_rot_in_ch); + ipu_idmac_disable_channel(priv->enc_rot_out_ch); + } + + if (ipu_rot_mode_is_irt(dev->rot_mode)) + ipu_idmac_unlink(priv->enc_ch, priv->enc_rot_in_ch); + + ipu_ic_disable(priv->ic_enc); + + prpenc_free_dma_buf(priv, &priv->rot_buf[0]); + prpenc_free_dma_buf(priv, &priv->rot_buf[1]); + prpenc_free_dma_buf(priv, &priv->underrun_buf); + + prpenc_put_ipu_resources(priv); + + /* cancel the EOF timeout timer */ + del_timer_sync(&priv->eof_timeout_timer); + + /* return any remaining active frames with error */ + for (i = 0; i < 2; i++) { + frame = priv->active_frame[i]; + if (frame && frame->vb.state == VB2_BUF_STATE_ACTIVE) { + frame->vb.timestamp = ktime_get_ns(); + vb2_buffer_done(&frame->vb, VB2_BUF_STATE_ERROR); + } + } + + return 0; +} + +static int prpenc_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct prpenc_priv *priv = v4l2_get_subdevdata(sd); + + return enable ? prpenc_start(priv) : prpenc_stop(priv); +} + +static struct v4l2_subdev_video_ops prpenc_video_ops = { + .s_stream = prpenc_s_stream, +}; + +static struct v4l2_subdev_ops prpenc_subdev_ops = { + .video = &prpenc_video_ops, +}; + +struct v4l2_subdev *imxcam_ic_prpenc_init(struct imxcam_dev *dev) +{ + struct prpenc_priv *priv; + + priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + init_timer(&priv->eof_timeout_timer); + priv->eof_timeout_timer.data = (unsigned long)priv; + priv->eof_timeout_timer.function = prpenc_eof_timeout; + + v4l2_subdev_init(&priv->sd, &prpenc_subdev_ops); + strlcpy(priv->sd.name, "imx-camera-prpenc", sizeof(priv->sd.name)); + v4l2_set_subdevdata(&priv->sd, priv); + + priv->dev = dev; + return &priv->sd; +} diff --git a/drivers/staging/media/imx/capture/imx-of.c b/drivers/staging/media/imx/capture/imx-of.c new file mode 100644 index 0000000..b6c5675 --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-of.c @@ -0,0 +1,354 @@ +/* + * Video Camera Capture driver for Freescale i.MX5/6 SOC + * + * Open Firmware parsing. + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/of_platform.h> +#include <media/v4l2-device.h> +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-of.h> +#include <media/v4l2-ctrls.h> +#include <video/imx-ipu-v3.h> +#include "imx-camif.h" + +/* parse inputs property from a sensor's upstream sink endpoint node */ +static void of_parse_sensor_inputs(struct imxcam_dev *dev, + struct device_node *sink_ep, + struct imxcam_sensor *sensor) +{ + struct imxcam_sensor_input *sinput = &sensor->input; + int next_input = dev->num_sensor_inputs; + int ret, i; + + for (i = 0; i < IMXCAM_MAX_INPUTS; i++) { + const char *input_name; + u32 val; + + ret = of_property_read_u32_index(sink_ep, "inputs", i, &val); + if (ret) + break; + + sinput->value[i] = val; + + ret = of_property_read_string_index(sink_ep, "input-names", i, + &input_name); + /* + * if input-names not provided, they will be set using + * the subdev name once the subdev is known during + * async bind + */ + if (!ret) + strncpy(sinput->name[i], input_name, + sizeof(sinput->name[i])); + + val = 0; + ret = of_property_read_u32_index(sink_ep, "input-caps", + i, &val); + sinput->caps[i] = val; + } + + sinput->num = i; + + /* if no inputs provided just assume a single input */ + if (sinput->num == 0) { + sinput->num = 1; + sinput->caps[0] = 0; + } + + sinput->first = next_input; + sinput->last = next_input + sinput->num - 1; + + dev->num_sensor_inputs = sinput->last + 1; +} + +static int of_parse_sensor(struct imxcam_dev *dev, + struct imxcam_sensor *sensor, + struct device_node *sink_ep, + struct device_node *csi_port, + struct device_node *sensor_node) +{ + struct device_node *sensor_ep, *csi_ep; + + sensor_ep = of_graph_get_next_endpoint(sensor_node, NULL); + if (!sensor_ep) + return -EINVAL; + csi_ep = of_get_next_child(csi_port, NULL); + if (!csi_ep) { + of_node_put(sensor_ep); + return -EINVAL; + } + + sensor->csi_np = csi_port; + + v4l2_of_parse_endpoint(sensor_ep, &sensor->ep); + v4l2_of_parse_endpoint(csi_ep, &sensor->csi_ep); + + of_parse_sensor_inputs(dev, sink_ep, sensor); + + of_node_put(sensor_ep); + of_node_put(csi_ep); + return 0; +} + +static struct v4l2_async_subdev *add_async_subdev(struct imxcam_dev *dev, + struct device_node *np) +{ + struct v4l2_async_subdev *asd; + int asd_idx; + + asd_idx = dev->subdev_notifier.num_subdevs; + if (asd_idx >= IMXCAM_MAX_SUBDEVS) + return ERR_PTR(-ENOSPC); + + asd = &dev->async_desc[asd_idx]; + dev->async_ptrs[asd_idx] = asd; + + asd->match_type = V4L2_ASYNC_MATCH_OF; + asd->match.of.node = np; + dev->subdev_notifier.num_subdevs++; + + dev_dbg(dev->dev, "%s: added %s, num %d, node %p\n", + __func__, np->name, dev->subdev_notifier.num_subdevs, np); + + return asd; +} + +/* Discover all the subdevices we need downstream from a sink endpoint */ +static int of_discover_subdevs(struct imxcam_dev *dev, + struct device_node *csi_port, + struct device_node *sink_ep, + int *vidmux_input) +{ + struct device_node *rpp, *epnode = NULL; + struct v4l2_async_subdev *asd; + struct imxcam_sensor *sensor; + int sensor_idx, num_sink_ports; + int i, vidmux_idx = -1, ret = 0; + + rpp = of_graph_get_remote_port_parent(sink_ep); + if (!rpp) + return 0; + if (!of_device_is_available(rpp)) + goto out; + + asd = add_async_subdev(dev, rpp); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out; + } + + if (of_device_is_compatible(rpp, "fsl,imx-mipi-csi2")) { + /* + * there is only one internal mipi receiver, so exit + * with 0 if we've already passed through here + */ + if (dev->csi2_asd) { + dev->subdev_notifier.num_subdevs--; + ret = 0; + goto out; + } + + /* the mipi csi2 receiver has only one sink port */ + num_sink_ports = 1; + dev->csi2_asd = asd; + dev_dbg(dev->dev, "found mipi-csi2 %s\n", rpp->name); + } else if (of_device_is_compatible(rpp, "imx-video-mux")) { + /* for the video mux, all but the last port are sinks */ + num_sink_ports = of_get_child_count(rpp) - 1; + + vidmux_idx = dev->num_vidmux; + if (vidmux_idx >= IMXCAM_MAX_VIDEOMUX) { + ret = -ENOSPC; + goto out; + } + + dev->vidmux_asd[vidmux_idx] = asd; + dev->num_vidmux++; + dev_dbg(dev->dev, "found video mux %s\n", rpp->name); + } else { + /* this rpp must be a sensor, it has no sink ports */ + num_sink_ports = 0; + + sensor_idx = dev->num_sensors; + if (sensor_idx >= IMXCAM_MAX_SENSORS) + return -ENOSPC; + + sensor = &dev->sensor_list[sensor_idx]; + + ret = of_parse_sensor(dev, sensor, sink_ep, csi_port, rpp); + if (ret) + goto out; + + /* + * save the input indeces of all video-muxes recorded in + * this pipeline path required to receive data from this + * sensor. + */ + memcpy(sensor->vidmux_input, vidmux_input, + sizeof(sensor->vidmux_input)); + + sensor->asd = asd; + dev->num_sensors++; + dev_dbg(dev->dev, "found sensor %s\n", rpp->name); + } + + /* continue discovery downstream */ + dev_dbg(dev->dev, "scanning %d sink ports on %s\n", + num_sink_ports, rpp->name); + + for (i = 0; i < num_sink_ports; i++) { + epnode = of_graph_get_next_endpoint(rpp, epnode); + if (!epnode) { + v4l2_err(&dev->v4l2_dev, + "no endpoint at port %d on %s\n", + i, rpp->name); + ret = -EINVAL; + break; + } + + if (vidmux_idx >= 0) + vidmux_input[vidmux_idx] = i; + + ret = of_discover_subdevs(dev, csi_port, epnode, vidmux_input); + of_node_put(epnode); + if (ret) + break; + } + +out: + of_node_put(rpp); + return ret; +} + +static int of_parse_ports(struct imxcam_dev *dev, struct device_node *np) +{ + struct device_node *port, *epnode; + struct v4l2_async_subdev *asd; + int vidmux_inputs[IMXCAM_MAX_VIDEOMUX]; + int i, j, csi_idx, ret = 0; + + for (i = 0; ; i++) { + port = of_parse_phandle(np, "ports", i); + if (!port) { + ret = 0; + break; + } + + csi_idx = dev->num_csi; + if (csi_idx >= IMXCAM_MAX_CSI) { + ret = -ENOSPC; + break; + } + /* register the CSI subdev */ + asd = add_async_subdev(dev, port); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + break; + } + dev->csi_asd[csi_idx] = asd; + dev->num_csi++; + + /* + * discover and register all async subdevs downstream + * from this CSI port. + */ + for_each_child_of_node(port, epnode) { + for (j = 0; j < IMXCAM_MAX_VIDEOMUX; j++) + vidmux_inputs[j] = -1; + + ret = of_discover_subdevs(dev, port, epnode, + vidmux_inputs); + of_node_put(epnode); + if (ret) + break; + } + + of_node_put(port); + if (ret) + break; + } + + if (ret) + return ret; + + if (!dev->num_sensors) { + v4l2_err(&dev->v4l2_dev, "no sensors found!\n"); + return -EINVAL; + } + + return 0; +} + +static int of_parse_fim(struct imxcam_dev *dev, struct device_node *np) +{ + struct imxcam_fim *fim = &dev->fim; + struct device_node *fim_np; + u32 val, tol[2], icap[2]; + int ret; + + fim_np = of_get_child_by_name(np, "fim"); + if (!fim_np) { + /* set to the default defaults */ + fim->of_defaults[FIM_CL_ENABLE] = FIM_CL_ENABLE_DEF; + fim->of_defaults[FIM_CL_NUM] = FIM_CL_NUM_DEF; + fim->of_defaults[FIM_CL_NUM_SKIP] = FIM_CL_NUM_SKIP_DEF; + fim->of_defaults[FIM_CL_TOLERANCE_MIN] = + FIM_CL_TOLERANCE_MIN_DEF; + fim->of_defaults[FIM_CL_TOLERANCE_MAX] = + FIM_CL_TOLERANCE_MAX_DEF; + fim->icap_channel = -1; + return 0; + } + + ret = of_property_read_u32(fim_np, "enable", &val); + if (ret) + val = FIM_CL_ENABLE_DEF; + fim->of_defaults[FIM_CL_ENABLE] = val; + + ret = of_property_read_u32(fim_np, "num-avg", &val); + if (ret) + val = FIM_CL_NUM_DEF; + fim->of_defaults[FIM_CL_NUM] = val; + + ret = of_property_read_u32(fim_np, "num-skip", &val); + if (ret) + val = FIM_CL_NUM_SKIP_DEF; + fim->of_defaults[FIM_CL_NUM_SKIP] = val; + + ret = of_property_read_u32_array(fim_np, "tolerance-range", tol, 2); + if (ret) { + tol[0] = FIM_CL_TOLERANCE_MIN_DEF; + tol[1] = FIM_CL_TOLERANCE_MAX_DEF; + } + fim->of_defaults[FIM_CL_TOLERANCE_MIN] = tol[0]; + fim->of_defaults[FIM_CL_TOLERANCE_MAX] = tol[1]; + + ret = of_property_read_u32_array(fim_np, "input-capture-channel", + icap, 2); + if (!ret) { + fim->icap_channel = icap[0]; + fim->icap_flags = icap[1]; + } else { + fim->icap_channel = -1; + } + + of_node_put(fim_np); + return 0; +} + +int imxcam_of_parse(struct imxcam_dev *dev, struct device_node *np) +{ + int ret = of_parse_fim(dev, np); + if (ret) + return ret; + + return of_parse_ports(dev, np); +} diff --git a/drivers/staging/media/imx/capture/imx-of.h b/drivers/staging/media/imx/capture/imx-of.h new file mode 100644 index 0000000..6f233bf --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-of.h @@ -0,0 +1,18 @@ +/* + * Video Camera Capture driver for Freescale i.MX5/6 SOC + * + * Open Firmware parsing. + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef _IMX_CAM_OF_H +#define _IMX_CAM_OF_H + +extern int imxcam_of_parse(struct imxcam_dev *dev, struct device_node *np); + +#endif diff --git a/drivers/staging/media/imx/capture/imx-smfc.c b/drivers/staging/media/imx/capture/imx-smfc.c new file mode 100644 index 0000000..4429c8e --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-smfc.c @@ -0,0 +1,505 @@ +/* + * V4L2 Capture SMFC Subdev for Freescale i.MX5/6 SOC + * + * This subdevice handles capture of raw/unconverted video frames + * from the CSI, directly to memory via the Sensor Multi-FIFO Controller. + * + * Copyright (c) 2012-2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pinctrl/consumer.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-of.h> +#include <media/v4l2-ctrls.h> +#include <video/imx-ipu-v3.h> +#include <media/imx.h> +#include "imx-camif.h" + +struct imx_smfc_priv { + struct imxcam_dev *dev; + struct v4l2_subdev sd; + + struct ipu_soc *ipu; + struct ipuv3_channel *smfc_ch; + struct ipu_smfc *smfc; + + struct v4l2_mbus_framefmt inf; /* input sensor format */ + struct v4l2_pix_format outf; /* output user format */ + + /* active (undergoing DMA) buffers, one for each IPU buffer */ + struct imxcam_buffer *active_frame[2]; + + struct imxcam_dma_buf underrun_buf; + int buf_num; + + struct timer_list eof_timeout_timer; + int eof_irq; + int nfb4eof_irq; + + bool last_eof; /* waiting for last EOF at stream off */ + struct completion last_eof_comp; +}; + +static void imx_smfc_put_ipu_resources(struct imx_smfc_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->smfc_ch)) + ipu_idmac_put(priv->smfc_ch); + priv->smfc_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->smfc)) + ipu_smfc_put(priv->smfc); + priv->smfc = NULL; +} + +static int imx_smfc_get_ipu_resources(struct imx_smfc_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + int csi_id = dev->sensor->csi_ep.base.port; + struct v4l2_subdev *csi_sd = dev->sensor->csi_sd; + int csi_ch_num, ret; + + priv->ipu = dev_get_drvdata(csi_sd->dev->parent); + + /* + * Choose the direct CSI-->SMFC-->MEM channel corresponding + * to the IPU and CSI IDs. + */ + csi_ch_num = IPUV3_CHANNEL_CSI0 + + (ipu_get_num(priv->ipu) << 1) + csi_id; + + priv->smfc = ipu_smfc_get(priv->ipu, csi_ch_num); + if (IS_ERR(priv->smfc)) { + v4l2_err(&priv->sd, "failed to get SMFC\n"); + ret = PTR_ERR(priv->smfc); + goto out; + } + + priv->smfc_ch = ipu_idmac_get(priv->ipu, csi_ch_num); + if (IS_ERR(priv->smfc_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + csi_ch_num); + ret = PTR_ERR(priv->smfc_ch); + goto out; + } + + return 0; +out: + imx_smfc_put_ipu_resources(priv); + return ret; +} + +static irqreturn_t imx_smfc_eof_interrupt(int irq, void *dev_id) +{ + struct imx_smfc_priv *priv = dev_id; + struct imxcam_dev *dev = priv->dev; + struct imxcam_ctx *ctx = dev->io_ctx; + struct imxcam_buffer *frame; + enum vb2_buffer_state state; + struct timeval cur_timeval; + unsigned long flags; + u64 cur_time_ns; + dma_addr_t phys; + + spin_lock_irqsave(&dev->irqlock, flags); + + cur_time_ns = ktime_get_ns(); + cur_timeval = ns_to_timeval(cur_time_ns); + + /* timestamp and return the completed frame */ + frame = priv->active_frame[priv->buf_num]; + if (frame) { + frame->vb.timestamp = cur_time_ns; + state = (dev->signal_locked && + !atomic_read(&dev->pending_restart)) ? + VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR; + vb2_buffer_done(&frame->vb, state); + } + + if (priv->last_eof) { + complete(&priv->last_eof_comp); + priv->active_frame[priv->buf_num] = NULL; + priv->last_eof = false; + goto unlock; + } + + if (dev->fim.eof && dev->fim.eof(dev, &cur_timeval)) + v4l2_subdev_notify(&priv->sd, IMXCAM_FRAME_INTERVAL_NOTIFY, + NULL); + + /* bump the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + + if (!list_empty(&ctx->ready_q)) { + frame = list_entry(ctx->ready_q.next, + struct imxcam_buffer, list); + phys = vb2_dma_contig_plane_dma_addr(&frame->vb, 0); + list_del(&frame->list); + priv->active_frame[priv->buf_num] = frame; + } else { + phys = priv->underrun_buf.phys; + priv->active_frame[priv->buf_num] = NULL; + } + + if (ipu_idmac_buffer_is_ready(priv->smfc_ch, priv->buf_num)) + ipu_idmac_clear_buffer(priv->smfc_ch, priv->buf_num); + + ipu_cpmem_set_buffer(priv->smfc_ch, priv->buf_num, phys); + ipu_idmac_select_buffer(priv->smfc_ch, priv->buf_num); + + priv->buf_num ^= 1; + +unlock: + spin_unlock_irqrestore(&dev->irqlock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t imx_smfc_nfb4eof_interrupt(int irq, void *dev_id) +{ + struct imx_smfc_priv *priv = dev_id; + + v4l2_err(&priv->sd, "NFB4EOF\n"); + + v4l2_subdev_notify(&priv->sd, IMXCAM_NFB4EOF_NOTIFY, NULL); + + return IRQ_HANDLED; +} + +/* + * EOF timeout timer function. + */ +static void imx_smfc_eof_timeout(unsigned long data) +{ + struct imx_smfc_priv *priv = (struct imx_smfc_priv *)data; + + v4l2_err(&priv->sd, "EOF timeout\n"); + + v4l2_subdev_notify(&priv->sd, IMXCAM_EOF_TIMEOUT_NOTIFY, NULL); +} + +static void imx_smfc_free_dma_buf(struct imx_smfc_priv *priv, + struct imxcam_dma_buf *buf) +{ + struct imxcam_dev *dev = priv->dev; + + if (buf->virt) + dma_free_coherent(dev->dev, buf->len, buf->virt, buf->phys); + + buf->virt = NULL; + buf->phys = 0; +} + +static int imx_smfc_alloc_dma_buf(struct imx_smfc_priv *priv, + struct imxcam_dma_buf *buf, + int size) +{ + struct imxcam_dev *dev = priv->dev; + + imx_smfc_free_dma_buf(priv, buf); + + buf->len = PAGE_ALIGN(size); + buf->virt = dma_alloc_coherent(dev->dev, buf->len, &buf->phys, + GFP_DMA | GFP_KERNEL); + if (!buf->virt) { + v4l2_err(&priv->sd, "failed to alloc dma buffer\n"); + return -ENOMEM; + } + + return 0; +} + +/* init the IC PRPENC-->MEM IDMAC channel */ +static void imx_smfc_setup_channel(struct imx_smfc_priv *priv, + dma_addr_t addr0, dma_addr_t addr1, + bool rot_swap_width_height) +{ + struct imxcam_dev *dev = priv->dev; + int csi_id = dev->sensor->csi_ep.base.port; + int vc_num = dev->sensor->csi_ep.base.id; + u32 width, height, stride; + unsigned int burst_size; + struct ipu_image image; + bool passthrough; + + width = priv->outf.width; + height = priv->outf.height; + + stride = dev->user_pixfmt->y_depth ? + (width * dev->user_pixfmt->y_depth) >> 3 : + (width * dev->user_pixfmt->bpp) >> 3; + + ipu_cpmem_zero(priv->smfc_ch); + + memset(&image, 0, sizeof(image)); + image.pix.width = image.rect.width = width; + image.pix.height = image.rect.height = height; + image.pix.bytesperline = stride; + image.pix.pixelformat = priv->outf.pixelformat; + image.phys0 = addr0; + image.phys1 = addr1; + ipu_cpmem_set_image(priv->smfc_ch, &image); + + burst_size = (width & 0xf) ? 8 : 16; + + ipu_cpmem_set_burstsize(priv->smfc_ch, burst_size); + + /* + * If the sensor uses 16-bit parallel CSI bus, we must handle + * the data internally in the IPU as 16-bit generic, aka + * passthrough mode. + */ + passthrough = (dev->sensor->ep.bus_type != V4L2_MBUS_CSI2 && + dev->sensor->ep.bus.parallel.bus_width >= 16); + + if (passthrough) + ipu_cpmem_set_format_passthrough(priv->smfc_ch, 16); + + if (dev->sensor->ep.bus_type == V4L2_MBUS_CSI2) + ipu_smfc_map_channel(priv->smfc, csi_id, vc_num); + else + ipu_smfc_map_channel(priv->smfc, csi_id, 0); + + /* + * Set the channel for the direct CSI-->memory via SMFC + * use-case to very high priority, by enabling the watermark + * signal in the SMFC, enabling WM in the channel, and setting + * the channel priority to high. + * + * Refer to the i.mx6 rev. D TRM Table 36-8: Calculated priority + * value. + * + * The WM's are set very low by intention here to ensure that + * the SMFC FIFOs do not overflow. + */ + ipu_smfc_set_watermark(priv->smfc, 0x02, 0x01); + ipu_cpmem_set_high_priority(priv->smfc_ch); + ipu_idmac_enable_watermark(priv->smfc_ch, true); + ipu_cpmem_set_axi_id(priv->smfc_ch, 0); + ipu_idmac_lock_enable(priv->smfc_ch, 8); + + burst_size = ipu_cpmem_get_burstsize(priv->smfc_ch); + burst_size = passthrough ? (burst_size >> 3) - 1 : (burst_size >> 2) - 1; + + ipu_smfc_set_burstsize(priv->smfc, burst_size); + + if (V4L2_FIELD_HAS_BOTH(priv->inf.field)) + ipu_cpmem_interlaced_scan(priv->smfc_ch, stride); + + ipu_idmac_set_double_buffer(priv->smfc_ch, true); +} + +static int imx_smfc_setup(struct imx_smfc_priv *priv, + dma_addr_t phys0, dma_addr_t phys1) +{ + int ret; + + ret = imx_smfc_alloc_dma_buf(priv, &priv->underrun_buf, + priv->outf.sizeimage); + if (ret) { + v4l2_err(&priv->sd, "failed to alloc underrun_buf, %d\n", ret); + return ret; + } + + imx_smfc_setup_channel(priv, phys0, phys1, false); + + ipu_cpmem_dump(priv->smfc_ch); + ipu_dump(priv->ipu); + + ipu_smfc_enable(priv->smfc); + + /* set buffers ready */ + ipu_idmac_select_buffer(priv->smfc_ch, 0); + ipu_idmac_select_buffer(priv->smfc_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->smfc_ch); + + return 0; +} + +static int imx_smfc_start(struct imx_smfc_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct imxcam_ctx *ctx = dev->io_ctx; + struct imxcam_buffer *frame, *tmp; + dma_addr_t phys[2] = {0}; + int i = 0, ret; + + ret = imx_smfc_get_ipu_resources(priv); + if (ret) + return ret; + + list_for_each_entry_safe(frame, tmp, &ctx->ready_q, list) { + phys[i] = vb2_dma_contig_plane_dma_addr(&frame->vb, 0); + list_del(&frame->list); + priv->active_frame[i++] = frame; + if (i >= 2) + break; + } + + priv->inf = dev->sensor_fmt; + priv->inf.width = dev->crop.width; + priv->inf.height = dev->crop.height; + priv->outf = dev->user_fmt.fmt.pix; + + priv->buf_num = 0; + + /* init EOF completion waitq */ + init_completion(&priv->last_eof_comp); + priv->last_eof = false; + + ret = imx_smfc_setup(priv, phys[0], phys[1]); + if (ret) + goto out_put_ipu; + + priv->nfb4eof_irq = ipu_idmac_channel_irq(priv->ipu, + priv->smfc_ch, + IPU_IRQ_NFB4EOF); + ret = devm_request_irq(dev->dev, priv->nfb4eof_irq, + imx_smfc_nfb4eof_interrupt, 0, + "imxcam-enc-nfb4eof", priv); + if (ret) { + v4l2_err(&priv->sd, + "Error registering encode NFB4EOF irq: %d\n", ret); + goto out_put_ipu; + } + + priv->eof_irq = ipu_idmac_channel_irq(priv->ipu, priv->smfc_ch, + IPU_IRQ_EOF); + + ret = devm_request_irq(dev->dev, priv->eof_irq, + imx_smfc_eof_interrupt, 0, + "imxcam-enc-eof", priv); + if (ret) { + v4l2_err(&priv->sd, + "Error registering encode eof irq: %d\n", ret); + goto out_free_nfb4eof_irq; + } + + /* sensor stream on */ + ret = dev->sensor_set_stream(dev, 1); + if (ret) { + v4l2_err(&priv->sd, "sensor stream on failed\n"); + goto out_free_eof_irq; + } + + /* start the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + + return 0; + +out_free_eof_irq: + devm_free_irq(dev->dev, priv->eof_irq, priv); +out_free_nfb4eof_irq: + devm_free_irq(dev->dev, priv->nfb4eof_irq, priv); +out_put_ipu: + imx_smfc_put_ipu_resources(priv); + for (i = 0; i < 2; i++) { + frame = priv->active_frame[i]; + vb2_buffer_done(&frame->vb, VB2_BUF_STATE_QUEUED); + } + return ret; +} + +static int imx_smfc_stop(struct imx_smfc_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct imxcam_buffer *frame; + unsigned long flags; + int i, ret; + + /* mark next EOF interrupt as the last before stream off */ + spin_lock_irqsave(&dev->irqlock, flags); + priv->last_eof = true; + spin_unlock_irqrestore(&dev->irqlock, flags); + + /* + * and then wait for interrupt handler to mark completion. + */ + ret = wait_for_completion_timeout(&priv->last_eof_comp, + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + if (ret == 0) + v4l2_warn(&priv->sd, "wait last encode EOF timeout\n"); + + /* sensor stream off */ + ret = dev->sensor_set_stream(dev, 0); + if (ret) + v4l2_warn(&priv->sd, "sensor stream off failed\n"); + + devm_free_irq(dev->dev, priv->eof_irq, priv); + devm_free_irq(dev->dev, priv->nfb4eof_irq, priv); + + ipu_idmac_disable_channel(priv->smfc_ch); + + ipu_smfc_disable(priv->smfc); + + imx_smfc_free_dma_buf(priv, &priv->underrun_buf); + + imx_smfc_put_ipu_resources(priv); + + /* cancel the EOF timeout timer */ + del_timer_sync(&priv->eof_timeout_timer); + + /* return any remaining active frames with error */ + for (i = 0; i < 2; i++) { + frame = priv->active_frame[i]; + if (frame && frame->vb.state == VB2_BUF_STATE_ACTIVE) { + frame->vb.timestamp = ktime_get_ns(); + vb2_buffer_done(&frame->vb, VB2_BUF_STATE_ERROR); + } + } + + return 0; +} + +static int imx_smfc_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx_smfc_priv *priv = v4l2_get_subdevdata(sd); + + return enable ? imx_smfc_start(priv) : imx_smfc_stop(priv); +} + +static struct v4l2_subdev_video_ops imx_smfc_video_ops = { + .s_stream = imx_smfc_s_stream, +}; + +static struct v4l2_subdev_ops imx_smfc_subdev_ops = { + .video = &imx_smfc_video_ops, +}; + +struct v4l2_subdev *imxcam_smfc_init(struct imxcam_dev *dev) +{ + struct imx_smfc_priv *priv; + + priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + init_timer(&priv->eof_timeout_timer); + priv->eof_timeout_timer.data = (unsigned long)priv; + priv->eof_timeout_timer.function = imx_smfc_eof_timeout; + + v4l2_subdev_init(&priv->sd, &imx_smfc_subdev_ops); + strlcpy(priv->sd.name, "imx-camera-smfc", sizeof(priv->sd.name)); + v4l2_set_subdevdata(&priv->sd, priv); + + priv->dev = dev; + return &priv->sd; +} diff --git a/drivers/staging/media/imx/capture/imx-vdic.c b/drivers/staging/media/imx/capture/imx-vdic.c new file mode 100644 index 0000000..3a73ef3 --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-vdic.c @@ -0,0 +1,994 @@ +/* + * V4L2 Capture Deinterlacer Subdev for Freescale i.MX5/6 SOC + * + * Copyright (c) 2014-2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/pinctrl/consumer.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-of.h> +#include <media/v4l2-ctrls.h> +#include <video/imx-ipu-v3.h> +#include <media/imx.h> +#include "imx-camif.h" + +/* + * This subdev implements two different video pipelines: + * + * CSI -> VDIC -> IC -> CH21 -> MEM + * + * In this pipeline, the CSI sends a single interlaced field F(n-1) + * directly to the VDIC (and optionally the following field F(n) + * can be sent to memory via IDMAC channel 13). So only two fields + * can be processed by the VDIC. This pipeline only works in VDIC's + * high motion mode, which only requires a single field for processing. + * The other motion modes (low and medium) require three fields, so this + * pipeline does not work in those modes. Also, it is not clear how this + * pipeline can deal with the various field orders (sequential BT/TB, + * interlaced BT/TB) and there are reported image quality issues output + * from the VDIC in this pipeline. + * + * CSI -> CH[0-3] -> MEM -> CH8,9,10 -> VDIC -> IC -> CH21 -> MEM + * + * In this pipeline, the CSI sends raw and full frames to memory buffers + * via the IDMAC SMFC channels 0-3. Fields from these frames are then + * transferred to the VDIC via IDMAC channels 8,9,10. The VDIC requires + * three fields: previous field F(n-1), current field F(n), and next + * field F(n+1), so we need three raw frames in memory: two completed frames + * to send F(n-1), F(n), F(n+1) to the VDIC, and a third frame for active + * CSI capture while the completed fields are sent through the VDIC->IC for + * processing. + * + * While the "direct" CSI->VDIC pipeline requires less memory bus bandwidth + * (just 1 channel vs. 5 channels for indirect pipeline), it can't be used + * for all motion modes, it only processes a single field (so half the + * original image resolution is lost), and it has the image quality issues + * mentioned above. With the indirect pipeline we have full control over + * field order. So by default the direct pipeline is disabled. Enable with + * the module param below, if enabled it will be used by high motion mode. + */ + +static int allow_direct; +module_param_named(direct, allow_direct, int, 0644); +MODULE_PARM_DESC(direct, "Allow CSI->VDIC direct pipeline (default: 0)"); + +struct vdic_priv; + +struct vdic_pipeline_ops { + int (*setup)(struct vdic_priv *priv); + void (*start)(struct vdic_priv *priv); + void (*stop)(struct vdic_priv *priv); + void (*disable)(struct vdic_priv *priv); +}; + +struct vdic_field_addr { + dma_addr_t prev; /* F(n-1) */ + dma_addr_t curr; /* F(n) */ + dma_addr_t next; /* F(n+1) */ +}; + +struct vdic_priv { + struct imxcam_dev *dev; + struct v4l2_subdev sd; + + /* IPU and its units we require */ + struct ipu_soc *ipu; + struct ipu_ic *ic_vf; + struct ipu_smfc *smfc; + struct ipu_vdi *vdi; + + struct ipuv3_channel *csi_ch; /* raw CSI frames channel */ + struct ipuv3_channel *vdi_in_ch_p; /* F(n-1) transfer channel */ + struct ipuv3_channel *vdi_in_ch; /* F(n) transfer channel */ + struct ipuv3_channel *vdi_in_ch_n; /* F(n+1) transfer channel */ + struct ipuv3_channel *prpvf_out_ch;/* final progressive frame channel */ + + /* pipeline operations */ + struct vdic_pipeline_ops *ops; + + /* active (undergoing DMA) buffers */ + struct imxcam_buffer *active_frame[2]; + struct imxcam_dma_buf underrun_buf; + int out_buf_num; + + /* + * Raw CSI frames for indirect pipeline, and the precalculated field + * addresses for each frame. The VDIC requires three fields: previous + * field F(n-1), current field F(n), and next field F(n+1), so we need + * three frames in memory: two completed frames to send F(n-1), F(n), + * F(n+1) to the VDIC, and a third frame for active CSI capture while + * the completed fields are sent through the VDIC->IC for processing. + */ + struct imxcam_dma_buf csi_frame[3]; + struct vdic_field_addr field[3]; + + int csi_frame_num; /* csi_frame index, 0-2 */ + int csi_buf_num; /* CSI channel double buffer index, 0-1 */ + + struct v4l2_mbus_framefmt inf; /* input sensor format */ + struct v4l2_pix_format outf; /* final output user format */ + enum ipu_color_space in_cs; /* input colorspace */ + enum ipu_color_space out_cs; /* output colorspace */ + u32 in_pixfmt; + + u32 in_stride; /* input and output line strides */ + u32 out_stride; + int field_size; /* 1/2 full image size */ + bool direct; /* using direct CSI->VDIC->IC pipeline */ + + struct timer_list eof_timeout_timer; + + int csi_eof_irq; /* CSI channel EOF IRQ */ + int nfb4eof_irq; /* CSI or PRPVF channel NFB4EOF IRQ */ + int out_eof_irq; /* PRPVF channel EOF IRQ */ + + bool last_eof; /* waiting for last EOF at vdic off */ + struct completion last_eof_comp; +}; + +static void vdic_put_ipu_resources(struct vdic_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->ic_vf)) + ipu_ic_put(priv->ic_vf); + priv->ic_vf = NULL; + + if (!IS_ERR_OR_NULL(priv->csi_ch)) + ipu_idmac_put(priv->csi_ch); + priv->csi_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->vdi_in_ch_p)) + ipu_idmac_put(priv->vdi_in_ch_p); + priv->vdi_in_ch_p = NULL; + + if (!IS_ERR_OR_NULL(priv->vdi_in_ch)) + ipu_idmac_put(priv->vdi_in_ch); + priv->vdi_in_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->vdi_in_ch_n)) + ipu_idmac_put(priv->vdi_in_ch_n); + priv->vdi_in_ch_n = NULL; + + if (!IS_ERR_OR_NULL(priv->prpvf_out_ch)) + ipu_idmac_put(priv->prpvf_out_ch); + priv->prpvf_out_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->vdi)) + ipu_vdi_put(priv->vdi); + priv->vdi = NULL; + + if (!IS_ERR_OR_NULL(priv->smfc)) + ipu_smfc_put(priv->smfc); + priv->smfc = NULL; +} + +static int vdic_get_ipu_resources(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + int csi_id = dev->sensor->csi_ep.base.port; + struct v4l2_subdev *csi_sd = dev->sensor->csi_sd; + int ret, err_chan; + + priv->ipu = dev_get_drvdata(csi_sd->dev->parent); + + priv->ic_vf = ipu_ic_get(priv->ipu, IC_TASK_VIEWFINDER); + if (IS_ERR(priv->ic_vf)) { + v4l2_err(&priv->sd, "failed to get IC VF\n"); + ret = PTR_ERR(priv->ic_vf); + goto out; + } + + priv->vdi = ipu_vdi_get(priv->ipu); + if (IS_ERR(priv->vdi)) { + v4l2_err(&priv->sd, "failed to get VDIC\n"); + ret = PTR_ERR(priv->vdi); + goto out; + } + + priv->prpvf_out_ch = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_IC_PRP_VF_MEM); + if (IS_ERR(priv->prpvf_out_ch)) { + err_chan = IPUV3_CHANNEL_IC_PRP_VF_MEM; + ret = PTR_ERR(priv->prpvf_out_ch); + goto out_err_chan; + } + + if (!priv->direct) { + /* + * Choose the CSI-->SMFC-->MEM channel corresponding + * to the IPU and CSI IDs. + */ + int csi_ch_num = IPUV3_CHANNEL_CSI0 + + (ipu_get_num(priv->ipu) << 1) + csi_id; + + priv->csi_ch = ipu_idmac_get(priv->ipu, csi_ch_num); + if (IS_ERR(priv->csi_ch)) { + err_chan = csi_ch_num; + ret = PTR_ERR(priv->csi_ch); + goto out_err_chan; + } + + priv->smfc = ipu_smfc_get(priv->ipu, csi_ch_num); + if (IS_ERR(priv->smfc)) { + v4l2_err(&priv->sd, "failed to get SMFC\n"); + ret = PTR_ERR(priv->smfc); + goto out; + } + + priv->vdi_in_ch_p = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_MEM_VDI_P); + if (IS_ERR(priv->vdi_in_ch_p)) { + err_chan = IPUV3_CHANNEL_MEM_VDI_P; + ret = PTR_ERR(priv->vdi_in_ch_p); + goto out_err_chan; + } + + priv->vdi_in_ch = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_MEM_VDI); + if (IS_ERR(priv->vdi_in_ch)) { + err_chan = IPUV3_CHANNEL_MEM_VDI; + ret = PTR_ERR(priv->vdi_in_ch); + goto out_err_chan; + } + + priv->vdi_in_ch_n = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_MEM_VDI_N); + if (IS_ERR(priv->vdi_in_ch_n)) { + err_chan = IPUV3_CHANNEL_MEM_VDI_N; + ret = PTR_ERR(priv->vdi_in_ch_n); + goto out_err_chan; + } + } + + return 0; + +out_err_chan: + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", err_chan); +out: + vdic_put_ipu_resources(priv); + return ret; +} + +static void prepare_csi_buffer(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + int next_frame, curr_frame; + + curr_frame = priv->csi_frame_num; + next_frame = (curr_frame + 2) % 3; + + dev_dbg(dev->dev, "%d - %d %d\n", + priv->csi_buf_num, curr_frame, next_frame); + + ipu_cpmem_set_buffer(priv->csi_ch, priv->csi_buf_num, + priv->csi_frame[next_frame].phys); + ipu_idmac_select_buffer(priv->csi_ch, priv->csi_buf_num); +} + +static void prepare_vdi_in_buffers(struct vdic_priv *priv) +{ + int last_frame, curr_frame; + + curr_frame = priv->csi_frame_num; + last_frame = curr_frame - 1; + if (last_frame < 0) + last_frame = 2; + + ipu_cpmem_set_buffer(priv->vdi_in_ch_p, 0, + priv->field[last_frame].prev); + ipu_cpmem_set_buffer(priv->vdi_in_ch, 0, + priv->field[curr_frame].curr); + ipu_cpmem_set_buffer(priv->vdi_in_ch_n, 0, + priv->field[curr_frame].next); + + ipu_idmac_select_buffer(priv->vdi_in_ch_p, 0); + ipu_idmac_select_buffer(priv->vdi_in_ch, 0); + ipu_idmac_select_buffer(priv->vdi_in_ch_n, 0); +} + +static void prepare_prpvf_out_buffer(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct imxcam_ctx *ctx = dev->io_ctx; + struct imxcam_buffer *frame; + dma_addr_t phys; + + if (!list_empty(&ctx->ready_q)) { + frame = list_entry(ctx->ready_q.next, + struct imxcam_buffer, list); + phys = vb2_dma_contig_plane_dma_addr(&frame->vb, 0); + list_del(&frame->list); + priv->active_frame[priv->out_buf_num] = frame; + } else { + phys = priv->underrun_buf.phys; + priv->active_frame[priv->out_buf_num] = NULL; + } + + ipu_cpmem_set_buffer(priv->prpvf_out_ch, priv->out_buf_num, phys); + ipu_idmac_select_buffer(priv->prpvf_out_ch, priv->out_buf_num); +} + +/* prpvf_out_ch EOF interrupt (progressive frame ready) */ +static irqreturn_t prpvf_out_eof_interrupt(int irq, void *dev_id) +{ + struct vdic_priv *priv = dev_id; + struct imxcam_dev *dev = priv->dev; + struct imxcam_buffer *frame; + enum vb2_buffer_state state; + struct timeval cur_timeval; + u64 cur_time_ns; + unsigned long flags; + + spin_lock_irqsave(&dev->irqlock, flags); + + cur_time_ns = ktime_get_ns(); + cur_timeval = ns_to_timeval(cur_time_ns); + + /* timestamp and return the completed frame */ + frame = priv->active_frame[priv->out_buf_num]; + if (frame) { + frame->vb.timestamp = cur_time_ns; + state = (dev->signal_locked && + !atomic_read(&dev->pending_restart)) ? + VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR; + vb2_buffer_done(&frame->vb, state); + } + + if (!priv->direct) + goto flip; + + if (priv->last_eof) { + complete(&priv->last_eof_comp); + priv->active_frame[priv->out_buf_num] = NULL; + priv->last_eof = false; + goto unlock; + } + + /* bump the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + + prepare_prpvf_out_buffer(priv); + +flip: + priv->out_buf_num ^= 1; + + if (dev->fim.eof && dev->fim.eof(dev, &cur_timeval)) + v4l2_subdev_notify(&priv->sd, IMXCAM_FRAME_INTERVAL_NOTIFY, + NULL); +unlock: + spin_unlock_irqrestore(&dev->irqlock, flags); + return IRQ_HANDLED; +} + +/* csi_ch EOF interrupt */ +static irqreturn_t csi_eof_interrupt(int irq, void *dev_id) +{ + struct vdic_priv *priv = dev_id; + struct imxcam_dev *dev = priv->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->irqlock, flags); + + if (priv->last_eof) { + complete(&priv->last_eof_comp); + priv->active_frame[priv->out_buf_num] = NULL; + priv->last_eof = false; + goto unlock; + } + + /* bump the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + + /* prepare next buffers */ + prepare_csi_buffer(priv); + prepare_prpvf_out_buffer(priv); + prepare_vdi_in_buffers(priv); + + /* increment double-buffer index and frame index */ + priv->csi_buf_num ^= 1; + priv->csi_frame_num = (priv->csi_frame_num + 1) % 3; + +unlock: + spin_unlock_irqrestore(&dev->irqlock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t nfb4eof_interrupt(int irq, void *dev_id) +{ + struct vdic_priv *priv = dev_id; + + v4l2_err(&priv->sd, "NFB4EOF\n"); + + v4l2_subdev_notify(&priv->sd, IMXCAM_NFB4EOF_NOTIFY, NULL); + + return IRQ_HANDLED; +} + +/* + * EOF timeout timer function. + */ +static void vdic_eof_timeout(unsigned long data) +{ + struct vdic_priv *priv = (struct vdic_priv *)data; + + v4l2_err(&priv->sd, "EOF timeout\n"); + + v4l2_subdev_notify(&priv->sd, IMXCAM_EOF_TIMEOUT_NOTIFY, NULL); +} + +static void vdic_free_dma_buf(struct vdic_priv *priv, + struct imxcam_dma_buf *buf) +{ + struct imxcam_dev *dev = priv->dev; + + if (buf->virt) + dma_free_coherent(dev->dev, buf->len, buf->virt, buf->phys); + + buf->virt = NULL; + buf->phys = 0; +} + +static int vdic_alloc_dma_buf(struct vdic_priv *priv, + struct imxcam_dma_buf *buf, + int size) +{ + struct imxcam_dev *dev = priv->dev; + + vdic_free_dma_buf(priv, buf); + + buf->len = PAGE_ALIGN(size); + buf->virt = dma_alloc_coherent(dev->dev, buf->len, &buf->phys, + GFP_DMA | GFP_KERNEL); + if (!buf->virt) { + v4l2_err(&priv->sd, "failed to alloc dma buffer\n"); + return -ENOMEM; + } + + return 0; +} + +static void setup_csi_channel(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct imxcam_sensor *sensor = dev->sensor; + struct ipuv3_channel *channel = priv->csi_ch; + struct v4l2_mbus_framefmt *inf = &priv->inf; + int csi_id = sensor->csi_ep.base.port; + int vc_num = sensor->csi_ep.base.id; + unsigned int burst_size; + struct ipu_image image; + bool passthrough; + + ipu_cpmem_zero(channel); + + memset(&image, 0, sizeof(image)); + image.pix.width = image.rect.width = inf->width; + image.pix.height = image.rect.height = inf->height; + image.pix.bytesperline = priv->in_stride; + image.pix.pixelformat = priv->in_pixfmt; + image.phys0 = priv->csi_frame[0].phys; + image.phys1 = priv->csi_frame[1].phys; + ipu_cpmem_set_image(channel, &image); + + burst_size = (inf->width & 0xf) ? 8 : 16; + + ipu_cpmem_set_burstsize(channel, burst_size); + + /* + * If the sensor uses 16-bit parallel CSI bus, we must handle + * the data internally in the IPU as 16-bit generic, aka + * passthrough mode. + */ + passthrough = (sensor->ep.bus_type != V4L2_MBUS_CSI2 && + sensor->ep.bus.parallel.bus_width >= 16); + + if (passthrough) + ipu_cpmem_set_format_passthrough(channel, 16); + + if (sensor->ep.bus_type == V4L2_MBUS_CSI2) + ipu_smfc_map_channel(priv->smfc, csi_id, vc_num); + else + ipu_smfc_map_channel(priv->smfc, csi_id, 0); + + /* + * Set the channel for the direct CSI-->memory via SMFC + * use-case to very high priority, by enabling the watermark + * signal in the SMFC, enabling WM in the channel, and setting + * the channel priority to high. + * + * Refer to the i.mx6 rev. D TRM Table 36-8: Calculated priority + * value. + * + * The WM's are set very low by intention here to ensure that + * the SMFC FIFOs do not overflow. + */ + ipu_smfc_set_watermark(priv->smfc, 0x02, 0x01); + ipu_cpmem_set_high_priority(channel); + ipu_idmac_enable_watermark(channel, true); + ipu_cpmem_set_axi_id(channel, 0); + ipu_idmac_lock_enable(channel, 8); + + burst_size = ipu_cpmem_get_burstsize(channel); + burst_size = passthrough ? + (burst_size >> 3) - 1 : (burst_size >> 2) - 1; + + ipu_smfc_set_burstsize(priv->smfc, burst_size); + + ipu_idmac_set_double_buffer(channel, true); +} + +static void setup_vdi_channel(struct vdic_priv *priv, + struct ipuv3_channel *channel, + dma_addr_t phys0, dma_addr_t phys1, + bool out_chan) +{ + u32 stride, width, height, pixfmt; + unsigned int burst_size; + struct ipu_image image; + + if (out_chan) { + width = priv->outf.width; + height = priv->outf.height; + pixfmt = priv->outf.pixelformat; + stride = priv->out_stride; + } else { + width = priv->inf.width; + height = priv->inf.height / 2; + pixfmt = priv->in_pixfmt; + stride = priv->in_stride; + } + + ipu_cpmem_zero(channel); + + memset(&image, 0, sizeof(image)); + image.pix.width = image.rect.width = width; + image.pix.height = image.rect.height = height; + image.pix.bytesperline = stride; + image.pix.pixelformat = pixfmt; + image.phys0 = phys0; + image.phys1 = phys1; + ipu_cpmem_set_image(channel, &image); + + burst_size = (width & 0xf) ? 8 : 16; + + ipu_cpmem_set_burstsize(channel, burst_size); + + if (out_chan) + ipu_ic_task_idma_init(priv->ic_vf, channel, width, height, + burst_size, IPU_ROTATE_NONE); + + ipu_cpmem_set_axi_id(channel, 1); + + ipu_idmac_set_double_buffer(channel, out_chan); +} + +static int vdic_setup_direct(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct imxcam_ctx *ctx = dev->io_ctx; + struct imxcam_buffer *frame, *tmp; + dma_addr_t phys[2] = {0}; + int i = 0; + + priv->out_buf_num = 0; + + list_for_each_entry_safe(frame, tmp, &ctx->ready_q, list) { + phys[i] = vb2_dma_contig_plane_dma_addr(&frame->vb, 0); + list_del(&frame->list); + priv->active_frame[i++] = frame; + if (i >= 2) + break; + } + + /* init the prpvf out channel */ + setup_vdi_channel(priv, priv->prpvf_out_ch, phys[0], phys[1], true); + + return 0; +} + +static void vdic_start_direct(struct vdic_priv *priv) +{ + /* set buffers ready */ + ipu_idmac_select_buffer(priv->prpvf_out_ch, 0); + ipu_idmac_select_buffer(priv->prpvf_out_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->prpvf_out_ch); +} + +static void vdic_stop_direct(struct vdic_priv *priv) +{ + ipu_idmac_disable_channel(priv->prpvf_out_ch); +} + +static void vdic_disable_direct(struct vdic_priv *priv) +{ + /* nothing to do */ +} + +static int vdic_setup_indirect(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct vdic_field_addr *field; + struct imxcam_dma_buf *frame; + int ret, in_size, i; + + /* + * FIXME: following in_size calc would not be correct for planar pixel + * formats, but all mbus pixel codes are packed formats, so so far this + * is OK. + */ + in_size = priv->in_stride * priv->inf.height; + + priv->csi_buf_num = priv->csi_frame_num = priv->out_buf_num = 0; + priv->field_size = in_size / 2; + + /* request EOF irq for vdi out channel */ + priv->csi_eof_irq = ipu_idmac_channel_irq(priv->ipu, + priv->csi_ch, + IPU_IRQ_EOF); + ret = devm_request_irq(dev->dev, priv->csi_eof_irq, + csi_eof_interrupt, 0, + "imxcam-csi-eof", priv); + if (ret) { + v4l2_err(&priv->sd, "Error registering CSI eof irq: %d\n", + ret); + return ret; + } + + for (i = 0; i < 3; i++) { + frame = &priv->csi_frame[i]; + + ret = vdic_alloc_dma_buf(priv, frame, in_size); + if (ret) { + v4l2_err(&priv->sd, + "failed to alloc csi_frame[%d], %d\n", i, ret); + while (--i >= 0) + vdic_free_dma_buf(priv, &priv->csi_frame[i]); + goto out_free_irq; + } + + /* precalculate the field addresses for this frame */ + field = &priv->field[i]; + switch (priv->inf.field) { + case V4L2_FIELD_SEQ_TB: + field->prev = frame->phys + priv->field_size; + field->curr = frame->phys; + field->next = frame->phys + priv->field_size; + break; + case V4L2_FIELD_SEQ_BT: + field->prev = frame->phys; + field->curr = frame->phys + priv->field_size; + field->next = frame->phys; + break; + case V4L2_FIELD_INTERLACED_BT: + field->prev = frame->phys; + field->curr = frame->phys + priv->in_stride; + field->next = frame->phys; + break; + default: + /* assume V4L2_FIELD_INTERLACED_TB */ + field->prev = frame->phys + priv->in_stride; + field->curr = frame->phys; + field->next = frame->phys + priv->in_stride; + break; + } + } + + priv->active_frame[0] = priv->active_frame[1] = NULL; + + /* init the CSI channel */ + setup_csi_channel(priv); + + /* init the vdi-in channels */ + setup_vdi_channel(priv, priv->vdi_in_ch_p, 0, 0, false); + setup_vdi_channel(priv, priv->vdi_in_ch, 0, 0, false); + setup_vdi_channel(priv, priv->vdi_in_ch_n, 0, 0, false); + + /* init the prpvf out channel */ + setup_vdi_channel(priv, priv->prpvf_out_ch, 0, 0, true); + + return 0; + +out_free_irq: + devm_free_irq(dev->dev, priv->csi_eof_irq, priv); + return ret; +} + +static void vdic_start_indirect(struct vdic_priv *priv) +{ + int i; + + /* set buffers ready */ + for (i = 0; i < 2; i++) + ipu_idmac_select_buffer(priv->csi_ch, i); + + /* enable SMFC */ + ipu_smfc_enable(priv->smfc); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->csi_ch); + ipu_idmac_enable_channel(priv->prpvf_out_ch); + ipu_idmac_enable_channel(priv->vdi_in_ch_p); + ipu_idmac_enable_channel(priv->vdi_in_ch); + ipu_idmac_enable_channel(priv->vdi_in_ch_n); +} + +static void vdic_stop_indirect(struct vdic_priv *priv) +{ + /* disable channels */ + ipu_idmac_disable_channel(priv->prpvf_out_ch); + ipu_idmac_disable_channel(priv->vdi_in_ch_p); + ipu_idmac_disable_channel(priv->vdi_in_ch); + ipu_idmac_disable_channel(priv->vdi_in_ch_n); + ipu_idmac_disable_channel(priv->csi_ch); + + /* disable SMFC */ + ipu_smfc_disable(priv->smfc); +} + +static void vdic_disable_indirect(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + int i; + + devm_free_irq(dev->dev, priv->csi_eof_irq, priv); + + for (i = 0; i < 3; i++) + vdic_free_dma_buf(priv, &priv->csi_frame[i]); +} + +static struct vdic_pipeline_ops direct_ops = { + .setup = vdic_setup_direct, + .start = vdic_start_direct, + .stop = vdic_stop_direct, + .disable = vdic_disable_direct, +}; + +static struct vdic_pipeline_ops indirect_ops = { + .setup = vdic_setup_indirect, + .start = vdic_start_indirect, + .stop = vdic_stop_indirect, + .disable = vdic_disable_indirect, +}; + +static int vdic_start(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + int csi_id = dev->sensor->csi_ep.base.port; + struct imxcam_buffer *frame; + int i, ret; + + priv->direct = (allow_direct && dev->motion == HIGH_MOTION); + /* this info is needed by CSI subdev for destination routing */ + dev->vdic_direct = priv->direct; + + priv->ops = priv->direct ? &direct_ops : &indirect_ops; + + ret = vdic_get_ipu_resources(priv); + if (ret) + return ret; + + priv->inf = dev->sensor_fmt; + priv->in_pixfmt = dev->sensor_pixfmt->fourcc; + priv->inf.width = dev->crop.width; + priv->inf.height = dev->crop.height; + priv->in_stride = dev->sensor_pixfmt->y_depth ? + (priv->inf.width * dev->sensor_pixfmt->y_depth) >> 3 : + (priv->inf.width * dev->sensor_pixfmt->bpp) >> 3; + priv->in_cs = ipu_mbus_code_to_colorspace(priv->inf.code); + + priv->outf = dev->user_fmt.fmt.pix; + priv->out_cs = ipu_pixelformat_to_colorspace(priv->outf.pixelformat); + priv->out_stride = dev->user_pixfmt->y_depth ? + (priv->outf.width * dev->user_pixfmt->y_depth) >> 3 : + (priv->outf.width * dev->user_pixfmt->bpp) >> 3; + + /* set IC to receive from VDIC */ + ipu_ic_set_src(priv->ic_vf, csi_id, true); + + /* + * set VDIC to receive from CSI for direct path, and memory + * for indirect. + */ + ipu_vdi_set_src(priv->vdi, priv->direct); + + ret = vdic_alloc_dma_buf(priv, &priv->underrun_buf, + priv->outf.sizeimage); + if (ret) { + v4l2_err(&priv->sd, "failed to alloc underrun_buf, %d\n", ret); + goto out_put_ipu; + } + + /* init EOF completion waitq */ + init_completion(&priv->last_eof_comp); + priv->last_eof = false; + + /* request EOF irq for prpvf out channel */ + priv->out_eof_irq = ipu_idmac_channel_irq(priv->ipu, + priv->prpvf_out_ch, + IPU_IRQ_EOF); + ret = devm_request_irq(dev->dev, priv->out_eof_irq, + prpvf_out_eof_interrupt, 0, + "imxcam-prpvf-out-eof", priv); + if (ret) { + v4l2_err(&priv->sd, + "Error registering prpvf out eof irq: %d\n", ret); + goto out_free_underrun; + } + + /* request NFB4EOF irq */ + priv->nfb4eof_irq = ipu_idmac_channel_irq(priv->ipu, priv->direct ? + priv->prpvf_out_ch : + priv->csi_ch, + IPU_IRQ_NFB4EOF); + ret = devm_request_irq(dev->dev, priv->nfb4eof_irq, + nfb4eof_interrupt, 0, + "imxcam-vdic-nfb4eof", priv); + if (ret) { + v4l2_err(&priv->sd, + "Error registering NFB4EOF irq: %d\n", ret); + goto out_free_eof_irq; + } + + /* init the VDIC */ + ipu_vdi_setup(priv->vdi, priv->inf.code, + priv->inf.width, priv->inf.height, priv->inf.field, + dev->motion); + + ret = ipu_ic_task_init(priv->ic_vf, + priv->inf.width, priv->inf.height, + priv->outf.width, priv->outf.height, + priv->in_cs, priv->out_cs); + if (ret) { + v4l2_err(&priv->sd, "ipu_ic_task_init failed, %d\n", ret); + goto out_free_nfb4eof_irq; + } + + ret = priv->ops->setup(priv); + if (ret) + goto out_free_nfb4eof_irq; + + ipu_vdi_enable(priv->vdi); + ipu_ic_enable(priv->ic_vf); + + priv->ops->start(priv); + + /* enable the IC VF task */ + ipu_ic_task_enable(priv->ic_vf); + + /* sensor stream on */ + ret = dev->sensor_set_stream(dev, 1); + if (ret) { + v4l2_err(&priv->sd, "sensor stream on failed\n"); + goto out_stop; + } + + /* start the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + + return 0; + +out_stop: + ipu_ic_task_disable(priv->ic_vf); + priv->ops->stop(priv); + ipu_ic_disable(priv->ic_vf); + ipu_vdi_disable(priv->vdi); + priv->ops->disable(priv); +out_free_nfb4eof_irq: + devm_free_irq(dev->dev, priv->nfb4eof_irq, priv); +out_free_eof_irq: + devm_free_irq(dev->dev, priv->out_eof_irq, priv); +out_free_underrun: + vdic_free_dma_buf(priv, &priv->underrun_buf); +out_put_ipu: + vdic_put_ipu_resources(priv); + for (i = 0; i < 2; i++) { + frame = priv->active_frame[i]; + if (frame) + vb2_buffer_done(&frame->vb, VB2_BUF_STATE_QUEUED); + } + return ret; +} + +static int vdic_stop(struct vdic_priv *priv) +{ + struct imxcam_dev *dev = priv->dev; + struct imxcam_buffer *frame; + unsigned long flags; + int i, ret; + + /* mark next EOF interrupt as the last before vdic off */ + spin_lock_irqsave(&dev->irqlock, flags); + priv->last_eof = true; + spin_unlock_irqrestore(&dev->irqlock, flags); + + /* + * and then wait for interrupt handler to mark completion. + */ + ret = wait_for_completion_timeout(&priv->last_eof_comp, + msecs_to_jiffies(IMXCAM_EOF_TIMEOUT)); + if (ret == 0) + v4l2_warn(&priv->sd, "wait last encode EOF timeout\n"); + + /* sensor stream off */ + ret = dev->sensor_set_stream(dev, 0); + if (ret) + v4l2_warn(&priv->sd, "sensor stream off failed\n"); + + ipu_ic_task_disable(priv->ic_vf); + priv->ops->stop(priv); + ipu_ic_disable(priv->ic_vf); + ipu_vdi_disable(priv->vdi); + priv->ops->disable(priv); + devm_free_irq(dev->dev, priv->nfb4eof_irq, priv); + devm_free_irq(dev->dev, priv->out_eof_irq, priv); + vdic_free_dma_buf(priv, &priv->underrun_buf); + vdic_put_ipu_resources(priv); + + /* cancel the EOF timeout timer */ + del_timer_sync(&priv->eof_timeout_timer); + + /* return any remaining active frames with error */ + for (i = 0; i < 2; i++) { + frame = priv->active_frame[i]; + if (frame && frame->vb.state == VB2_BUF_STATE_ACTIVE) { + frame->vb.timestamp = ktime_get_ns(); + vb2_buffer_done(&frame->vb, VB2_BUF_STATE_ERROR); + } + } + + return 0; +} + +static int vdic_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + + return enable ? vdic_start(priv) : vdic_stop(priv); +} + +static struct v4l2_subdev_video_ops vdic_video_ops = { + .s_stream = vdic_s_stream, +}; + +static struct v4l2_subdev_ops vdic_subdev_ops = { + .video = &vdic_video_ops, +}; + +struct v4l2_subdev *imxcam_vdic_init(struct imxcam_dev *dev) +{ + struct vdic_priv *priv; + + priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + init_timer(&priv->eof_timeout_timer); + priv->eof_timeout_timer.data = (unsigned long)priv; + priv->eof_timeout_timer.function = vdic_eof_timeout; + + v4l2_subdev_init(&priv->sd, &vdic_subdev_ops); + strlcpy(priv->sd.name, "imx-camera-vdic", sizeof(priv->sd.name)); + v4l2_set_subdevdata(&priv->sd, priv); + + priv->dev = dev; + return &priv->sd; +} diff --git a/include/media/imx.h b/include/media/imx.h new file mode 100644 index 0000000..5025a72 --- /dev/null +++ b/include/media/imx.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2014-2015 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the + * License, or (at your option) any later version + */ + +#ifndef __MEDIA_IMX_H__ +#define __MEDIA_IMX_H__ + +#include <uapi/media/imx.h> + +#endif diff --git a/include/uapi/Kbuild b/include/uapi/Kbuild index 245aa6e..9a51957 100644 --- a/include/uapi/Kbuild +++ b/include/uapi/Kbuild @@ -6,6 +6,7 @@ header-y += asm-generic/ header-y += linux/ header-y += sound/ +header-y += media/ header-y += mtd/ header-y += rdma/ header-y += video/ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index b6a357a..9343950 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -180,6 +180,10 @@ enum v4l2_colorfx { * We reserve 16 controls for this driver. */ #define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080) +/* The base for the imx driver controls. + * We reserve 16 controls for this driver. */ +#define V4L2_CID_USER_IMX_BASE (V4L2_CID_USER_BASE + 0x1090) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ diff --git a/include/uapi/media/Kbuild b/include/uapi/media/Kbuild new file mode 100644 index 0000000..fa78958 --- /dev/null +++ b/include/uapi/media/Kbuild @@ -0,0 +1,2 @@ +# UAPI Header export list +header-y += imx.h diff --git a/include/uapi/media/imx.h b/include/uapi/media/imx.h new file mode 100644 index 0000000..de1447c --- /dev/null +++ b/include/uapi/media/imx.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2015 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the + * License, or (at your option) any later version + */ + +#ifndef __UAPI_MEDIA_IMX_H__ +#define __UAPI_MEDIA_IMX_H__ + +enum imx_ctrl_id { + V4L2_CID_IMX_MOTION = (V4L2_CID_USER_IMX_BASE + 0), + V4L2_CID_IMX_FIM_ENABLE, + V4L2_CID_IMX_FIM_NUM, + V4L2_CID_IMX_FIM_TOLERANCE_MIN, + V4L2_CID_IMX_FIM_TOLERANCE_MAX, + V4L2_CID_IMX_FIM_NUM_SKIP, +}; + +#endif -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html