This is a V4L2 camera interface driver for i.MX6. See Documentation/video4linux/mx6_camera.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/video4linux/mx6_camera.txt | 188 ++ drivers/staging/media/Kconfig | 2 + drivers/staging/media/Makefile | 1 + drivers/staging/media/imx6/Kconfig | 25 + drivers/staging/media/imx6/Makefile | 1 + drivers/staging/media/imx6/capture/Kconfig | 11 + drivers/staging/media/imx6/capture/Makefile | 4 + drivers/staging/media/imx6/capture/mipi-csi2.c | 322 ++++ drivers/staging/media/imx6/capture/mx6-camif.c | 2235 ++++++++++++++++++++++ drivers/staging/media/imx6/capture/mx6-camif.h | 197 ++ drivers/staging/media/imx6/capture/mx6-encode.c | 775 ++++++++ drivers/staging/media/imx6/capture/mx6-preview.c | 748 ++++++++ include/media/imx6.h | 18 + 13 files changed, 4527 insertions(+) create mode 100644 Documentation/video4linux/mx6_camera.txt create mode 100644 drivers/staging/media/imx6/Kconfig create mode 100644 drivers/staging/media/imx6/Makefile create mode 100644 drivers/staging/media/imx6/capture/Kconfig create mode 100644 drivers/staging/media/imx6/capture/Makefile create mode 100644 drivers/staging/media/imx6/capture/mipi-csi2.c create mode 100644 drivers/staging/media/imx6/capture/mx6-camif.c create mode 100644 drivers/staging/media/imx6/capture/mx6-camif.h create mode 100644 drivers/staging/media/imx6/capture/mx6-encode.c create mode 100644 drivers/staging/media/imx6/capture/mx6-preview.c create mode 100644 include/media/imx6.h diff --git a/Documentation/video4linux/mx6_camera.txt b/Documentation/video4linux/mx6_camera.txt new file mode 100644 index 0000000..d712f91 --- /dev/null +++ b/Documentation/video4linux/mx6_camera.txt @@ -0,0 +1,188 @@ + i.MX6 Video Capture Driver + ========================== + +Introduction +------------ + +The Freescale i.MX6 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.MX6 reference +manuals listed under References. + + +Features +-------- + +Some of the features of this driver include: + +- Supports parallel, BT.565, and MIPI CSI-2 interfaces. + +- Camera Preview mode. + +- 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. + +- Separate rotation control for both streaming and preview. Streaming + rotation control is via main video device node, and preview rotation + control via subdevice node. + +- Scaling and color-space conversion for both streaming and preview. + +- 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. + +- De-interlacing is supported via simple even/odd line interleaving + which is a facility of the IDMAC. The VDIC is not yet supported, but + plans are in the works to allow more advanced motion compensation. + + + +Usage Notes +----------- + +The i.MX6 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 + + +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 + + +Preview Notes +------------- + +Preview accepts a framebuffer physaddr via standard VIDIOC_S_FBUF. The +driver is agnostic about the source of this framebuffer, it could come +from a DRM-based background or overlay plane, or from legacy fbdev. + +Preview is implemented as a sub-device, and exports controls to +allow preview horizontal/vertical flip and rotation settings independent +of the same settings for streaming. These controls are available on +/dev/v4l-subdev0. + + +Known Issues +------------ + +There is one currently known issue. 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. + + +File list +--------- + +drivers/staging/media/imx6/capture/ +include/media/imx6.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-2014 Mentor Graphics Inc. diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index a9f2e63..fd94c05 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -31,6 +31,8 @@ source "drivers/staging/media/dt3155v4l/Kconfig" source "drivers/staging/media/go7007/Kconfig" +source "drivers/staging/media/imx6/Kconfig" + source "drivers/staging/media/msi3101/Kconfig" source "drivers/staging/media/omap24xx/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 8e2c5d2..6faee43 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_LIRC_STAGING) += lirc/ obj-$(CONFIG_SOLO6X10) += solo6x10/ obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l/ obj-$(CONFIG_VIDEO_GO7007) += go7007/ +obj-$(CONFIG_VIDEO_IMX6) += imx6/ obj-$(CONFIG_USB_MSI3101) += msi3101/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ diff --git a/drivers/staging/media/imx6/Kconfig b/drivers/staging/media/imx6/Kconfig new file mode 100644 index 0000000..6d94f3f --- /dev/null +++ b/drivers/staging/media/imx6/Kconfig @@ -0,0 +1,25 @@ +config VIDEO_IMX6 + tristate "i.MX6 V4L2 devices" + depends on VIDEO_V4L2 && ARCH_MXC && DRM_IMX_IPUV3_CORE + default y + ---help--- + Say yes here to enable support for video4linux capture for + the i.MX6 SOC. + +config VIDEO_IMX6_CAMERA + tristate "i.MX6 Camera Interface driver" + depends on VIDEO_IMX6 && VIDEO_DEV && I2C + select VIDEOBUF2_DMA_CONTIG + default y + ---help--- + A video4linux capture driver for i.MX6 SOC. Some of the + features of this driver include simultaneous streaming + and preview (overlay) support, MIPI CSI-2 sensor support, + separate rotation control for both streaming and preview, + scaling and colorspace conversion, simultaneous capture + from separate sensors, dmabuf importer/exporter, and full + devicetree support. + +if VIDEO_IMX6_CAMERA +source "drivers/staging/media/imx6/capture/Kconfig" +endif diff --git a/drivers/staging/media/imx6/Makefile b/drivers/staging/media/imx6/Makefile new file mode 100644 index 0000000..e0ed058 --- /dev/null +++ b/drivers/staging/media/imx6/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VIDEO_IMX6_CAMERA) += capture/ diff --git a/drivers/staging/media/imx6/capture/Kconfig b/drivers/staging/media/imx6/capture/Kconfig new file mode 100644 index 0000000..ee4f017 --- /dev/null +++ b/drivers/staging/media/imx6/capture/Kconfig @@ -0,0 +1,11 @@ +menu "i.MX6 Camera Sensors" + +config IMX6_MIPI_CSI2 + tristate "MIPI CSI2 Receiver Driver" + depends on VIDEO_IMX6_CAMERA + default y + ---help--- + MIPI CSI-2 Receiver driver support. This driver is required + for sensor drivers with a MIPI CSI2 interface. + +endmenu diff --git a/drivers/staging/media/imx6/capture/Makefile b/drivers/staging/media/imx6/capture/Makefile new file mode 100644 index 0000000..832d75d --- /dev/null +++ b/drivers/staging/media/imx6/capture/Makefile @@ -0,0 +1,4 @@ +mx6-camera-objs := mx6-camif.o mx6-encode.o mx6-preview.o + +obj-$(CONFIG_VIDEO_IMX6_CAMERA) += mx6-camera.o +obj-$(CONFIG_IMX6_MIPI_CSI2) += mipi-csi2.o diff --git a/drivers/staging/media/imx6/capture/mipi-csi2.c b/drivers/staging/media/imx6/capture/mipi-csi2.c new file mode 100644 index 0000000..4e2aadd --- /dev/null +++ b/drivers/staging/media/imx6/capture/mipi-csi2.c @@ -0,0 +1,322 @@ +/* + * MIPI CSI-2 Receiver Subdev for Freescale i.MX6 SOC. + * + * Copyright (c) 2012-2014 Mentor Graphics Inc. + * Copyright 2004-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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/export.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/list.h> +#include <linux/irq.h> +#include <linux/of_device.h> +#include <media/v4l2-of.h> +#include <media/v4l2-subdev.h> +#include <asm/mach/irq.h> +#include <linux/platform_data/imx-ipu-v3.h> + +struct mx6csi2_dev { + struct device *dev; + struct v4l2_subdev sd; + struct clk *dphy_clk; + struct clk *cfg_clk; + void __iomem *base; + int intr1; + int intr2; + struct v4l2_of_bus_mipi_csi2 bus; + spinlock_t lock; + bool on; +}; + +#define DEVICE_NAME "imx6-mipi-csi2" + +/* Register offsets */ +#define CSI2_VERSION 0x000 +#define CSI2_N_LANES 0x004 +#define CSI2_PHY_SHUTDOWNZ 0x008 +#define CSI2_DPHY_RSTZ 0x00c +#define CSI2_RESETN 0x010 +#define CSI2_PHY_STATE 0x014 +#define CSI2_DATA_IDS_1 0x018 +#define CSI2_DATA_IDS_2 0x01c +#define CSI2_ERR1 0x020 +#define CSI2_ERR2 0x024 +#define CSI2_MSK1 0x028 +#define CSI2_MSK2 0x02c +#define CSI2_PHY_TST_CTRL0 0x030 +#define CSI2_PHY_TST_CTRL1 0x034 +#define CSI2_SFT_RESET 0xf00 + +static inline struct mx6csi2_dev *sd_to_dev(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct mx6csi2_dev, sd); +} + +static inline u32 mx6csi2_read(struct mx6csi2_dev *csi2, unsigned regoff) +{ + return readl(csi2->base + regoff); +} + +static inline void mx6csi2_write(struct mx6csi2_dev *csi2, u32 val, + unsigned regoff) +{ + writel(val, csi2->base + regoff); +} + +static void mx6csi2_set_lanes(struct mx6csi2_dev *csi2) +{ + int lanes = csi2->bus.num_data_lanes; + unsigned long flags; + + spin_lock_irqsave(&csi2->lock, flags); + + mx6csi2_write(csi2, lanes - 1, CSI2_N_LANES); + + spin_unlock_irqrestore(&csi2->lock, flags); +} + +static void __mx6csi2_enable(struct mx6csi2_dev *csi2, bool enable) +{ + if (enable) { + mx6csi2_write(csi2, 0xffffffff, CSI2_PHY_SHUTDOWNZ); + mx6csi2_write(csi2, 0xffffffff, CSI2_DPHY_RSTZ); + mx6csi2_write(csi2, 0xffffffff, CSI2_RESETN); + } else { + mx6csi2_write(csi2, 0x0, CSI2_PHY_SHUTDOWNZ); + mx6csi2_write(csi2, 0x0, CSI2_DPHY_RSTZ); + mx6csi2_write(csi2, 0x0, CSI2_RESETN); + } +} + +static void mx6csi2_enable(struct mx6csi2_dev *csi2, bool enable) +{ + unsigned long flags; + + spin_lock_irqsave(&csi2->lock, flags); + __mx6csi2_enable(csi2, enable); + spin_unlock_irqrestore(&csi2->lock, flags); +} + +static void mx6csi2_reset(struct mx6csi2_dev *csi2) +{ + unsigned long flags; + + spin_lock_irqsave(&csi2->lock, flags); + + __mx6csi2_enable(csi2, false); + + mx6csi2_write(csi2, 0x00000001, CSI2_PHY_TST_CTRL0); + mx6csi2_write(csi2, 0x00000000, CSI2_PHY_TST_CTRL1); + mx6csi2_write(csi2, 0x00000000, CSI2_PHY_TST_CTRL0); + mx6csi2_write(csi2, 0x00000002, CSI2_PHY_TST_CTRL0); + mx6csi2_write(csi2, 0x00010044, CSI2_PHY_TST_CTRL1); + mx6csi2_write(csi2, 0x00000000, CSI2_PHY_TST_CTRL0); + mx6csi2_write(csi2, 0x00000014, CSI2_PHY_TST_CTRL1); + mx6csi2_write(csi2, 0x00000002, CSI2_PHY_TST_CTRL0); + mx6csi2_write(csi2, 0x00000000, CSI2_PHY_TST_CTRL0); + + __mx6csi2_enable(csi2, true); + + spin_unlock_irqrestore(&csi2->lock, flags); +} + +static int mx6csi2_dphy_wait(struct mx6csi2_dev *csi2) +{ + u32 reg; + int i; + + /* wait for mipi sensor ready */ + for (i = 0; i < 50; i++) { + reg = mx6csi2_read(csi2, CSI2_PHY_STATE); + if (reg != 0x200) + break; + usleep_range(10000, 10001); + } + + if (i >= 50) { + v4l2_err(&csi2->sd, + "wait for clock lane timeout, phy_state = 0x%08x\n", + reg); + return -ETIME; + } + + /* wait for mipi stable */ + for (i = 0; i < 50; i++) { + reg = mx6csi2_read(csi2, CSI2_ERR1); + if (reg == 0x0) + break; + usleep_range(10000, 10001); + } + + if (i >= 50) { + v4l2_err(&csi2->sd, + "wait for controller timeout, err1 = 0x%08x\n", + reg); + return -ETIME; + } + + v4l2_info(&csi2->sd, "ready, dphy version 0x%x\n", + mx6csi2_read(csi2, CSI2_VERSION)); + return 0; +} + +/* + * V4L2 subdev operations + */ +static int mx6csi2_s_power(struct v4l2_subdev *sd, int on) +{ + struct mx6csi2_dev *csi2 = sd_to_dev(sd); + int ret = 0; + + if (on && !csi2->on) { + v4l2_info(&csi2->sd, "power on\n"); + clk_prepare_enable(csi2->cfg_clk); + clk_prepare_enable(csi2->dphy_clk); + mx6csi2_set_lanes(csi2); + mx6csi2_reset(csi2); + ret = mx6csi2_dphy_wait(csi2); + } else if (!on && csi2->on) { + v4l2_info(&csi2->sd, "power off\n"); + mx6csi2_enable(csi2, false); + clk_disable_unprepare(csi2->dphy_clk); + clk_disable_unprepare(csi2->cfg_clk); + } + + csi2->on = on; + return ret; +} + +static struct v4l2_subdev_core_ops mx6csi2_core_ops = { + .s_power = mx6csi2_s_power, +}; + +static struct v4l2_subdev_ops mx6csi2_subdev_ops = { + .core = &mx6csi2_core_ops, +}; + +static int mx6csi2_parse_endpoints(struct mx6csi2_dev *csi2) +{ + struct device_node *node = csi2->dev->of_node; + struct device_node *epnode; + struct v4l2_of_endpoint ep; + int ret = 0; + + epnode = of_graph_get_next_endpoint(node, NULL); + if (!epnode) { + v4l2_err(&csi2->sd, "failed to get endpoint node\n"); + return -EINVAL; + } + + v4l2_of_parse_endpoint(epnode, &ep); + if (ep.bus_type != V4L2_MBUS_CSI2) { + v4l2_err(&csi2->sd, "invalid bus type, must be MIPI CSI2\n"); + ret = -EINVAL; + goto out; + } + + csi2->bus = ep.bus.mipi_csi2; + + v4l2_info(&csi2->sd, "data lanes: %d\n", csi2->bus.num_data_lanes); + v4l2_info(&csi2->sd, "flags: 0x%08x\n", csi2->bus.flags); +out: + of_node_put(epnode); + return ret; +} + +static int mx6csi2_probe(struct platform_device *pdev) +{ + struct mx6csi2_dev *csi2; + struct resource *res; + int ret; + + csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); + if (!csi2) + return -ENOMEM; + + csi2->dev = &pdev->dev; + spin_lock_init(&csi2->lock); + + v4l2_subdev_init(&csi2->sd, &mx6csi2_subdev_ops); + csi2->sd.owner = THIS_MODULE; + strcpy(csi2->sd.name, DEVICE_NAME); + + ret = mx6csi2_parse_endpoints(csi2); + if (ret) + return ret; + + csi2->cfg_clk = devm_clk_get(&pdev->dev, "cfg_clk"); + if (IS_ERR(csi2->cfg_clk)) { + v4l2_err(&csi2->sd, "failed to get cfg clock\n"); + ret = PTR_ERR(csi2->cfg_clk); + return ret; + } + + csi2->dphy_clk = devm_clk_get(&pdev->dev, "dphy_clk"); + if (IS_ERR(csi2->dphy_clk)) { + v4l2_err(&csi2->sd, "failed to get dphy clock\n"); + ret = PTR_ERR(csi2->dphy_clk); + return ret; + } + + csi2->intr1 = platform_get_irq(pdev, 0); + csi2->intr2 = platform_get_irq(pdev, 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res || csi2->intr1 < 0 || csi2->intr2 < 0) { + v4l2_err(&csi2->sd, "failed to get platform resources\n"); + return -ENODEV; + } + + csi2->base = devm_ioremap(&pdev->dev, res->start, PAGE_SIZE); + if (!csi2->base) { + v4l2_err(&csi2->sd, "failed to map CSI-2 registers\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, &csi2->sd); + + return 0; +} + +static int mx6csi2_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + + return mx6csi2_s_power(sd, 0); +} + +static const struct of_device_id mx6csi2_dt_ids[] = { + { .compatible = "fsl,imx6-mipi-csi2", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mx6csi2_dt_ids); + +static struct platform_driver mx6csi2_driver = { + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + .of_match_table = mx6csi2_dt_ids, + }, + .probe = mx6csi2_probe, + .remove = mx6csi2_remove, +}; + +module_platform_driver(mx6csi2_driver); + +MODULE_DESCRIPTION("i.MX6 MIPI CSI-2 Receiver driver"); +MODULE_AUTHOR("Mentor Graphics Inc."); +MODULE_LICENSE("GPL"); + diff --git a/drivers/staging/media/imx6/capture/mx6-camif.c b/drivers/staging/media/imx6/capture/mx6-camif.c new file mode 100644 index 0000000..0c70aa9 --- /dev/null +++ b/drivers/staging/media/imx6/capture/mx6-camif.c @@ -0,0 +1,2235 @@ +/* + * Video Camera Capture driver for Freescale i.MX6 SOC + * + * Copyright (c) 2012-2014 Mentor Graphics Inc. + * Copyright 2004-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 <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 <linux/platform_data/imx-ipu-v3.h> +#include <media/imx6.h> +#include "mx6-camif.h" + +/* + * Min/Max supported width and heights. + */ +#define MIN_W 176 +#define MIN_H 144 +#define MAX_W 4096 +#define MAX_H 4096 +#define MAX_W_IC 1024 +#define MAX_H_IC 1024 + +#define H_ALIGN 1 /* multiple of 2 */ +#define S_ALIGN 1 /* multiple of 2 */ + +#define DEVICE_NAME "mx6-camera" + +/* In bytes, per queue */ +#define VID_MEM_LIMIT SZ_64M + +static struct vb2_ops mx6cam_qops; + +/* + * The Gstreamer v4l2src plugin appears to have a bug, it doesn't handle + * frame sizes of type V4L2_FRMSIZE_TYPE_STEPWISE correctly. Set this + * module param to get around this bug. We can remove once v4l2src handles + * stepwise frame sizes correctly. + */ +static int v4l2src_compat = 1; +module_param(v4l2src_compat, int, 0644); +MODULE_PARM_DESC(v4l2src_compat, + "Gstreamer v4l2src plugin compatibility (default: 1)"); + +static inline struct mx6cam_dev *sd2dev(struct v4l2_subdev *sd) +{ + return container_of(sd->v4l2_dev, struct mx6cam_dev, v4l2_dev); +} + +static inline struct mx6cam_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct mx6cam_ctx, fh); +} + +/* Supported pixel formats */ +static struct mx6cam_pixfmt mx6cam_pixformats[] = { + { + .name = "RGB565", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + }, { + .name = "RGB24", + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + }, { + .name = "BGR24", + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + }, { + .name = "RGB32", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + }, { + .name = "BGR32", + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + }, { + .name = "4:2:2 packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + }, { + .name = "4:2:2 packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + }, { + .name = "4:2:0 planar, YUV", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .y_depth = 8, + }, { + .name = "4:2:0 planar, YVU", + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 12, + .y_depth = 8, + }, { + .name = "4:2:2 planar, YUV", + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .y_depth = 8, + }, { + .name = "4:2:0 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .y_depth = 8, + }, +}; +#define NUM_FORMATS ARRAY_SIZE(mx6cam_pixformats) + +static struct mx6cam_pixfmt *mx6cam_get_format(u32 fourcc) +{ + struct mx6cam_pixfmt *ret = NULL; + int i; + + for (i = 0; i < NUM_FORMATS; i++) { + if (mx6cam_pixformats[i].fourcc == fourcc) { + ret = &mx6cam_pixformats[i]; + break; + } + } + + return ret; +} + +/* Support functions */ + +static const char *mx6cam_v4l2_std_to_string(v4l2_std_id std) +{ + switch (std) { + case V4L2_STD_PAL: + return "PAL"; + case V4L2_STD_PAL_N: + return "PAL N"; + case V4L2_STD_NTSC: + return "NTSC"; + case V4L2_STD_NTSC_443: + return "NTSC 4.43"; + case V4L2_STD_PAL_M: + return "PAL M"; + case V4L2_STD_PAL_60: + return "PAL 60"; + case V4L2_STD_UNKNOWN: + return "UNKNOWN"; + case V4L2_STD_ALL: + return "ALL"; + default: + return "Unsupported"; + } +} + +/* find the endpoint that is handling this input index */ +static struct mx6cam_endpoint *find_ep_by_input_index(struct mx6cam_dev *dev, + int input_idx) +{ + struct mx6cam_endpoint *ep; + int i; + + for (i = 0; i < dev->num_eps; i++) { + ep = &dev->eplist[i]; + if (!ep->sd) + continue; + + if (input_idx >= ep->sensor_input.first && + input_idx <= ep->sensor_input.last) + break; + } + + return (i < dev->num_eps) ? ep : NULL; +} + +/* + * Query sensor and update signal lock status. Returns true if lock + * status has changed. + */ +static bool update_signal_lock_status(struct mx6cam_dev *dev) +{ + bool locked, changed; + u32 status; + int ret; + + ret = v4l2_subdev_call(dev->ep->sd, video, g_input_status, &status); + if (ret) + return false; + + locked = ((status & V4L2_IN_ST_NO_SYNC) == 0); + changed = (dev->signal_locked != locked); + dev->signal_locked = locked; + + return changed; +} + +/* + * Return true if the current capture parameters require the use of + * the Image Converter. We need the IC for scaling, colorspace conversion, + * preview, and rotation. + */ +static bool need_ic(struct mx6cam_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->preview_on || + 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 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 mx6cam_dev *dev, + struct v4l2_mbus_framefmt *sf, + struct v4l2_format *uf) +{ + struct mx6cam_endpoint *ep = dev->ep; + struct ipu_csi_signal_cfg csicfg; + + ipu_csi_mbus_fmt_to_sig_cfg(&csicfg, sf->code); + + return ((ep->ep.bus_type == V4L2_MBUS_CSI2 || + csicfg.data_width != IPU_CSI_DATA_WIDTH_16) && + ep->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 mx6cam_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 8:1 */ + if (dev->rot_mode >= IPU_ROTATE_90_RIGHT) { + *height = max_t(__u32, *height, crop->width / 8); + *width = max_t(__u32, *width, crop->height / 8); + } else { + *width = max_t(__u32, *width, crop->width / 8); + *height = max_t(__u32, *height, crop->height / 8); + } +} + +static void adjust_user_fmt(struct mx6cam_dev *dev, + struct v4l2_mbus_framefmt *sf, + struct v4l2_format *uf, + struct v4l2_rect *crop) +{ + /* + * 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; + } + + uf->fmt.pix.bytesperline = + (uf->fmt.pix.width * + ipu_bits_per_pixel(uf->fmt.pix.pixelformat)) >> 3; + uf->fmt.pix.sizeimage = uf->fmt.pix.height * uf->fmt.pix.bytesperline; +} + +/* + * Calculate what the default active crop window should be. Ask + * the sensor via g_crop. This crop window will be stored to dev->crop. + */ +static void calc_default_crop(struct mx6cam_dev *dev, + struct v4l2_rect *rect, + struct v4l2_mbus_framefmt *sf) +{ + struct v4l2_crop crop; + int ret; + + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = v4l2_subdev_call(dev->ep->sd, video, g_crop, &crop); + if (ret) { + /* sensor doesn't support .g_crop(), assume sensor frame */ + rect->top = rect->left = 0; + rect->width = sf->width; + rect->height = sf->height; + } else + *rect = crop.c; + + /* adjust crop window to h/w alignment restrictions */ + rect->width &= ~0x7; + rect->left &= ~0x3; +} + +/* + * Use the parsed endpoint info and sensor format to fill + * ipu_csi_signal_cfg. + */ +static void fill_csi_signal_cfg(struct mx6cam_dev *dev) +{ + struct ipu_csi_signal_cfg *csicfg = &dev->ep->csi_sig_cfg; + struct v4l2_of_endpoint *ep = &dev->ep->ep; + + memset(csicfg, 0, sizeof(*csicfg)); + + ipu_csi_mbus_fmt_to_sig_cfg(csicfg, dev->sensor_fmt.code); + + switch (ep->bus_type) { + case V4L2_MBUS_PARALLEL: + csicfg->ext_vsync = 0; + csicfg->vsync_pol = (ep->bus.parallel.flags & + V4L2_MBUS_VSYNC_ACTIVE_LOW) ? 1 : 0; + csicfg->hsync_pol = (ep->bus.parallel.flags & + V4L2_MBUS_HSYNC_ACTIVE_LOW) ? 1 : 0; + csicfg->pixclk_pol = (ep->bus.parallel.flags & + V4L2_MBUS_PCLK_SAMPLE_FALLING) ? 1 : 0; + csicfg->clk_mode = IPU_CSI_CLK_MODE_GATED_CLK; + break; + case V4L2_MBUS_BT656: + csicfg->ext_vsync = 1; + if (dev->sensor_fmt.field == V4L2_FIELD_INTERLACED) + csicfg->clk_mode = IPU_CSI_CLK_MODE_CCIR656_INTERLACED; + else + csicfg->clk_mode = IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE; + break; + case V4L2_MBUS_CSI2: + /* + * MIPI CSI-2 requires non gated clock mode, all other + * parameters are not applicable for MIPI CSI-2 bus. + */ + csicfg->clk_mode = IPU_CSI_CLK_MODE_NONGATED_CLK; + break; + default: + /* will never get here, keep compiler quiet */ + break; + } +} + +static int update_sensor_std(struct mx6cam_dev *dev) +{ + return v4l2_subdev_call(dev->ep->sd, video, querystd, + &dev->current_std); +} + +static int update_sensor_fmt(struct mx6cam_dev *dev) +{ + int ret; + + ret = v4l2_subdev_call(dev->ep->sd, video, g_mbus_fmt, + &dev->sensor_fmt); + if (ret) + return ret; + + fill_csi_signal_cfg(dev); + + /* update sensor crop bounds */ + dev->crop_bounds.top = dev->crop_bounds.left = 0; + dev->crop_bounds.width = dev->sensor_fmt.width; + dev->crop_bounds.height = dev->sensor_fmt.height; + dev->crop_defrect = dev->crop_bounds; + + return 0; +} + +/* + * Turn current sensor power on/off according to power_count. + */ +static int sensor_set_power(struct mx6cam_dev *dev, int on) +{ + struct mx6cam_endpoint *ep = dev->ep; + struct v4l2_subdev *sd = ep->sd; + int ret; + + if (on && ep->power_count++ > 0) + return 0; + else if (!on && (ep->power_count == 0 || --ep->power_count > 0)) + return 0; + + ret = v4l2_subdev_call(sd, core, s_power, on); + return ret != -ENOIOCTLCMD ? ret : 0; +} + +/* + * Turn current sensor streaming on/off according to stream_count. + */ +static int sensor_set_stream(struct mx6cam_dev *dev, int on) +{ + struct mx6cam_endpoint *ep = dev->ep; + struct v4l2_subdev *sd = ep->sd; + int ret; + + if (on && ep->stream_count++ > 0) + return 0; + else if (!on && (ep->stream_count == 0 || --ep->stream_count > 0)) + return 0; + + ret = v4l2_subdev_call(sd, video, s_stream, on); + return ret != -ENOIOCTLCMD ? ret : 0; +} + +/* + * Start the encoder for buffer streaming. There must be at least two + * frames in the vb2 queue. + */ +static int start_encoder(struct mx6cam_dev *dev) +{ + int ret; + + if (dev->encoder_on) + return 0; + + /* sensor stream on */ + ret = sensor_set_stream(dev, 1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "sensor stream on failed\n"); + return ret; + } + + /* encoder stream on */ + ret = v4l2_subdev_call(dev->encoder_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 mx6cam_dev *dev) +{ + int ret; + + if (!dev->encoder_on) + return 0; + + /* encoder off */ + ret = v4l2_subdev_call(dev->encoder_sd, video, s_stream, 0); + if (ret) + v4l2_err(&dev->v4l2_dev, "encoder stream off failed\n"); + + /* sensor stream off */ + ret = sensor_set_stream(dev, 0); + if (ret) + v4l2_err(&dev->v4l2_dev, "sensor stream off failed\n"); + + dev->encoder_on = false; + return ret; +} + +/* + * Start preview. + */ +static int start_preview(struct mx6cam_dev *dev) +{ + int ret; + + if (atomic_read(&dev->status_change)) { + update_signal_lock_status(dev); + update_sensor_std(dev); + update_sensor_fmt(dev); + /* reset active crop window */ + calc_default_crop(dev, &dev->crop, &dev->sensor_fmt); + atomic_set(&dev->status_change, 0); + v4l2_info(&dev->v4l2_dev, "at preview on: %s, %s\n", + mx6cam_v4l2_std_to_string(dev->current_std), + dev->signal_locked ? "signal locked" : "no signal"); + } + + /* sensor stream on */ + ret = sensor_set_stream(dev, 1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "sensor stream on failed\n"); + return ret; + } + + /* preview stream on */ + ret = v4l2_subdev_call(dev->preview_sd, video, s_stream, 1); + if (ret) + v4l2_err(&dev->v4l2_dev, "preview stream on failed\n"); + + return ret; +} + +/* + * Stop preview. + */ +static int stop_preview(struct mx6cam_dev *dev) +{ + int ret; + + /* preview stream off */ + ret = v4l2_subdev_call(dev->preview_sd, video, s_stream, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "preview stream off failed\n"); + return ret; + } + + /* sensor stream off */ + ret = sensor_set_stream(dev, 0); + if (ret) + v4l2_err(&dev->v4l2_dev, "sensor stream off failed\n"); + + return ret; +} + +/* + * Start/Stop streaming. + */ +static int set_stream(struct mx6cam_ctx *ctx, bool on) +{ + struct mx6cam_dev *dev = ctx->dev; + int ret = 0; + + if (on) { + if (atomic_read(&dev->status_change)) { + update_signal_lock_status(dev); + update_sensor_std(dev); + update_sensor_fmt(dev); + /* reset active crop window */ + calc_default_crop(dev, &dev->crop, &dev->sensor_fmt); + atomic_set(&dev->status_change, 0); + v4l2_info(&dev->v4l2_dev, "at stream on: %s, %s\n", + mx6cam_v4l2_std_to_string(dev->current_std), + dev->signal_locked ? + "signal locked" : "no signal"); + } + + dev->using_ic = + (need_ic(dev, &dev->sensor_fmt, &dev->user_fmt, + &dev->crop) && + can_use_ic(dev, &dev->sensor_fmt, &dev->user_fmt)); + + if (dev->preview_on) + stop_preview(dev); + + /* + * 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); + + if (dev->preview_on) + start_preview(dev); + } else { + ret = stop_encoder(dev); + } + + return ret; +} + +/* + * Restart work handler. This is called in three cases during active + * streaming and/or preview: + * + * 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 mx6cam_ctx *ctx = container_of(w, struct mx6cam_ctx, + restart_work); + struct mx6cam_dev *dev = ctx->dev; + + mutex_lock(&dev->mutex); + + if (!vb2_is_streaming(&dev->buffer_queue)) { + /* just restart preview if on */ + if (dev->preview_on) { + v4l2_warn(&dev->v4l2_dev, "restarting preview\n"); + stop_preview(dev); + start_preview(dev); + } + goto out_unlock; + } + + 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 mx6cam_ctx *ctx = container_of(w, struct mx6cam_ctx, + stop_work); + struct mx6cam_dev *dev = ctx->dev; + + mutex_lock(&dev->mutex); + + if (dev->preview_on) { + v4l2_err(&dev->v4l2_dev, "stopping preview\n"); + stop_preview(dev); + dev->preview_on = false; + } + + 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 mx6cam_restart_timeout(unsigned long data) +{ + struct mx6cam_ctx *ctx = (struct mx6cam_ctx *)data; + + schedule_work(&ctx->restart_work); +} + +/* Controls */ +static int mx6cam_set_rotation(struct mx6cam_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 mx6cam_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mx6cam_dev *dev = container_of(ctrl->handler, + struct mx6cam_dev, ctrl_hdlr); + 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; + default: + v4l2_err(&dev->v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + + return mx6cam_set_rotation(dev, rotation, hflip, vflip); +} + +static const struct v4l2_ctrl_ops mx6cam_ctrl_ops = { + .s_ctrl = mx6cam_s_ctrl, +}; + +static int mx6cam_init_controls(struct mx6cam_dev *dev) +{ + struct v4l2_ctrl_handler *hdlr = &dev->ctrl_hdlr; + int ret; + + v4l2_ctrl_handler_init(hdlr, 3); + + v4l2_ctrl_new_std(hdlr, &mx6cam_ctrl_ops, V4L2_CID_HFLIP, + 0, 1, 1, 0); + v4l2_ctrl_new_std(hdlr, &mx6cam_ctrl_ops, V4L2_CID_VFLIP, + 0, 1, 1, 0); + v4l2_ctrl_new_std(hdlr, &mx6cam_ctrl_ops, V4L2_CID_ROTATE, + 0, 270, 90, 0); + + if (hdlr->error) { + ret = hdlr->error; + v4l2_ctrl_handler_free(hdlr); + return ret; + } + + dev->v4l2_dev.ctrl_handler = hdlr; + dev->vfd->ctrl_handler = hdlr; + + v4l2_ctrl_handler_setup(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->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OVERLAY; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mx6cam_pixfmt *fmt; + + if (f->index >= NUM_FORMATS) + return -EINVAL; + + fmt = &mx6cam_pixformats[f->index]; + strncpy(f->description, fmt->name, sizeof(f->description) - 1); + f->pixelformat = fmt->fourcc; + return 0; +} + +static int vidioc_enum_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt_vid_cap(file, priv, f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + + f->fmt.pix = dev->user_fmt.fmt.pix; + return 0; +} + +static int vidioc_g_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + + f->fmt.win = dev->win; + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct v4l2_mbus_framefmt mbus_fmt; + struct mx6cam_pixfmt *fmt; + unsigned int width_align; + struct v4l2_rect crop; + int ret; + + fmt = mx6cam_get_format(f->fmt.pix.pixelformat); + if (!fmt) { + v4l2_err(&dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + /* + * 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. + */ + 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); + + v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, 0); + ret = v4l2_subdev_call(dev->ep->sd, video, try_mbus_fmt, &mbus_fmt); + if (ret) + return ret; + + /* + * calculate what the optimal crop window will be for this + * sensor format and make any user format adjustments. + */ + calc_default_crop(dev, &crop, &mbus_fmt); + adjust_user_fmt(dev, &mbus_fmt, f, &crop); + + return 0; +} + +static int vidioc_try_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct v4l2_window *win = &f->fmt.win; + unsigned int width_align; + + width_align = ipu_pixelformat_is_planar(dev->fbuf.fmt.pixelformat) ? + 4 : 3; + + v4l_bound_align_image(&win->w.width, MIN_W, MAX_W_IC, + width_align, &win->w.height, + MIN_H, MAX_H_IC, H_ALIGN, S_ALIGN); + + adjust_to_resizer_limits(dev, f, &dev->crop); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct v4l2_mbus_framefmt mbus_fmt; + 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; + + v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, 0); + ret = v4l2_subdev_call(dev->ep->sd, video, s_mbus_fmt, &mbus_fmt); + if (ret) { + v4l2_err(&dev->v4l2_dev, "%s s_mbus_fmt failed\n", __func__); + return ret; + } + + ret = update_sensor_fmt(dev); + if (ret) + return ret; + + /* reset active crop window */ + calc_default_crop(dev, &dev->crop, &dev->sensor_fmt); + + dev->user_fmt = *f; + + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct mx6cam_pixfmt *fmt; + struct v4l2_format uf; + int ret = 0; + + fmt = mx6cam_get_format(fsize->pixel_format); + if (!fmt) + return -EINVAL; + + if (!v4l2src_compat) { + 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; + } + } else { + ret = v4l2_subdev_call(dev->ep->sd, video, + enum_framesizes, fsize); + } + + return ret; +} + +static int vidioc_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct mx6cam_pixfmt *fmt; + + fmt = mx6cam_get_format(fival->pixel_format); + if (!fmt) + return -EINVAL; + + return v4l2_subdev_call(dev->ep->sd, video, enum_frameintervals, fival); +} + +static int vidioc_s_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct v4l2_window *win = &f->fmt.win; + int ret; + + ret = vidioc_try_fmt_vid_overlay(file, priv, f); + if (ret) + return ret; + + if (dev->preview_on) + stop_preview(dev); + + dev->win = *win; + + if (dev->preview_on) + start_preview(dev); + + return 0; +} + +static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_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 mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_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 mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + int ret; + + if (vb2_is_busy(&dev->buffer_queue)) + return -EBUSY; + + ret = v4l2_subdev_call(dev->ep->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 mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct mx6cam_sensor_input *epinput; + struct mx6cam_endpoint *ep; + int sensor_input; + + /* find the endpoint that is handling this input */ + ep = find_ep_by_input_index(dev, input->index); + if (!ep) + return -EINVAL; + + epinput = &ep->sensor_input; + sensor_input = input->index - epinput->first; + + input->type = V4L2_INPUT_TYPE_CAMERA; + input->capabilities = epinput->caps[sensor_input]; + strncpy(input->name, epinput->name[sensor_input], sizeof(input->name)); + + if (input->index == dev->current_input) { + v4l2_subdev_call(ep->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 mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + + *index = dev->current_input; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int index) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct mx6cam_sensor_input *epinput; + struct mx6cam_endpoint *ep; + int ret, sensor_input; + + if (index == dev->current_input) + return 0; + + /* find the endpoint that is handling this input */ + ep = find_ep_by_input_index(dev, index); + if (!ep) + return -EINVAL; + + if (dev->ep != ep) { + /* + * don't allow switching sensors if there are queued buffers, + * preview is on, or there are other users of the current + * sensor besides us. + */ + if (vb2_is_busy(&dev->buffer_queue) || dev->preview_on || + dev->ep->power_count > 1) + return -EBUSY; + + v4l2_info(&dev->v4l2_dev, "switching to sensor %s\n", + ep->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 endpoint */ + dev->ep = ep; + + /* power-on the new sensor */ + ret = sensor_set_power(dev, 1); + if (ret) + v4l2_warn(&dev->v4l2_dev, "sensor power on failed\n"); + + /* power-on the csi2 receiver */ + if (dev->ep->ep.bus_type == V4L2_MBUS_CSI2 && dev->csi2_sd) { + ret = v4l2_subdev_call(dev->csi2_sd, core, s_power, + true); + if (ret) + v4l2_err(&dev->v4l2_dev, + "csi2 power on failed\n"); + } + } + + /* finally select the sensor's input */ + epinput = &ep->sensor_input; + sensor_input = index - epinput->first; + ret = v4l2_subdev_call(dev->ep->sd, video, s_routing, + epinput->value[sensor_input], 0, 0); + + dev->current_input = index; + + /* + * sometimes on switching video input on video decoder devices + * no lock status change event is generated, but vertical sync + * is messed up nevertheless. So schedule a restart to correct it. + */ + if (ctx->io_allowed) + mod_timer(&ctx->restart_timer, + jiffies + msecs_to_jiffies(MX6CAM_RESTART_DELAY)); + + return 0; +} + +static int vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return v4l2_subdev_call(dev->ep->sd, video, g_parm, a); +} + +static int vidioc_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return v4l2_subdev_call(dev->ep->sd, video, s_parm, a); +} + +static int vidioc_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cropcap) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cropcap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + + cropcap->bounds = dev->crop_bounds; + cropcap->defrect = dev->crop_defrect; + cropcap->pixelaspect.numerator = 1; + cropcap->pixelaspect.denominator = 1; + return 0; +} + +static int vidioc_g_crop(struct file *file, void *priv, + struct v4l2_crop *crop) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + + crop->c = dev->crop; + return 0; +} + +static int vidioc_s_crop(struct file *file, void *priv, + const struct v4l2_crop *crop) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct v4l2_rect *bounds = &dev->crop_bounds; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + + if (vb2_is_busy(&dev->buffer_queue)) + return -EBUSY; + + /* make sure crop window is within bounds */ + if (crop->c.top < 0 || crop->c.left < 0 || + crop->c.left + crop->c.width > bounds->width || + crop->c.top + crop->c.height > bounds->height) + return -EINVAL; + + /* + * FIXME: the IPU currently does not setup the CCIR code + * registers properly to handle arbitrary crop windows. So + * ignore this request if the sensor bus is BT.656. + */ + if (dev->ep->ep.bus_type == V4L2_MBUS_BT656) + return 0; + + dev->crop = crop->c; + + /* adjust crop window to h/w alignment restrictions */ + dev->crop.width &= ~0x7; + dev->crop.left &= ~0x3; + + /* + * 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_s_fbuf(struct file *file, void *priv, + const struct v4l2_framebuffer *fbuf) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct mx6cam_pixfmt *fmt; + + if (fbuf->flags != V4L2_FBUF_FLAG_OVERLAY || !fbuf->base) + return -EINVAL; + + fmt = mx6cam_get_format(fbuf->fmt.pixelformat); + if (!fmt) { + v4l2_err(&dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + fbuf->fmt.pixelformat); + return -EINVAL; + } + + if (dev->preview_on) + stop_preview(dev); + + dev->fbuf = *fbuf; + + if (dev->preview_on) + start_preview(dev); + + return 0; +} + +static int vidioc_g_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fbuf) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + + *fbuf = dev->fbuf; + + return 0; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + struct vb2_queue *vq = &dev->buffer_queue; + int ret; + + if (vb2_is_busy(vq)) + 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); + ctx->restart_timer.data = (unsigned long)ctx; + ctx->restart_timer.function = mx6cam_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 mx6cam_buffer); + vq->ops = &mx6cam_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; + } + + ctx->io_allowed = true; + dev->io_ctx = ctx; + + return vb2_reqbufs(vq, reqbufs); + +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 mx6cam_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 mx6cam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!ctx->io_allowed) + return -EBUSY; + + return vb2_qbuf(vq, buf); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!ctx->io_allowed) + 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 mx6cam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!ctx->io_allowed) + return -EBUSY; + + return vb2_expbuf(vq, eb); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!ctx->io_allowed) + return -EBUSY; + + return vb2_streamon(vq, type); +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct vb2_queue *vq = &ctx->dev->buffer_queue; + + if (!ctx->io_allowed) + return -EBUSY; + + return vb2_streamoff(vq, type); +} + +static int vidioc_overlay(struct file *file, void *priv, + unsigned int enable) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + int ret = 0; + + if (!ctx->io_allowed) + return -EBUSY; + + if (enable && !dev->preview_on) { + if (vb2_is_streaming(&dev->buffer_queue) && !dev->using_ic) { + v4l2_err(&dev->v4l2_dev, + "%s: not allowed while streaming w/o IC\n", + __func__); + return -EBUSY; + } + + if (!can_use_ic(dev, &dev->sensor_fmt, &dev->user_fmt)) { + v4l2_err(&dev->v4l2_dev, + "%s: current format does not allow preview\n", + __func__); + return -EINVAL; + } + + ret = start_preview(dev); + if (!ret) + dev->preview_on = true; + } else if (!enable && dev->preview_on) { + ret = stop_preview(dev); + dev->preview_on = false; + } + + return ret; +} + +static const struct v4l2_ioctl_ops mx6cam_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_enum_fmt_vid_overlay = vidioc_enum_fmt_vid_overlay, + .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_vid_overlay, + .vidioc_try_fmt_vid_overlay = vidioc_try_fmt_vid_overlay, + .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_vid_overlay, + + .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_fbuf = vidioc_g_fbuf, + .vidioc_s_fbuf = vidioc_s_fbuf, + + .vidioc_cropcap = vidioc_cropcap, + .vidioc_g_crop = vidioc_g_crop, + .vidioc_s_crop = vidioc_s_crop, + + .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, + .vidioc_overlay = vidioc_overlay, +}; + + +/* + * Queue operations + */ + +static int mx6cam_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct mx6cam_ctx *ctx = vb2_get_drv_priv(vq); + struct mx6cam_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 mx6cam_buf_init(struct vb2_buffer *vb) +{ + struct mx6cam_buffer *buf = to_mx6cam_vb(vb); + INIT_LIST_HEAD(&buf->list); + return 0; +} + +static int mx6cam_buf_prepare(struct vb2_buffer *vb) +{ + struct mx6cam_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mx6cam_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 mx6cam_buf_queue(struct vb2_buffer *vb) +{ + struct mx6cam_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mx6cam_dev *dev = ctx->dev; + struct mx6cam_buffer *buf = to_mx6cam_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 mx6cam_lock(struct vb2_queue *vq) +{ + struct mx6cam_ctx *ctx = vb2_get_drv_priv(vq); + struct mx6cam_dev *dev = ctx->dev; + + mutex_lock(&dev->mutex); +} + +static void mx6cam_unlock(struct vb2_queue *vq) +{ + struct mx6cam_ctx *ctx = vb2_get_drv_priv(vq); + struct mx6cam_dev *dev = ctx->dev; + + mutex_unlock(&dev->mutex); +} + +static int mx6cam_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct mx6cam_ctx *ctx = vb2_get_drv_priv(vq); + + if (vb2_is_streaming(vq)) + return 0; + + return set_stream(ctx, true); +} + +static void mx6cam_stop_streaming(struct vb2_queue *vq) +{ + struct mx6cam_ctx *ctx = vb2_get_drv_priv(vq); + struct mx6cam_dev *dev = ctx->dev; + struct mx6cam_buffer *frame; + unsigned long flags; + + if (!vb2_is_streaming(vq)) + return; + + 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 mx6cam_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 mx6cam_qops = { + .queue_setup = mx6cam_queue_setup, + .buf_init = mx6cam_buf_init, + .buf_prepare = mx6cam_buf_prepare, + .buf_queue = mx6cam_buf_queue, + .wait_prepare = mx6cam_unlock, + .wait_finish = mx6cam_lock, + .start_streaming = mx6cam_start_streaming, + .stop_streaming = mx6cam_stop_streaming, +}; + +/* + * File operations + */ +static int mx6cam_open(struct file *file) +{ + struct mx6cam_dev *dev = video_drvdata(file); + struct mx6cam_ctx *ctx; + int ret; + + if (mutex_lock_interruptible(&dev->mutex)) + return -ERESTARTSYS; + + if (!dev->ep || !dev->ep->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); + ctx->io_allowed = false; + + ret = sensor_set_power(dev, 1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "sensor power on failed\n"); + goto ctx_free; + } + + if (dev->ep->ep.bus_type == V4L2_MBUS_CSI2 && dev->csi2_sd) { + ret = v4l2_subdev_call(dev->csi2_sd, core, s_power, true); + if (ret) { + v4l2_err(&dev->v4l2_dev, "csi2 power on failed\n"); + goto sensor_off; + } + } + + /* update the sensor's current format */ + update_sensor_fmt(dev); + /* and init crop window if needed */ + if (!dev->crop.width || !dev->crop.height) + calc_default_crop(dev, &dev->crop, &dev->sensor_fmt); + + mutex_unlock(&dev->mutex); + return 0; + +sensor_off: + sensor_set_power(dev, 0); +ctx_free: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +unlock: + mutex_unlock(&dev->mutex); + return ret; +} + +static int mx6cam_release(struct file *file) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_dev *dev = ctx->dev; + int ret = 0; + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + mutex_lock(&dev->mutex); + + if (ctx->io_allowed) { + BUG_ON(dev->io_ctx != ctx); + + vb2_queue_release(&dev->buffer_queue); + vb2_dma_contig_cleanup_ctx(ctx->alloc_ctx); + + if (dev->preview_on) { + stop_preview(dev); + dev->preview_on = false; + } + + dev->io_ctx = NULL; + } + + if (dev->ep == NULL || dev->ep->sd == NULL) { + v4l2_warn(&dev->v4l2_dev, "lost the slave?\n"); + goto unlock; + } + + ret = sensor_set_power(dev, 0); + if (ret) + v4l2_err(&dev->v4l2_dev, "sensor power off failed\n"); + +unlock: + mutex_unlock(&dev->mutex); + kfree(ctx); + return ret; +} + +static unsigned int mx6cam_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_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 mx6cam_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct mx6cam_ctx *ctx = file2ctx(file); + struct mx6cam_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 mx6cam_fops = { + .owner = THIS_MODULE, + .open = mx6cam_open, + .release = mx6cam_release, + .poll = mx6cam_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = mx6cam_mmap, +}; + +static struct video_device mx6cam_videodev = { + .name = DEVICE_NAME, + .fops = &mx6cam_fops, + .ioctl_ops = &mx6cam_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 mx6cam_subdev_notification(struct v4l2_subdev *sd, + unsigned int notification, + void *arg) +{ + struct mx6cam_dev *dev; + struct mx6cam_ctx *ctx; + + if (sd == NULL) + return; + + dev = sd2dev(sd); + ctx = dev->io_ctx; + + switch (notification) { + case MX6CAM_NFB4EOF_NOTIFY: + if (ctx) + mod_timer(&ctx->restart_timer, jiffies + + msecs_to_jiffies(MX6CAM_RESTART_DELAY)); + break; + case DECODER_STATUS_CHANGE_NOTIFY: + atomic_set(&dev->status_change, 1); + if (ctx) { + v4l2_warn(&dev->v4l2_dev, "decoder status change\n"); + mod_timer(&ctx->restart_timer, jiffies + + msecs_to_jiffies(MX6CAM_RESTART_DELAY)); + } + break; + case MX6CAM_EOF_TIMEOUT_NOTIFY: + if (ctx) { + /* 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; + } +} + +static int mx6cam_add_sensor(struct mx6cam_dev *dev, + struct device_node *remote, + struct mx6cam_endpoint *ep) +{ + struct i2c_client *client; + int ret = 0; + + client = of_find_i2c_device_by_node(remote); + if (!client) + return -EPROBE_DEFER; + + device_lock(&client->dev); + + if (!client->dev.driver || + !try_module_get(client->dev.driver->owner)) { + ret = -EPROBE_DEFER; + v4l2_info(&dev->v4l2_dev, "No driver found for %s\n", + remote->full_name); + goto unlock; + } + + ep->sd = i2c_get_clientdata(client); + ret = v4l2_device_register_subdev(&dev->v4l2_dev, ep->sd); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to register subdev %s\n", + ep->sd->name); + goto mod_put; + } + + v4l2_info(&dev->v4l2_dev, "Registered sensor subdev %s on CSI%d\n", + ep->sd->name, ep->ep.base.port); + ret = 0; + +mod_put: + module_put(client->dev.driver->owner); +unlock: + device_unlock(&client->dev); + put_device(&client->dev); + return ret; +} + +static int mx6cam_add_csi2_receiver(struct mx6cam_dev *dev) +{ + struct platform_device *pdev; + struct device_node *node; + int ret = -EPROBE_DEFER; + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6-mipi-csi2"); + if (!node) + return 0; + + pdev = of_find_device_by_node(node); + of_node_put(node); + if (!pdev) + return 0; + + /* Lock to ensure dev->driver won't change. */ + device_lock(&pdev->dev); + + if (!pdev->dev.driver || !try_module_get(pdev->dev.driver->owner)) + goto dev_unlock; + + dev->csi2_sd = dev_get_drvdata(&pdev->dev); + + ret = v4l2_device_register_subdev(&dev->v4l2_dev, dev->csi2_sd); + if (ret) { + v4l2_err(&dev->v4l2_dev, "failed to register mipi_csi2!\n"); + goto mod_put; + } + + v4l2_info(&dev->v4l2_dev, "Registered subdev %s\n", + dev->csi2_sd->name); +mod_put: + module_put(pdev->dev.driver->owner); +dev_unlock: + device_unlock(&pdev->dev); + if (ret == -EPROBE_DEFER) + v4l2_info(&dev->v4l2_dev, + "deferring mipi_csi2 registration\n"); + else if (ret < 0) + v4l2_err(&dev->v4l2_dev, + "mipi_csi2 registration failed (%d)\n", ret); + return ret; +} + +/* parse inputs property from v4l2_of_endpoint node */ +static int mx6cam_parse_inputs(struct mx6cam_dev *dev, + struct device_node *node, + int next_input, + struct mx6cam_endpoint *ep) +{ + struct mx6cam_sensor_input *epinput = &ep->sensor_input; + int ret, i; + + for (i = 0; i < MX6CAM_MAX_INPUTS; i++) { + const char *input_name; + u32 val; + + ret = of_property_read_u32_index(node, "inputs", i, &val); + if (ret) + break; + + epinput->value[i] = val; + + ret = of_property_read_string_index(node, "input-names", i, + &input_name); + if (!ret) + strncpy(epinput->name[i], input_name, + sizeof(epinput->name[i])); + else + snprintf(epinput->name[i], sizeof(epinput->name[i]), + "%s-%d", ep->sd->name, i); + + val = 0; + ret = of_property_read_u32_index(node, "input-caps", i, &val); + epinput->caps[i] = val; + } + + epinput->num = i; + + /* if no inputs provided just assume a single input */ + if (epinput->num == 0) { + epinput->num = 1; + epinput->caps[0] = 0; + strncpy(epinput->name[0], ep->sd->name, + sizeof(epinput->name[0])); + } + + epinput->first = next_input; + epinput->last = next_input + epinput->num - 1; + return epinput->last + 1; +} + +static int mx6cam_parse_endpoints(struct mx6cam_dev *dev, + struct device_node *node) +{ + struct device_node *remote, *epnode = NULL; + struct v4l2_of_endpoint ep; + int ret, next_input = 0; + + while (dev->num_eps < MX6CAM_MAX_ENDPOINTS) { + epnode = of_graph_get_next_endpoint(node, epnode); + if (!epnode) + break; + + v4l2_of_parse_endpoint(epnode, &ep); + + if (ep.base.port > 1) { + v4l2_err(&dev->v4l2_dev, "invalid port %d\n", + ep.base.port); + of_node_put(epnode); + return -EINVAL; + } + + remote = of_graph_get_remote_port_parent(epnode); + if (!remote) { + v4l2_err(&dev->v4l2_dev, + "failed to find remote port parent\n"); + of_node_put(epnode); + return -EINVAL; + } + + dev->eplist[dev->num_eps].ep = ep; + ret = mx6cam_add_sensor(dev, remote, + &dev->eplist[dev->num_eps]); + if (ret) + goto out; + + next_input = mx6cam_parse_inputs(dev, epnode, next_input, + &dev->eplist[dev->num_eps]); + + dev->num_eps++; + + of_node_put(remote); + of_node_put(epnode); + } + + if (!dev->num_eps) { + v4l2_err(&dev->v4l2_dev, "no endpoints defined!\n"); + return -EINVAL; + } + + dev->ep = &dev->eplist[0]; + return 0; +out: + of_node_put(remote); + of_node_put(epnode); + return ret; +} + +static void mx6cam_unregister_subdevs(struct mx6cam_dev *dev) +{ + struct i2c_adapter *adapter; + struct i2c_client *client; + struct mx6cam_endpoint *ep; + int i; + + if (!IS_ERR_OR_NULL(dev->encoder_sd)) + v4l2_device_unregister_subdev(dev->encoder_sd); + + if (!IS_ERR_OR_NULL(dev->preview_sd)) + v4l2_device_unregister_subdev(dev->preview_sd); + + if (!IS_ERR_OR_NULL(dev->csi2_sd)) + v4l2_device_unregister_subdev(dev->csi2_sd); + + for (i = 0; i < dev->num_eps; i++) { + ep = &dev->eplist[i]; + if (!ep->sd) + continue; + client = v4l2_get_subdevdata(ep->sd); + if (!client) + continue; + + v4l2_device_unregister_subdev(ep->sd); + + if (!client->dev.of_node) { + adapter = client->adapter; + i2c_unregister_device(client); + if (adapter) + i2c_put_adapter(adapter); + } + } +} + +static int mx6cam_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct mx6cam_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); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + return ret; + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + /* get our parent IPU */ + dev->ipu = dev_get_drvdata(pdev->dev.parent); + if (IS_ERR(dev->ipu)) { + v4l2_err(&dev->v4l2_dev, "could not get parent ipu\n"); + ret = -ENODEV; + goto unreg_dev; + } + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = mx6cam_videodev; + vfd->lock = &dev->mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + dev->v4l2_dev.notify = mx6cam_subdev_notification; + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto unreg_vdev; + } + + video_set_drvdata(vfd, dev); + snprintf(vfd->name, sizeof(vfd->name), "%s", mx6cam_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->current_std = V4L2_STD_ALL; + + /* init our controls */ + ret = mx6cam_init_controls(dev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "init controls failed\n"); + goto unreg_vdev; + } + + /* find and register mipi csi2 receiver subdev */ + ret = mx6cam_add_csi2_receiver(dev); + if (ret) + goto free_ctrls; + + /* parse and register all sensor endpoints */ + ret = mx6cam_parse_endpoints(dev, node); + if (ret) + goto unreg_subdevs; + + dev->encoder_sd = mx6cam_encoder_init(dev); + if (IS_ERR(dev->encoder_sd)) { + ret = PTR_ERR(dev->encoder_sd); + goto unreg_subdevs; + } + + dev->preview_sd = mx6cam_preview_init(dev); + if (IS_ERR(dev->preview_sd)) { + ret = PTR_ERR(dev->preview_sd); + goto unreg_subdevs; + } + + ret = v4l2_device_register_subdev(&dev->v4l2_dev, dev->encoder_sd); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, + "failed to register encoder subdev\n"); + goto unreg_subdevs; + } + v4l2_info(&dev->v4l2_dev, "Registered subdev %s\n", + dev->encoder_sd->name); + + ret = v4l2_device_register_subdev(&dev->v4l2_dev, dev->preview_sd); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, + "failed to register preview subdev\n"); + goto unreg_subdevs; + } + v4l2_info(&dev->v4l2_dev, "Registered subdev %s\n", + dev->preview_sd->name); + + ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev); + if (ret) + goto unreg_subdevs; + + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d, parent is ipu%d\n", + vfd->num, ipu_get_num(dev->ipu)); + + return 0; + +unreg_subdevs: + mx6cam_unregister_subdevs(dev); +free_ctrls: + v4l2_ctrl_handler_free(&dev->ctrl_hdlr); +unreg_vdev: + video_unregister_device(dev->vfd); +unreg_dev: + v4l2_device_unregister(&dev->v4l2_dev); + return ret; +} + +static int mx6cam_remove(struct platform_device *pdev) +{ + struct mx6cam_dev *dev = + (struct mx6cam_dev *)platform_get_drvdata(pdev); + + v4l2_info(&dev->v4l2_dev, "Removing " DEVICE_NAME "\n"); + v4l2_ctrl_handler_free(&dev->ctrl_hdlr); + video_unregister_device(dev->vfd); + mx6cam_unregister_subdevs(dev); + v4l2_device_unregister(&dev->v4l2_dev); + + return 0; +} + +static struct of_device_id mx6cam_dt_ids[] = { + { .compatible = "fsl,imx6-v4l2-capture" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mx6cam_dt_ids); + +static struct platform_driver mx6cam_pdrv = { + .probe = mx6cam_probe, + .remove = mx6cam_remove, + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + .of_match_table = mx6cam_dt_ids, + }, +}; + +module_platform_driver(mx6cam_pdrv); + +MODULE_DESCRIPTION("i.MX6 v4l2 capture driver"); +MODULE_AUTHOR("Mentor Graphics Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/imx6/capture/mx6-camif.h b/drivers/staging/media/imx6/capture/mx6-camif.h new file mode 100644 index 0000000..6ebcec6 --- /dev/null +++ b/drivers/staging/media/imx6/capture/mx6-camif.h @@ -0,0 +1,197 @@ +/* + * Video Capture driver for Freescale i.MX6 SOC + * + * Copyright (c) 2012-2014 Mentor Graphics Inc. + * Copyright 2004-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 _MX6_CAMIF_H +#define _MX6_CAMIF_H + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, 1, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + +/* + * There can be a maximum of 5 endpoints (and 5 sensors attached to those + * endpoints): 1 parallel endpoints, and 4 MIPI-CSI2 endpoints for each + * virtual channel. + */ +#define MX6CAM_MAX_ENDPOINTS 5 + +/* + * How long before no EOF interrupts cause a stream/preview + * restart, or a buffer dequeue timeout, in msec. The dequeue + * timeout should be longer than the EOF timeout. + */ +#define MX6CAM_EOF_TIMEOUT 1000 +#define MX6CAM_DQ_TIMEOUT 5000 + +/* + * How long to delay a restart on ADV718x status changes or NFB4EOF, + * in msec. + */ +#define MX6CAM_RESTART_DELAY 200 + +/* + * Internal subdev notifications + */ +#define MX6CAM_NFB4EOF_NOTIFY _IO('6', 0) +#define MX6CAM_EOF_TIMEOUT_NOTIFY _IO('6', 1) + +struct mx6cam_buffer { + struct vb2_buffer vb; /* v4l buffer must be first */ + struct list_head list; +}; + +static inline struct mx6cam_buffer *to_mx6cam_vb(struct vb2_buffer *vb) +{ + return container_of(vb, struct mx6cam_buffer, vb); +} + +struct mx6cam_pixfmt { + char *name; + u32 fourcc; + int depth; /* total bpp */ + int y_depth; /* depth of first Y plane for planar formats */ +}; + +struct mx6cam_dma_buf { + void *virt; + dma_addr_t phys; + unsigned long len; +}; + +/* + * A sensor's inputs parsed from v4l2_of_endpoint nodes in devicetree + */ +#define MX6CAM_MAX_INPUTS 16 + +struct mx6cam_sensor_input { + /* input values passed to s_routing */ + u32 value[MX6CAM_MAX_INPUTS]; + /* input capabilities (V4L2_IN_CAP_*) */ + u32 caps[MX6CAM_MAX_INPUTS]; + /* input names */ + char name[MX6CAM_MAX_INPUTS][32]; + + /* number of inputs */ + int num; + /* first and last input indexes from mx6cam perspective */ + int first; + int last; +}; + +/* + * Everything to describe a V4L2 endpoint. Endpoints are handled by + * one of the two CSI's, and connect to exactly one remote sensor. + */ +struct mx6cam_endpoint { + struct v4l2_of_endpoint ep; /* the parsed DT endpoint info */ + struct v4l2_subdev *sd; /* the remote sensor when attached + to this endpoint */ + struct mx6cam_sensor_input sensor_input; + struct ipu_csi_signal_cfg csi_sig_cfg; + int power_count; /* power use counter */ + int stream_count; /* stream use counter */ +}; + +struct mx6cam_ctx; + +struct mx6cam_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + struct device *dev; + + struct mutex mutex; + spinlock_t irqlock; + + /* 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; + /* derived from rotation, hflip, vflip controls */ + enum ipu_rotate_mode rot_mode; + + /* the format from sensor and from userland */ + struct v4l2_format user_fmt; + struct v4l2_mbus_framefmt sensor_fmt; + + /* + * win (from s_fmt_vid_cap_overlay) holds global alpha, chromakey, + * and interlace info for the preview overlay. + */ + struct v4l2_window win; + + /* + * info about the overlay framebuffer for preview (base address, + * width/height, pix format). + */ + struct v4l2_framebuffer fbuf; + + /* + * 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 */ + bool signal_locked; /* sensor signal lock */ + bool encoder_on; /* encode is on */ + bool preview_on; /* preview is on */ + bool using_ic; /* IC is being used for encode */ + + /* encoder, preview, and mipi csi2 subdevices */ + struct v4l2_subdev *encoder_sd; + struct v4l2_subdev *preview_sd; + struct v4l2_subdev *csi2_sd; + + /* sensor endpoints */ + struct mx6cam_endpoint eplist[MX6CAM_MAX_ENDPOINTS]; + struct mx6cam_endpoint *ep; /* the current active endpoint */ + int num_eps; + + /* + * the current open context that is doing IO (there can only + * be one allowed IO context at a time). + */ + struct mx6cam_ctx *io_ctx; + + /* parent IPU */ + struct ipu_soc *ipu; +}; + +struct mx6cam_ctx { + struct v4l2_fh fh; + struct mx6cam_dev *dev; + + struct vb2_alloc_ctx *alloc_ctx; + + /* streaming buffer queue */ + struct list_head ready_q; + + /* stream/preview stop and restart handling */ + struct work_struct restart_work; + struct work_struct stop_work; + struct timer_list restart_timer; + + /* is this ctx allowed to do IO */ + bool io_allowed; +}; + +struct v4l2_subdev *mx6cam_encoder_init(struct mx6cam_dev *dev); +struct v4l2_subdev *mx6cam_preview_init(struct mx6cam_dev *dev); + +#endif /* _MX6_CAMIF_H */ diff --git a/drivers/staging/media/imx6/capture/mx6-encode.c b/drivers/staging/media/imx6/capture/mx6-encode.c new file mode 100644 index 0000000..fad36aa --- /dev/null +++ b/drivers/staging/media/imx6/capture/mx6-encode.c @@ -0,0 +1,775 @@ +/* + * V4L2 Capture Encoder Subdev for Freescale i.MX6 SOC + * + * Copyright (c) 2012-2014 Mentor Graphics Inc. + * Copyright 2004-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 <linux/platform_data/imx-ipu-v3.h> +#include <media/imx6.h> +#include "mx6-camif.h" + +struct encoder_priv { + struct mx6cam_dev *dev; + struct v4l2_subdev sd; + + 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_irt *irt; + struct ipu_smfc *smfc; + struct ipu_csi *csi; + + /* active (undergoing DMA) buffers, one for each IPU buffer */ + struct mx6cam_buffer *active_frame[2]; + + struct mx6cam_dma_buf rot_buf[2]; + struct mx6cam_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 encoder off */ + struct completion last_eof_comp; +}; + +/* + * Update the CSI whole sensor and active windows, and initialize + * the CSI interface and muxes. + */ +static void encoder_setup_csi(struct encoder_priv *priv) +{ + struct mx6cam_dev *dev = priv->dev; + + ipu_csi_set_window_size(priv->csi, dev->crop.width, dev->crop.height); + ipu_csi_set_window_pos(priv->csi, dev->crop.left, dev->crop.top); + ipu_csi_init_interface(priv->csi, dev->crop_bounds.width, + dev->crop_bounds.height, &dev->ep->csi_sig_cfg); + + if (dev->ep->ep.bus_type == V4L2_MBUS_CSI2) + ipu_csi_set_mipi_datatype(priv->csi, dev->ep->ep.base.id, + &dev->ep->csi_sig_cfg); + + /* select either parallel or MIPI-CSI2 as input to our CSI */ + ipu_csi_set_src(priv->csi, dev->ep->ep.base.id, + dev->ep->ep.bus_type == V4L2_MBUS_CSI2); + /* set CSI destination to IC or direct to mem via SMFC */ + ipu_csi_set_dest(priv->csi, dev->using_ic); +} + +static void encoder_put_ipu_resources(struct encoder_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->irt)) + ipu_irt_put(priv->irt); + priv->irt = NULL; + + 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; + + if (!IS_ERR_OR_NULL(priv->csi)) + ipu_csi_put(priv->csi); + priv->csi = NULL; +} + +static int encoder_get_ipu_resources(struct encoder_priv *priv) +{ + struct mx6cam_dev *dev = priv->dev; + int csi_id, csi_ch_num, err; + + csi_id = dev->ep->ep.base.port; + priv->csi = ipu_csi_get(dev->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); + } + + if (dev->using_ic) { + priv->ic_enc = ipu_ic_get(dev->ipu, IC_TASK_ENCODER); + if (IS_ERR(priv->ic_enc)) { + v4l2_err(&priv->sd, "failed to get IC ENC\n"); + err = PTR_ERR(priv->ic_enc); + goto out; + } + + priv->irt = ipu_irt_get(dev->ipu); + if (IS_ERR(priv->irt)) { + v4l2_err(&priv->sd, "failed to get IRT\n"); + err = PTR_ERR(priv->irt); + goto out; + } + + priv->enc_ch = ipu_idmac_get(dev->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); + err = PTR_ERR(priv->enc_ch); + goto out; + } + + priv->enc_rot_in_ch = ipu_idmac_get(dev->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); + err = PTR_ERR(priv->enc_rot_in_ch); + goto out; + } + + priv->enc_rot_out_ch = ipu_idmac_get(dev->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); + err = PTR_ERR(priv->enc_rot_out_ch); + goto out; + } + } else { + priv->smfc = ipu_smfc_get(dev->ipu); + if (IS_ERR(priv->smfc)) { + v4l2_err(&priv->sd, "failed to get SMFC\n"); + err = PTR_ERR(priv->smfc); + goto out; + } + + /* + * Choose the direct CSI-->SMFC-->MEM channel corresponding + * to the IPU and CSI IDs. + */ + csi_ch_num = IPUV3_CHANNEL_CSI0 + + (ipu_get_num(dev->ipu) << 1) + csi_id; + + priv->enc_ch = ipu_idmac_get(dev->ipu, csi_ch_num); + if (IS_ERR(priv->enc_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + csi_ch_num); + err = PTR_ERR(priv->enc_ch); + goto out; + } + } + + return 0; +out: + encoder_put_ipu_resources(priv); + return err; +} + +static irqreturn_t encoder_eof_interrupt(int irq, void *dev_id) +{ + struct encoder_priv *priv = dev_id; + struct mx6cam_dev *dev = priv->dev; + struct mx6cam_ctx *ctx = dev->io_ctx; + struct mx6cam_buffer *frame; + struct ipuv3_channel *channel; + enum vb2_buffer_state state; + struct timeval cur_time; + unsigned long flags; + dma_addr_t phys; + + spin_lock_irqsave(&dev->irqlock, flags); + + /* timestamp and return the completed frame */ + frame = priv->active_frame[priv->buf_num]; + if (frame) { + do_gettimeofday(&cur_time); + frame->vb.v4l2_buf.timestamp = cur_time; + state = dev->signal_locked ? + 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; + } + + /* bump the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(MX6CAM_EOF_TIMEOUT)); + + if (!list_empty(&ctx->ready_q)) { + frame = list_entry(ctx->ready_q.next, + struct mx6cam_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 = (dev->rot_mode >= IPU_ROTATE_90_RIGHT) ? + 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 encoder_nfb4eof_interrupt(int irq, void *dev_id) +{ + struct encoder_priv *priv = dev_id; + struct mx6cam_dev *dev = priv->dev; + + v4l2_err(&priv->sd, "NFB4EOF\n"); + + /* + * It has been discovered that with rotation, encoder disable + * 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. + */ + if (!dev->using_ic) + v4l2_subdev_notify(&priv->sd, MX6CAM_NFB4EOF_NOTIFY, NULL); + + return IRQ_HANDLED; +} + +/* + * EOF timeout timer function. + */ +static void encoder_eof_timeout(unsigned long data) +{ + struct encoder_priv *priv = (struct encoder_priv *)data; + + v4l2_err(&priv->sd, "encoder EOF timeout\n"); + + v4l2_subdev_notify(&priv->sd, MX6CAM_EOF_TIMEOUT_NOTIFY, NULL); +} + +static void encoder_free_dma_buf(struct encoder_priv *priv, + struct mx6cam_dma_buf *buf) +{ + struct mx6cam_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 encoder_alloc_dma_buf(struct encoder_priv *priv, + struct mx6cam_dma_buf *buf, + int size) +{ + struct mx6cam_dev *dev = priv->dev; + + encoder_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 encoder_setup_channel(struct encoder_priv *priv, + struct ipuv3_channel *channel, + struct v4l2_pix_format *f, + enum ipu_rotate_mode rot_mode, + dma_addr_t addr0, dma_addr_t addr1, + bool rot_swap_width_height) +{ + struct mx6cam_dev *dev = priv->dev; + u32 width, height, stride; + unsigned int burst_size; + struct ipu_image image; + + if (dev->using_ic && rot_swap_width_height) { + width = f->height; + height = f->width; + } else { + width = f->width; + height = f->height; + } + stride = ipu_stride_to_bytes(width, f->pixelformat); + + 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 = f->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 % 16) ? 8 : 16; + + ipu_cpmem_set_burstsize(channel, burst_size); + + if (!dev->using_ic) { + int csi_id = ipu_csi_get_num(priv->csi); + bool passthrough; + + /* + * 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->ep->ep.bus_type != V4L2_MBUS_CSI2 && + dev->ep->csi_sig_cfg.data_width == + IPU_CSI_DATA_WIDTH_16); + + if (passthrough) + ipu_cpmem_set_format_passthrough(channel, 16); + + if (dev->ep->ep.bus_type == V4L2_MBUS_CSI2) + ipu_smfc_map(priv->smfc, channel, csi_id, + dev->ep->ep.base.id); + else + ipu_smfc_map(priv->smfc, channel, 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 iMx6 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_wmc(priv->smfc, channel, false, 0x01); + ipu_smfc_set_wmc(priv->smfc, channel, true, 0x02); + 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); + + ipu_smfc_set_burst_size(priv->smfc, channel, + burst_size, passthrough); + } + + if (rot_mode) + ipu_cpmem_set_rotation(channel, rot_mode); + + if (ipu_csi_is_interlaced(priv->csi) && channel == priv->enc_ch) + ipu_cpmem_interlaced_scan(channel, stride); + + if (dev->using_ic) { + 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 encoder_setup_rotation(struct encoder_priv *priv, + dma_addr_t phys0, dma_addr_t phys1, + struct v4l2_mbus_framefmt *inf, + struct v4l2_pix_format *outf) +{ + struct mx6cam_dev *dev = priv->dev; + enum ipu_color_space in_cs, out_cs; + int out_size = (outf->width * outf->height * + ipu_bits_per_pixel(outf->pixelformat)) >> 3; + int err; + + err = encoder_alloc_dma_buf(priv, &priv->underrun_buf, out_size); + if (err) { + v4l2_err(&priv->sd, "failed to alloc underrun_buf, %d\n", err); + return err; + } + + err = encoder_alloc_dma_buf(priv, &priv->rot_buf[0], out_size); + if (err) { + v4l2_err(&priv->sd, "failed to alloc rot_buf[0], %d\n", err); + goto free_underrun; + } + err = encoder_alloc_dma_buf(priv, &priv->rot_buf[1], out_size); + if (err) { + v4l2_err(&priv->sd, "failed to alloc rot_buf[1], %d\n", err); + goto free_rot0; + } + + in_cs = ipu_mbus_code_to_colorspace(inf->code); + out_cs = ipu_pixelformat_to_colorspace(outf->pixelformat); + + err = ipu_ic_task_init(priv->ic_enc, + inf->width, inf->height, + outf->height, outf->width, + in_cs, out_cs); + if (err) { + v4l2_err(&priv->sd, "ipu_ic_task_init failed, %d\n", err); + goto free_rot1; + } + + /* init the IC ENC-->MEM IDMAC channel */ + encoder_setup_channel(priv, priv->enc_ch, outf, + IPU_ROTATE_NONE, + priv->rot_buf[0].phys, + priv->rot_buf[1].phys, + true); + + /* init the MEM-->IC ENC ROT IDMAC channel */ + encoder_setup_channel(priv, priv->enc_rot_in_ch, outf, + dev->rot_mode, + priv->rot_buf[0].phys, + priv->rot_buf[1].phys, + true); + + /* init the destination IC ENC ROT-->MEM IDMAC channel */ + encoder_setup_channel(priv, priv->enc_rot_out_ch, outf, + IPU_ROTATE_NONE, + phys0, phys1, + false); + + /* now link IC PRP-->MEM to MEM-->IC PRP ROT */ + ipu_link_prp_enc_rot_enc(dev->ipu); + + /* enable the IC and IRT */ + ipu_ic_enable(priv->ic_enc); + ipu_irt_enable(priv->irt); + + /* 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: + encoder_free_dma_buf(priv, &priv->rot_buf[1]); +free_rot0: + encoder_free_dma_buf(priv, &priv->rot_buf[0]); +free_underrun: + encoder_free_dma_buf(priv, &priv->underrun_buf); + return err; +} + +static int encoder_setup_norotation(struct encoder_priv *priv, + dma_addr_t phys0, dma_addr_t phys1, + struct v4l2_mbus_framefmt *inf, + struct v4l2_pix_format *outf) +{ + struct mx6cam_dev *dev = priv->dev; + enum ipu_color_space in_cs, out_cs; + int out_size = (outf->width * outf->height * + ipu_bits_per_pixel(outf->pixelformat)) >> 3; + int err; + + err = encoder_alloc_dma_buf(priv, &priv->underrun_buf, out_size); + if (err) { + v4l2_err(&priv->sd, "failed to alloc underrun_buf, %d\n", err); + return err; + } + + in_cs = ipu_mbus_code_to_colorspace(inf->code); + out_cs = ipu_pixelformat_to_colorspace(outf->pixelformat); + + if (dev->using_ic) { + err = ipu_ic_task_init(priv->ic_enc, + inf->width, inf->height, + outf->width, outf->height, + in_cs, out_cs); + if (err) { + v4l2_err(&priv->sd, "ipu_ic_task_init failed, %d\n", + err); + goto free_underrun; + } + } + + /* init the IC PRP-->MEM IDMAC channel */ + encoder_setup_channel(priv, priv->enc_ch, outf, + dev->rot_mode, phys0, phys1, false); + + if (dev->using_ic) + ipu_ic_enable(priv->ic_enc); + else + ipu_smfc_enable(priv->smfc); + + /* 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); + + if (dev->using_ic) { + /* enable the IC ENCODE task */ + ipu_ic_task_enable(priv->ic_enc); + } + + return 0; + +free_underrun: + encoder_free_dma_buf(priv, &priv->underrun_buf); + return err; +} + +static int encoder_start(struct encoder_priv *priv) +{ + struct mx6cam_dev *dev = priv->dev; + struct mx6cam_ctx *ctx = dev->io_ctx; + struct v4l2_mbus_framefmt inf; + struct v4l2_pix_format outf; + struct mx6cam_buffer *frame, *tmp; + dma_addr_t phys[2]; + int i = 0, err; + + err = encoder_get_ipu_resources(priv); + if (err) + return err; + + phys[0] = phys[1] = 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; + } + + /* if preview is enabled it has already setup the CSI */ + if (!dev->preview_on) + encoder_setup_csi(priv); + + inf = dev->sensor_fmt; + inf.width = dev->crop.width; + inf.height = dev->crop.height; + outf = dev->user_fmt.fmt.pix; + + priv->buf_num = 0; + + if (dev->rot_mode >= IPU_ROTATE_90_RIGHT) + err = encoder_setup_rotation(priv, phys[0], phys[1], + &inf, &outf); + else + err = encoder_setup_norotation(priv, phys[0], phys[1], + &inf, &outf); + if (err) + goto out_put_ipu; + + priv->nfb4eof_irq = ipu_idmac_channel_irq(dev->ipu, + priv->enc_ch, + IPU_IRQ_NFB4EOF); + err = devm_request_irq(dev->dev, priv->nfb4eof_irq, + encoder_nfb4eof_interrupt, 0, + "mx6cam-enc-nfb4eof", priv); + if (err) { + v4l2_err(&priv->sd, + "Error registering encode NFB4EOF irq: %d\n", err); + goto out_put_ipu; + } + + if (dev->rot_mode >= IPU_ROTATE_90_RIGHT) + priv->eof_irq = ipu_idmac_channel_irq( + dev->ipu, priv->enc_rot_out_ch, IPU_IRQ_EOF); + else + priv->eof_irq = ipu_idmac_channel_irq( + dev->ipu, priv->enc_ch, IPU_IRQ_EOF); + + err = devm_request_irq(dev->dev, priv->eof_irq, + encoder_eof_interrupt, 0, + "mx6cam-enc-eof", priv); + if (err) { + v4l2_err(&priv->sd, + "Error registering encode eof irq: %d\n", err); + goto out_free_nfb4eof_irq; + } + + err = ipu_csi_enable(priv->csi); + if (err) { + v4l2_err(&priv->sd, "CSI enable error: %d\n", err); + goto out_free_eof_irq; + } + + /* start the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(MX6CAM_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: + encoder_put_ipu_resources(priv); + return err; +} + +static int encoder_stop(struct encoder_priv *priv) +{ + struct mx6cam_dev *dev = priv->dev; + struct mx6cam_buffer *frame; + struct timeval cur_time; + int i, ret; + + /* stop the EOF timeout timer */ + del_timer_sync(&priv->eof_timeout_timer); + + /* + * Mark next EOF interrupt as the last before encoder off, + * and then wait for interrupt handler to mark completion. + */ + init_completion(&priv->last_eof_comp); + priv->last_eof = true; + ret = wait_for_completion_timeout(&priv->last_eof_comp, + msecs_to_jiffies(MX6CAM_EOF_TIMEOUT)); + if (ret == 0) + v4l2_warn(&priv->sd, "wait last encode EOF timeout\n"); + + ipu_csi_disable(priv->csi); + + devm_free_irq(dev->dev, priv->eof_irq, priv); + devm_free_irq(dev->dev, priv->nfb4eof_irq, priv); + + /* disable IC tasks and the channels */ + if (dev->using_ic) + ipu_ic_task_disable(priv->ic_enc); + + ipu_idmac_disable_channel(priv->enc_ch); + if (dev->rot_mode >= IPU_ROTATE_90_RIGHT) { + ipu_idmac_disable_channel(priv->enc_rot_in_ch); + ipu_idmac_disable_channel(priv->enc_rot_out_ch); + } + + if (dev->rot_mode >= IPU_ROTATE_90_RIGHT) + ipu_unlink_prp_enc_rot_enc(dev->ipu); + + if (dev->using_ic) + ipu_ic_disable(priv->ic_enc); + else + ipu_smfc_disable(priv->smfc); + + if (dev->rot_mode >= IPU_ROTATE_90_RIGHT) + ipu_irt_disable(priv->irt); + + encoder_free_dma_buf(priv, &priv->rot_buf[0]); + encoder_free_dma_buf(priv, &priv->rot_buf[1]); + encoder_free_dma_buf(priv, &priv->underrun_buf); + + encoder_put_ipu_resources(priv); + + /* 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) { + do_gettimeofday(&cur_time); + frame->vb.v4l2_buf.timestamp = cur_time; + vb2_buffer_done(&frame->vb, VB2_BUF_STATE_ERROR); + } + } + + return 0; +} + +static int encoder_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct encoder_priv *priv = v4l2_get_subdevdata(sd); + + if (enable) + return encoder_start(priv); + else + return encoder_stop(priv); +} + +static struct v4l2_subdev_video_ops encoder_video_ops = { + .s_stream = encoder_s_stream, +}; + +static struct v4l2_subdev_ops encoder_subdev_ops = { + .video = &encoder_video_ops, +}; + +struct v4l2_subdev *mx6cam_encoder_init(struct mx6cam_dev *dev) +{ + struct encoder_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 = encoder_eof_timeout; + + v4l2_subdev_init(&priv->sd, &encoder_subdev_ops); + strlcpy(priv->sd.name, "mx6-camera-encoder", sizeof(priv->sd.name)); + v4l2_set_subdevdata(&priv->sd, priv); + + priv->dev = dev; + return &priv->sd; +} diff --git a/drivers/staging/media/imx6/capture/mx6-preview.c b/drivers/staging/media/imx6/capture/mx6-preview.c new file mode 100644 index 0000000..01d1e54 --- /dev/null +++ b/drivers/staging/media/imx6/capture/mx6-preview.c @@ -0,0 +1,748 @@ +/* + * V4L2 Preview Subdev for Freescale i.MX6 SOC + * + * Copyright (c) 2012-2014 Mentor Graphics Inc. + * Copyright 2004-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 <linux/platform_data/imx-ipu-v3.h> +#include <media/imx6.h> +#include "mx6-camif.h" + +struct preview_priv { + struct mx6cam_dev *dev; + struct v4l2_subdev sd; + struct v4l2_ctrl_handler ctrl_hdlr; + + struct ipuv3_channel *preview_ch; + struct ipuv3_channel *preview_rot_in_ch; + struct ipuv3_channel *preview_rot_out_ch; + struct ipu_ic *ic_vf; + struct ipu_irt *irt; + struct ipu_csi *csi; + + /* v4l2 controls */ + int rotation; /* degrees */ + bool hflip; + bool vflip; + /* derived from rotation, hflip, vflip controls */ + enum ipu_rotate_mode rot_mode; + + struct timer_list eof_timeout_timer; + int eof_irq; + int nfb4eof_irq; + + struct mx6cam_dma_buf rot_buf[2]; + int buf_num; + + bool preview_active; + bool last_eof; /* waiting for last EOF at preview off */ + struct completion last_eof_comp; +}; + +/* + * Update the CSI whole sensor and active windows, and initialize + * the CSI interface and muxes. + */ +static void preview_setup_csi(struct preview_priv *priv) +{ + struct mx6cam_dev *dev = priv->dev; + + ipu_csi_set_window_size(priv->csi, dev->crop.width, dev->crop.height); + ipu_csi_set_window_pos(priv->csi, dev->crop.left, dev->crop.top); + ipu_csi_init_interface(priv->csi, dev->crop_bounds.width, + dev->crop_bounds.height, &dev->ep->csi_sig_cfg); + + if (dev->ep->ep.bus_type == V4L2_MBUS_CSI2) + ipu_csi_set_mipi_datatype(priv->csi, dev->ep->ep.base.id, + &dev->ep->csi_sig_cfg); + + /* select either parallel or MIPI-CSI2 as input to our CSI */ + ipu_csi_set_src(priv->csi, dev->ep->ep.base.id, + dev->ep->ep.bus_type == V4L2_MBUS_CSI2); + /* set CSI destination to IC */ + ipu_csi_set_dest(priv->csi, true); +} + +static void preview_put_ipu_resources(struct preview_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->irt)) + ipu_irt_put(priv->irt); + priv->irt = NULL; + + if (!IS_ERR_OR_NULL(priv->ic_vf)) + ipu_ic_put(priv->ic_vf); + priv->ic_vf = NULL; + + if (!IS_ERR_OR_NULL(priv->preview_ch)) + ipu_idmac_put(priv->preview_ch); + priv->preview_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->preview_rot_in_ch)) + ipu_idmac_put(priv->preview_rot_in_ch); + priv->preview_rot_in_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->preview_rot_out_ch)) + ipu_idmac_put(priv->preview_rot_out_ch); + priv->preview_rot_out_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->csi)) + ipu_csi_put(priv->csi); + priv->csi = NULL; +} + +static int preview_get_ipu_resources(struct preview_priv *priv) +{ + struct mx6cam_dev *dev = priv->dev; + int csi_id, err; + + csi_id = dev->ep->ep.base.port; + priv->csi = ipu_csi_get(dev->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); + } + + priv->ic_vf = ipu_ic_get(dev->ipu, IC_TASK_VIEWFINDER); + if (IS_ERR(priv->ic_vf)) { + v4l2_err(&priv->sd, "failed to get IC VF\n"); + err = PTR_ERR(priv->ic_vf); + goto out; + } + + priv->irt = ipu_irt_get(dev->ipu); + if (IS_ERR(priv->irt)) { + v4l2_err(&priv->sd, "failed to get IRT\n"); + err = PTR_ERR(priv->irt); + goto out; + } + + priv->preview_ch = ipu_idmac_get(dev->ipu, + IPUV3_CHANNEL_IC_PRP_VF_MEM); + if (IS_ERR(priv->preview_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + IPUV3_CHANNEL_IC_PRP_VF_MEM); + err = PTR_ERR(priv->preview_ch); + goto out; + } + + priv->preview_rot_in_ch = ipu_idmac_get(dev->ipu, + IPUV3_CHANNEL_MEM_ROT_VF); + if (IS_ERR(priv->preview_rot_in_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + IPUV3_CHANNEL_MEM_ROT_ENC); + err = PTR_ERR(priv->preview_rot_in_ch); + goto out; + } + + priv->preview_rot_out_ch = ipu_idmac_get(dev->ipu, + IPUV3_CHANNEL_ROT_VF_MEM); + if (IS_ERR(priv->preview_rot_out_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + IPUV3_CHANNEL_ROT_ENC_MEM); + err = PTR_ERR(priv->preview_rot_out_ch); + goto out; + } + + return 0; +out: + preview_put_ipu_resources(priv); + return err; +} + +static irqreturn_t preview_eof_interrupt(int irq, void *dev_id) +{ + struct preview_priv *priv = dev_id; + + if (priv->last_eof) { + complete(&priv->last_eof_comp); + priv->last_eof = false; + return IRQ_HANDLED; + } + + /* bump the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(MX6CAM_EOF_TIMEOUT)); + + if (priv->rot_mode >= IPU_ROTATE_90_RIGHT) + ipu_idmac_select_buffer(priv->preview_rot_out_ch, + priv->buf_num); + else + ipu_idmac_select_buffer(priv->preview_ch, priv->buf_num); + + priv->buf_num ^= 1; + + return IRQ_HANDLED; +} + +static irqreturn_t preview_nfb4eof_interrupt(int irq, void *dev_id) +{ + struct preview_priv *priv = dev_id; + + v4l2_err(&priv->sd, "preview NFB4EOF\n"); + + /* + * It has been discovered that with rotation, preview disable + * 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 preview 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. + */ +#if 0 + v4l2_subdev_notify(&priv->sd, MX6CAM_NFB4EOF_NOTIFY, NULL); +#endif + + return IRQ_HANDLED; +} + +/* + * EOF timeout timer function. + */ +static void preview_eof_timeout(unsigned long data) +{ + struct preview_priv *priv = (struct preview_priv *)data; + + v4l2_err(&priv->sd, "preview EOF timeout\n"); + + v4l2_subdev_notify(&priv->sd, MX6CAM_EOF_TIMEOUT_NOTIFY, NULL); +} + +static void preview_free_dma_buf(struct preview_priv *priv, + struct mx6cam_dma_buf *buf) +{ + struct mx6cam_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 preview_alloc_dma_buf(struct preview_priv *priv, + struct mx6cam_dma_buf *buf, + int size) +{ + struct mx6cam_dev *dev = priv->dev; + + preview_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 preview_setup_channel(struct preview_priv *priv, + struct ipuv3_channel *channel, + struct v4l2_pix_format *f, + enum ipu_rotate_mode rot_mode, + dma_addr_t addr0, dma_addr_t addr1, + bool rot_swap_width_height) +{ + unsigned int burst_size; + u32 width, height, stride; + struct ipu_image image; + + if (rot_swap_width_height) { + width = f->height; + height = f->width; + } else { + width = f->width; + height = f->height; + } + stride = ipu_stride_to_bytes(width, f->pixelformat); + + 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 = f->pixelformat; + image.phys0 = addr0; + image.phys1 = addr1; + ipu_cpmem_set_image(channel, &image); + + if (rot_mode) + ipu_cpmem_set_rotation(channel, rot_mode); + + if (channel == priv->preview_rot_in_ch || + channel == priv->preview_rot_out_ch) { + burst_size = 8; + ipu_cpmem_set_block_mode(channel); + } else + burst_size = (width % 16) ? 8 : 16; + + ipu_cpmem_set_burstsize(channel, burst_size); + + if (ipu_csi_is_interlaced(priv->csi) && channel == priv->preview_ch) + ipu_cpmem_interlaced_scan(channel, stride); + + ipu_ic_task_idma_init(priv->ic_vf, channel, width, height, + burst_size, rot_mode); + + ipu_cpmem_set_axi_id(channel, 1); + + ipu_idmac_set_double_buffer(channel, true); +} + +static int preview_setup_rotation(struct preview_priv *priv, + dma_addr_t phys0, dma_addr_t phys1, + struct v4l2_mbus_framefmt *inf, + struct v4l2_pix_format *outf) +{ + struct mx6cam_dev *dev = priv->dev; + enum ipu_color_space in_cs, out_cs; + int out_size = (outf->width * outf->height * + ipu_bits_per_pixel(outf->pixelformat)) >> 3; + int err; + + err = preview_alloc_dma_buf(priv, &priv->rot_buf[0], out_size); + if (err) { + v4l2_err(&priv->sd, "failed to alloc rot_buf[0], %d\n", err); + return err; + } + err = preview_alloc_dma_buf(priv, &priv->rot_buf[1], out_size); + if (err) { + v4l2_err(&priv->sd, "failed to alloc rot_buf[1], %d\n", err); + goto free_rot0; + } + + in_cs = ipu_mbus_code_to_colorspace(inf->code); + out_cs = ipu_pixelformat_to_colorspace(outf->pixelformat); + + err = ipu_ic_task_init(priv->ic_vf, + inf->width, inf->height, + outf->height, outf->width, + in_cs, out_cs); + if (err) { + v4l2_err(&priv->sd, "ipu_ic_task_init failed, %d\n", err); + goto free_rot1; + } + + /* init the IC PREVIEW-->MEM IDMAC channel */ + preview_setup_channel(priv, priv->preview_ch, outf, + IPU_ROTATE_NONE, + priv->rot_buf[0].phys, + priv->rot_buf[1].phys, + true); + + /* init the MEM-->IC PREVIEW ROT IDMAC channel */ + preview_setup_channel(priv, priv->preview_rot_in_ch, outf, + priv->rot_mode, + priv->rot_buf[0].phys, + priv->rot_buf[1].phys, + true); + + /* init the destination IC PREVIEW ROT-->MEM IDMAC channel */ + preview_setup_channel(priv, priv->preview_rot_out_ch, outf, + IPU_ROTATE_NONE, + phys0, phys1, + false); + + /* now link IC PREVIEW-->MEM to MEM-->IC PREVIEW ROT */ + ipu_link_prpvf_rot_prpvf(dev->ipu); + + /* enable the IC and IRT */ + ipu_ic_enable(priv->ic_vf); + ipu_irt_enable(priv->irt); + + /* set buffers ready */ + ipu_idmac_select_buffer(priv->preview_ch, 0); + ipu_idmac_select_buffer(priv->preview_ch, 1); + ipu_idmac_select_buffer(priv->preview_rot_out_ch, 0); + ipu_idmac_select_buffer(priv->preview_rot_out_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->preview_ch); + ipu_idmac_enable_channel(priv->preview_rot_in_ch); + ipu_idmac_enable_channel(priv->preview_rot_out_ch); + + /* and finally enable the IC PREVIEW task */ + ipu_ic_task_enable(priv->ic_vf); + + return 0; + +free_rot1: + preview_free_dma_buf(priv, &priv->rot_buf[1]); +free_rot0: + preview_free_dma_buf(priv, &priv->rot_buf[0]); + return err; +} + +static int preview_setup_norotation(struct preview_priv *priv, + dma_addr_t phys0, dma_addr_t phys1, + struct v4l2_mbus_framefmt *inf, + struct v4l2_pix_format *outf) +{ + enum ipu_color_space in_cs, out_cs; + int err; + + in_cs = ipu_mbus_code_to_colorspace(inf->code); + out_cs = ipu_pixelformat_to_colorspace(outf->pixelformat); + + err = ipu_ic_task_init(priv->ic_vf, + inf->width, inf->height, + outf->width, outf->height, + in_cs, out_cs); + if (err) { + v4l2_err(&priv->sd, "ipu_ic_task_init failed, %d\n", err); + return err; + } + + /* init the IC PREVIEW-->MEM IDMAC channel */ + preview_setup_channel(priv, priv->preview_ch, outf, + priv->rot_mode, phys0, phys1, false); + + ipu_ic_enable(priv->ic_vf); + + /* set buffers ready */ + ipu_idmac_select_buffer(priv->preview_ch, 0); + ipu_idmac_select_buffer(priv->preview_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->preview_ch); + + /* and finally enable the IC PREVIEW task */ + ipu_ic_task_enable(priv->ic_vf); + + return 0; +} + +static int preview_start(struct preview_priv *priv) +{ + struct mx6cam_dev *dev = priv->dev; + struct v4l2_mbus_framefmt inf; + struct v4l2_pix_format outf; + dma_addr_t fb_buf; + int err = 0; + + if (priv->preview_active) { + v4l2_warn(&priv->sd, "preview already started\n"); + return 0; + } + + err = preview_get_ipu_resources(priv); + if (err) + return err; + + /* if encoder is enabled it has already setup the CSI */ + if (!dev->encoder_on) + preview_setup_csi(priv); + + inf = dev->sensor_fmt; + inf.width = dev->crop.width; + inf.height = dev->crop.height; + outf.width = dev->win.w.width; + outf.height = dev->win.w.height; + outf.pixelformat = dev->fbuf.fmt.pixelformat; + + fb_buf = (dma_addr_t)dev->fbuf.base; + + priv->buf_num = 0; + + if (priv->rot_mode >= IPU_ROTATE_90_RIGHT) + err = preview_setup_rotation(priv, fb_buf, fb_buf, + &inf, &outf); + else + err = preview_setup_norotation(priv, fb_buf, fb_buf, + &inf, &outf); + if (err) + goto out_put_ipu; + + priv->nfb4eof_irq = ipu_idmac_channel_irq(dev->ipu, + priv->preview_ch, + IPU_IRQ_NFB4EOF); + err = devm_request_irq(dev->dev, priv->nfb4eof_irq, + preview_nfb4eof_interrupt, 0, + "mx6cam-preview-nfb4eof", priv); + if (err) { + v4l2_err(&priv->sd, + "Error registering preview NFB4EOF irq: %d\n", err); + goto out_put_ipu; + } + + if (priv->rot_mode >= IPU_ROTATE_90_RIGHT) + priv->eof_irq = ipu_idmac_channel_irq(dev->ipu, + priv->preview_rot_out_ch, + IPU_IRQ_EOF); + else + priv->eof_irq = ipu_idmac_channel_irq(dev->ipu, + priv->preview_ch, + IPU_IRQ_EOF); + + err = devm_request_irq(dev->dev, priv->eof_irq, + preview_eof_interrupt, 0, + "mx6cam-preview-eof", priv); + if (err) { + v4l2_err(&priv->sd, + "Error registering preview eof irq: %d\n", err); + goto out_free_nfb4eof_irq; + } + + err = ipu_csi_enable(priv->csi); + if (err) { + v4l2_err(&priv->sd, "CSI enable error: %d\n", err); + goto out_free_eof_irq; + } + + priv->preview_active = true; + + /* start the VF EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(MX6CAM_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: + preview_put_ipu_resources(priv); + return err; +} + +static int preview_stop(struct preview_priv *priv) +{ + struct mx6cam_dev *dev = priv->dev; + int ret; + + if (!priv->preview_active) + return 0; + + /* stop the VF EOF timeout timer */ + del_timer_sync(&priv->eof_timeout_timer); + + /* + * Mark next EOF interrupt as the last before preview off, + * and then wait for interrupt handler to mark completion. + */ + init_completion(&priv->last_eof_comp); + priv->last_eof = true; + ret = wait_for_completion_timeout(&priv->last_eof_comp, + msecs_to_jiffies(MX6CAM_EOF_TIMEOUT)); + if (ret == 0) + v4l2_warn(&priv->sd, "wait last preview EOF timeout\n"); + + ipu_csi_disable(priv->csi); + + 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_vf); + + ipu_idmac_disable_channel(priv->preview_ch); + if (priv->rot_mode >= IPU_ROTATE_90_RIGHT) { + ipu_idmac_disable_channel(priv->preview_rot_in_ch); + ipu_idmac_disable_channel(priv->preview_rot_out_ch); + } + + if (priv->rot_mode >= IPU_ROTATE_90_RIGHT) + ipu_unlink_prpvf_rot_prpvf(dev->ipu); + + ipu_ic_disable(priv->ic_vf); + if (priv->rot_mode >= IPU_ROTATE_90_RIGHT) + ipu_irt_disable(priv->irt); + + preview_free_dma_buf(priv, &priv->rot_buf[0]); + preview_free_dma_buf(priv, &priv->rot_buf[1]); + + preview_put_ipu_resources(priv); + + priv->preview_active = false; + return 0; +} + +static int preview_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct preview_priv *priv = v4l2_get_subdevdata(sd); + + if (enable) + return preview_start(priv); + else + return preview_stop(priv); +} + +static void preview_unregistered(struct v4l2_subdev *sd) +{ + struct preview_priv *priv = v4l2_get_subdevdata(sd); + + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); +} + +/* Controls */ + +static int preview_set_rotation(struct preview_priv *priv, + int rotation, bool hflip, bool vflip) +{ + struct mx6cam_dev *dev = priv->dev; + enum ipu_rotate_mode rot_mode; + int ret; + + ret = ipu_degrees_to_rot_mode(&rot_mode, rotation, + hflip, vflip); + if (ret) + return ret; + + priv->rotation = rotation; + priv->hflip = hflip; + priv->vflip = vflip; + + if (rot_mode != priv->rot_mode) { + if (dev->preview_on) + preview_stop(priv); + priv->rot_mode = rot_mode; + if (dev->preview_on) + preview_start(priv); + } + + return 0; +} + +static int preview_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct preview_priv *priv = container_of(ctrl->handler, + struct preview_priv, + ctrl_hdlr); + struct mx6cam_dev *dev = priv->dev; + bool hflip, vflip; + int rotation; + + rotation = priv->rotation; + hflip = priv->hflip; + vflip = priv->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; + default: + + v4l2_err(&dev->v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + + return preview_set_rotation(priv, rotation, hflip, vflip); +} + +static const struct v4l2_ctrl_ops preview_ctrl_ops = { + .s_ctrl = preview_s_ctrl, +}; + +static int preview_setup_controls(struct preview_priv *priv) +{ + struct v4l2_ctrl_handler *hdlr = &priv->ctrl_hdlr; + int ret; + + v4l2_ctrl_handler_init(hdlr, 3); + + v4l2_ctrl_new_std(hdlr, &preview_ctrl_ops, V4L2_CID_HFLIP, + 0, 1, 1, 0); + v4l2_ctrl_new_std(hdlr, &preview_ctrl_ops, V4L2_CID_VFLIP, + 0, 1, 1, 0); + v4l2_ctrl_new_std(hdlr, &preview_ctrl_ops, V4L2_CID_ROTATE, + 0, 270, 90, 0); + + priv->sd.ctrl_handler = hdlr; + + if (hdlr->error) { + ret = hdlr->error; + v4l2_ctrl_handler_free(hdlr); + return ret; + } + + v4l2_ctrl_handler_setup(hdlr); + + return 0; +} + +static struct v4l2_subdev_core_ops preview_core_ops = { + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, +}; + +static struct v4l2_subdev_video_ops preview_video_ops = { + .s_stream = preview_s_stream, +}; + +static struct v4l2_subdev_ops preview_subdev_ops = { + .core = &preview_core_ops, + .video = &preview_video_ops, +}; + +static struct v4l2_subdev_internal_ops preview_internal_ops = { + .unregistered = preview_unregistered, +}; + +struct v4l2_subdev *mx6cam_preview_init(struct mx6cam_dev *dev) +{ + struct preview_priv *priv; + int ret; + + 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 = preview_eof_timeout; + + v4l2_subdev_init(&priv->sd, &preview_subdev_ops); + strlcpy(priv->sd.name, "mx6-camera-preview", sizeof(priv->sd.name)); + v4l2_set_subdevdata(&priv->sd, priv); + priv->sd.internal_ops = &preview_internal_ops; + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ret = preview_setup_controls(priv); + if (ret) { + kfree(priv); + return ERR_PTR(ret); + } + + priv->dev = dev; + return &priv->sd; +} diff --git a/include/media/imx6.h b/include/media/imx6.h new file mode 100644 index 0000000..232378b --- /dev/null +++ b/include/media/imx6.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2014 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_IMX6_H__ +#define __MEDIA_IMX6_H__ + +/* + * Analog decoder status change notifications + */ +#define DECODER_STATUS_CHANGE_NOTIFY _IO('7', 0) + +#endif -- 1.7.9.5 -- 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