From 4792d808dc79c5edf1afb06e197714f2680b2ffb Mon Sep 17 00:00:00 2001 From: "Guoniu.zhou" Date: Thu, 18 Apr 2019 15:25:49 +0800 Subject: [PATCH 1/9] media: dt-bindings: add bindings for i.MX8QXP/QM ISI driver Add bindings documentation for i.MX8QXP/QM ISI drivers. Signed-off-by: Guoniu.zhou --- .../devicetree/bindings/media/imx8-isi.txt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/imx8-isi.txt diff --git a/Documentation/devicetree/bindings/media/imx8-isi.txt b/Documentation/devicetree/bindings/media/imx8-isi.txt new file mode 100644 index 000000000000..7739121f0ca6 --- /dev/null +++ b/Documentation/devicetree/bindings/media/imx8-isi.txt @@ -0,0 +1,33 @@ +NXP Image Sensor Interface +======================== + +The Image Sensor Interface (ISI) is used to obtain the image data for +processing in its pipeline channels. Each pipeline processes the image +line from a configured source and performs one or more functions that +are configured by software, such as down scaling, color space conversion, +de-interlacing, alpha insertion, cropping and rotation (horizontal and +vertical). The processed image is stored into programmable memory locations. + +Required properties: +- compatible: should be "fsl,imx8-isi", where SoC can be one of imx8qxp, imx8qm +- reg: the register base and size for the device registers +- interrupts: the ISI interrupt, high level active +- clock-names: should be "per" +- clocks: the ISI AXI clock +- interface: specify ISI input, virtual channel and output, + + Input : 0-DC0, 1-DC1, 2-MIPI CSI0, 3-MIPI CSI1, 4-HDMI, 5-MEM + VCx : 0-VC0, 1-VC1, 2-VC2, 3-VC3, MIPI CSI only + Output: 0-DC0, 1-DC1, 2-MEM + +Example: + isi_0: isi@58100000 { + compatible = "fsl,imx8-isi"; + reg = <0x58100000 0x10000>; + interrupts = ; + interrupt-parent = <&gic>; + clocks = <&img_lpcg IMX_IMG_LPCG_PDMA0_CLK>; + clock-names = "per"; + power-domains = <&pd IMX_SC_R_ISI_CH0>; + interface = <2 0 2>; + }; From ade11ee3836976df1e218697bb4f5bd6bfd5d037 Mon Sep 17 00:00:00 2001 From: "Guoniu.zhou" Date: Mon, 21 Jan 2019 10:58:49 +0800 Subject: [PATCH 2/9] media: staging: imx: add isi driver support for imx8qm/qxp ISI is an image sensor interface which used to process image data from camera sensor and then transfer to DC or memory. Add V4L2 Capture subdev driver support for it. Signed-off-by: Guoniu.zhou [ Aisheng: Kconfig & Makefile update for a clean base ] Signed-off-by: Dong Aisheng --- drivers/staging/media/Makefile | 2 +- drivers/staging/media/imx/Kconfig | 24 + drivers/staging/media/imx/Makefile | 3 + drivers/staging/media/imx/imx8-isi-cap.c | 1667 +++++++++++++++++++++ drivers/staging/media/imx/imx8-isi-core.c | 221 +++ drivers/staging/media/imx/imx8-isi-core.h | 276 ++++ drivers/staging/media/imx/imx8-isi-hw.c | 651 ++++++++ drivers/staging/media/imx/imx8-isi-hw.h | 477 ++++++ 8 files changed, 3320 insertions(+), 1 deletion(-) create mode 100644 drivers/staging/media/imx/imx8-isi-cap.c create mode 100644 drivers/staging/media/imx/imx8-isi-core.c create mode 100644 drivers/staging/media/imx/imx8-isi-core.h create mode 100644 drivers/staging/media/imx/imx8-isi-hw.c create mode 100644 drivers/staging/media/imx/imx8-isi-hw.h diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 2f1711a8aeed..7cd981ec74e1 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_VIDEO_ALLEGRO_DVT) += allegro-dvt/ -obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/ +obj-$(CONFIG_VIDEO_IMX_CAPTURE) += imx/ obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ obj-$(CONFIG_VIDEO_SUNXI) += sunxi/ diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig index 8f1ae50a4abd..76f61762a005 100644 --- a/drivers/staging/media/imx/Kconfig +++ b/drivers/staging/media/imx/Kconfig @@ -31,3 +31,27 @@ config VIDEO_IMX7_CSI i.MX6UL/L or i.MX7. endmenu endif + +config VIDEO_IMX_CAPTURE + tristate "i.MX V4L2 media core driver" + depends on ARCH_MXC || COMPILE_TEST + depends on MEDIA_CONTROLLER && VIDEO_V4L2 + depends on VIDEO_V4L2_SUBDEV_API + depends on HAS_DMA + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Say yes here to enable support for video4linux media controller + driver for the i.MX5/6 SOC. + +if VIDEO_IMX_CAPTURE +menu "i.MX8QXP/QM Camera ISI/MIPI Features support" + +config IMX8_CAPTURE_DRIVER + tristate "IMX8 Camera Controller" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + default y + +endmenu +endif #VIDEO_IMX_CAPTURE diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile index 9bd9e873ba7c..5d5494c946b7 100644 --- a/drivers/staging/media/imx/Makefile +++ b/drivers/staging/media/imx/Makefile @@ -3,6 +3,8 @@ imx6-media-objs := imx-media-dev.o imx-media-internal-sd.o \ imx-ic-common.o imx-ic-prp.o imx-ic-prpencvf.o imx-media-vdic.o \ imx-media-csc-scaler.o +imx8-capture-objs := imx8-isi-core.o imx8-isi-cap.o imx8-isi-hw.o + imx-media-common-objs := imx-media-capture.o imx-media-dev-common.o \ imx-media-of.o imx-media-utils.o @@ -16,3 +18,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o +obj-$(CONFIG_IMX8_CAPTURE_DRIVER) += imx8-capture.o diff --git a/drivers/staging/media/imx/imx8-isi-cap.c b/drivers/staging/media/imx/imx8-isi-cap.c new file mode 100644 index 000000000000..35f3659e5643 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-cap.c @@ -0,0 +1,1667 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform + * + * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which + * used to process image from camera sensor to memory or DC + * + * Copyright (c) 2019 NXP Semiconductor + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx8-isi-hw.h" +#include "imx8-common.h" + +struct mxc_isi_fmt mxc_isi_out_formats[] = { + { + .name = "RGB565", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = { 16 }, + .color = MXC_ISI_OUT_FMT_RGB565, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_RGB565_1X16, + }, { + .name = "RGB24", + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = { 24 }, + .color = MXC_ISI_OUT_FMT_BGR32P, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + }, { + .name = "BGR24", + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = { 24 }, + .color = MXC_ISI_OUT_FMT_RGB32P, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_BGR888_1X24, + }, { + .name = "YUYV-16", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = { 16 }, + .color = MXC_ISI_OUT_FMT_YUV422_1P8P, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + }, { + .name = "YUV32 (X-Y-U-V)", + .fourcc = V4L2_PIX_FMT_YUV32, + .depth = { 32 }, + .color = MXC_ISI_OUT_FMT_YUV444_1P8, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_AYUV8_1X32, + }, { + .name = "NV12 (YUYV)", + .fourcc = V4L2_PIX_FMT_NV12, + .depth = { 8, 8 }, + .color = MXC_ISI_OUT_FMT_YUV420_2P8P, + .memplanes = 2, + .colplanes = 2, + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + }, { + .name = "YUV444M (Y-U-V)", + .fourcc = V4L2_PIX_FMT_YUV444M, + .depth = { 8, 8, 8 }, + .color = MXC_ISI_OUT_FMT_YUV444_3P8P, + .memplanes = 3, + .colplanes = 3, + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + }, { + .name = "xBGR32", + .fourcc = V4L2_PIX_FMT_XBGR32, + .depth = { 32 }, + .color = MXC_ISI_OUT_FMT_XRGB32, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + }, { + .name = "ABGR32", + .fourcc = V4L2_PIX_FMT_ABGR32, + .depth = { 32 }, + .color = MXC_ISI_OUT_FMT_ARGB32, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + } +}; + +/* + * Pixel link input format + */ +struct mxc_isi_fmt mxc_isi_src_formats[] = { + { + .name = "RGB32", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = { 32 }, + .memplanes = 1, + .colplanes = 1, + }, { + .name = "YUV32 (X-Y-U-V)", + .fourcc = V4L2_PIX_FMT_YUV32, + .depth = { 32 }, + .memplanes = 1, + .colplanes = 1, + } +}; + +struct mxc_isi_fmt *mxc_isi_get_format(unsigned int index) +{ + return &mxc_isi_out_formats[index]; +} + +/* + * lookup mxc_isi color format by fourcc or media bus format + */ +struct mxc_isi_fmt *mxc_isi_find_format(const u32 *pixelformat, + const u32 *mbus_code, int index) +{ + struct mxc_isi_fmt *fmt, *def_fmt = NULL; + unsigned int i; + int id = 0; + + if (index >= (int)ARRAY_SIZE(mxc_isi_out_formats)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) { + fmt = &mxc_isi_out_formats[i]; + if (pixelformat && fmt->fourcc == *pixelformat) + return fmt; + if (mbus_code && fmt->mbus_code == *mbus_code) + return fmt; + if (index == id) + def_fmt = fmt; + id++; + } + return def_fmt; +} + +struct mxc_isi_fmt *mxc_isi_get_src_fmt(struct v4l2_subdev_format *sd_fmt) +{ + u32 index; + + /* two fmt RGB32 and YUV444 from pixellink */ + if (sd_fmt->format.code == MEDIA_BUS_FMT_YUYV8_1X16 || + sd_fmt->format.code == MEDIA_BUS_FMT_YVYU8_2X8 || + sd_fmt->format.code == MEDIA_BUS_FMT_AYUV8_1X32 || + sd_fmt->format.code == MEDIA_BUS_FMT_UYVY8_2X8) + index = 1; + else + index = 0; + return &mxc_isi_src_formats[index]; +} + +static inline struct mxc_isi_buffer *to_isi_buffer(struct vb2_v4l2_buffer *v4l2_buf) +{ + return container_of(v4l2_buf, struct mxc_isi_buffer, v4l2_buf); +} + +/* + * mxc_isi_pipeline_enable() - Enable streaming on a pipeline + */ +static int mxc_isi_pipeline_enable(struct mxc_isi_dev *mxc_isi, bool enable) +{ + struct device *dev = &mxc_isi->pdev->dev; + struct media_entity *entity = &mxc_isi->isi_cap.vdev.entity; + struct media_device *mdev = entity->graph_obj.mdev; + struct media_graph graph; + struct v4l2_subdev *subdev; + int ret = 0; + + mutex_lock(&mdev->graph_mutex); + + ret = media_graph_walk_init(&graph, entity->graph_obj.mdev); + if (ret) { + mutex_unlock(&mdev->graph_mutex); + return ret; + } + media_graph_walk_start(&graph, entity); + + while ((entity = media_graph_walk_next(&graph))) { + if (!entity) { + dev_dbg(dev, "entity is NULL\n"); + continue; + } + + if (!is_media_entity_v4l2_subdev(entity)) { + dev_dbg(dev, "%s is no v4l2 subdev\n", entity->name); + continue; + } + + subdev = media_entity_to_v4l2_subdev(entity); + if (!subdev) { + dev_dbg(dev, "%s subdev is NULL\n", entity->name); + continue; + } + + ret = v4l2_subdev_call(subdev, video, s_stream, enable); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_err(dev, "subdev %s s_stream failed\n", subdev->name); + break; + } + } + mutex_unlock(&mdev->graph_mutex); + media_graph_walk_cleanup(&graph); + + return ret; +} + +static int mxc_isi_update_buf_paddr(struct mxc_isi_buffer *buf, int memplanes) +{ + struct frame_addr *paddr = &buf->paddr; + struct vb2_buffer *vb2 = &buf->v4l2_buf.vb2_buf; + int ret = 0; + + paddr->cb = 0; + paddr->cr = 0; + + switch (memplanes) { + case 3: + paddr->cr = vb2_dma_contig_plane_dma_addr(vb2, 2); + case 2: + paddr->cb = vb2_dma_contig_plane_dma_addr(vb2, 1); + case 1: + paddr->y = vb2_dma_contig_plane_dma_addr(vb2, 0); + break; + default: + return -EINVAL; + } + + return ret; +} + +void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi) +{ + struct device *dev = &mxc_isi->pdev->dev; + struct mxc_isi_buffer *buf; + struct vb2_buffer *vb2; + + if (list_empty(&mxc_isi->isi_cap.out_active)) { + dev_warn(dev, "trying to access empty active list\n"); + return; + } + + buf = list_first_entry(&mxc_isi->isi_cap.out_active, + struct mxc_isi_buffer, list); + + if (buf->discard) { + list_move_tail(mxc_isi->isi_cap.out_active.next, + &mxc_isi->isi_cap.out_discard); + } else { + vb2 = &buf->v4l2_buf.vb2_buf; + list_del_init(&buf->list); + buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE); + } + + mxc_isi->isi_cap.frame_count++; + + if (list_empty(&mxc_isi->isi_cap.out_pending)) { + if (list_empty(&mxc_isi->isi_cap.out_discard)) { + dev_warn(dev, "trying to access empty discard list\n"); + return; + } + + buf = list_first_entry(&mxc_isi->isi_cap.out_discard, + struct mxc_isi_buffer, list); + buf->v4l2_buf.sequence = mxc_isi->isi_cap.frame_count; + mxc_isi_channel_set_outbuf(mxc_isi, buf); + list_move_tail(mxc_isi->isi_cap.out_discard.next, + &mxc_isi->isi_cap.out_active); + return; + } + + /* ISI channel output buffer */ + buf = list_first_entry(&mxc_isi->isi_cap.out_pending, + struct mxc_isi_buffer, list); + buf->v4l2_buf.sequence = mxc_isi->isi_cap.frame_count; + mxc_isi_channel_set_outbuf(mxc_isi, buf); + vb2 = &buf->v4l2_buf.vb2_buf; + vb2->state = VB2_BUF_STATE_ACTIVE; + list_move_tail(mxc_isi->isi_cap.out_pending.next, + &mxc_isi->isi_cap.out_active); +} + +static int cap_vb2_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mxc_isi_dev *mxc_isi = q->drv_priv; + struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_fmt *fmt = dst_f->fmt; + unsigned long wh; + int i; + + if (!fmt) + return -EINVAL; + + for (i = 0; i < fmt->memplanes; i++) + alloc_devs[i] = &mxc_isi->pdev->dev; + + wh = dst_f->width * dst_f->height; + + *num_planes = fmt->memplanes; + + for (i = 0; i < fmt->memplanes; i++) { + unsigned int size = (wh * fmt->depth[i]) / 8; + + if (i == 1 && fmt->fourcc == V4L2_PIX_FMT_NV12) + size >>= 1; + sizes[i] = max_t(u32, size, dst_f->sizeimage[i]); + } + dev_dbg(&mxc_isi->pdev->dev, "%s, buf_n=%d, size=%d\n", + __func__, *num_buffers, sizes[0]); + + return 0; +} + +static int cap_vb2_buffer_prepare(struct vb2_buffer *vb2) +{ + struct vb2_queue *q = vb2->vb2_queue; + struct mxc_isi_dev *mxc_isi = q->drv_priv; + struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + int i; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + if (!mxc_isi->isi_cap.dst_f.fmt) + return -EINVAL; + + for (i = 0; i < dst_f->fmt->memplanes; i++) { + unsigned long size = dst_f->sizeimage[i]; + + if (vb2_plane_size(vb2, i) < size) { + v4l2_err(&mxc_isi->isi_cap.vdev, + "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb2, i), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb2, i, size); + } + + return 0; +} + +static void cap_vb2_buffer_queue(struct vb2_buffer *vb2) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb2); + struct mxc_isi_buffer *buf = to_isi_buffer(v4l2_buf); + struct mxc_isi_dev *mxc_isi = vb2_get_drv_priv(vb2->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&mxc_isi->slock, flags); + + mxc_isi_update_buf_paddr(buf, mxc_isi->isi_cap.dst_f.fmt->mdataplanes); + list_add_tail(&buf->list, &mxc_isi->isi_cap.out_pending); + + spin_unlock_irqrestore(&mxc_isi->slock, flags); +} + +static int cap_vb2_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mxc_isi_dev *mxc_isi = q->drv_priv; + struct mxc_isi_buffer *buf; + struct vb2_buffer *vb2; + unsigned long flags; + int i, j; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + if (count < 2) + return -ENOBUFS; + + /* Create a buffer for discard operation */ + for (i = 0; i < mxc_isi->pix.num_planes; i++) { + mxc_isi->discard_size[i] = mxc_isi->isi_cap.dst_f.sizeimage[i]; + mxc_isi->discard_buffer[i] = + dma_alloc_coherent(&mxc_isi->pdev->dev, + PAGE_ALIGN(mxc_isi->discard_size[i]), + &mxc_isi->discard_buffer_dma[i], + GFP_DMA | GFP_KERNEL); + if (!mxc_isi->discard_buffer[i]) { + for (j = 0; j < i; j++) { + dma_free_coherent(&mxc_isi->pdev->dev, + mxc_isi->discard_size[j], + mxc_isi->discard_buffer[j], + mxc_isi->discard_buffer_dma[j]); + dev_err(&mxc_isi->pdev->dev, + "alloc dma buffer(%d) fail\n", j); + } + return -ENOMEM; + } + dev_dbg(&mxc_isi->pdev->dev, + "%s: num_plane=%d discard_size=%d discard_buffer=%p\n" + , __func__, i, + (int)mxc_isi->discard_size[i], + mxc_isi->discard_buffer[i]); + } + + spin_lock_irqsave(&mxc_isi->slock, flags); + + /* add two list member to out_discard list head */ + mxc_isi->buf_discard[0].discard = true; + list_add_tail(&mxc_isi->buf_discard[0].list, + &mxc_isi->isi_cap.out_discard); + + mxc_isi->buf_discard[1].discard = true; + list_add_tail(&mxc_isi->buf_discard[1].list, + &mxc_isi->isi_cap.out_discard); + + /* ISI channel output buffer 1 */ + buf = list_first_entry(&mxc_isi->isi_cap.out_discard, + struct mxc_isi_buffer, list); + buf->v4l2_buf.sequence = 0; + vb2 = &buf->v4l2_buf.vb2_buf; + vb2->state = VB2_BUF_STATE_ACTIVE; + mxc_isi_channel_set_outbuf(mxc_isi, buf); + list_move_tail(mxc_isi->isi_cap.out_discard.next, + &mxc_isi->isi_cap.out_active); + + /* ISI channel output buffer 2 */ + buf = list_first_entry(&mxc_isi->isi_cap.out_pending, + struct mxc_isi_buffer, list); + buf->v4l2_buf.sequence = 1; + vb2 = &buf->v4l2_buf.vb2_buf; + vb2->state = VB2_BUF_STATE_ACTIVE; + mxc_isi_channel_set_outbuf(mxc_isi, buf); + list_move_tail(mxc_isi->isi_cap.out_pending.next, + &mxc_isi->isi_cap.out_active); + + /* Clear frame count */ + mxc_isi->isi_cap.frame_count = 1; + spin_unlock_irqrestore(&mxc_isi->slock, flags); + + return 0; +} + +static void cap_vb2_stop_streaming(struct vb2_queue *q) +{ + struct mxc_isi_dev *mxc_isi = q->drv_priv; + struct mxc_isi_buffer *buf, *tmp; + unsigned long flags; + int i; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + mxc_isi_channel_disable(mxc_isi); + + spin_lock_irqsave(&mxc_isi->slock, flags); + + while (!list_empty(&mxc_isi->isi_cap.out_active)) { + buf = list_entry(mxc_isi->isi_cap.out_active.next, + struct mxc_isi_buffer, list); + list_del(&buf->list); + if (buf->discard) + continue; + + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); + } + + while (!list_empty(&mxc_isi->isi_cap.out_pending)) { + buf = list_entry(mxc_isi->isi_cap.out_pending.next, + struct mxc_isi_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); + } + + while (!list_empty(&mxc_isi->isi_cap.out_discard)) { + buf = list_entry(mxc_isi->isi_cap.out_discard.next, + struct mxc_isi_buffer, list); + list_del(&buf->list); + } + + list_for_each_entry_safe(buf, tmp, &mxc_isi->isi_cap.out_active, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); + } + + list_for_each_entry_safe(buf, tmp, &mxc_isi->isi_cap.out_pending, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); + } + + INIT_LIST_HEAD(&mxc_isi->isi_cap.out_active); + INIT_LIST_HEAD(&mxc_isi->isi_cap.out_pending); + INIT_LIST_HEAD(&mxc_isi->isi_cap.out_discard); + + spin_unlock_irqrestore(&mxc_isi->slock, flags); + + for (i = 0; i < mxc_isi->pix.num_planes; i++) + dma_free_coherent(&mxc_isi->pdev->dev, + mxc_isi->discard_size[i], + mxc_isi->discard_buffer[i], + mxc_isi->discard_buffer_dma[i]); +} + +static struct vb2_ops mxc_cap_vb2_qops = { + .queue_setup = cap_vb2_queue_setup, + .buf_prepare = cap_vb2_buffer_prepare, + .buf_queue = cap_vb2_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = cap_vb2_start_streaming, + .stop_streaming = cap_vb2_stop_streaming, +}; + +/* + * V4L2 controls handling + */ +static inline struct mxc_isi_dev *ctrl_to_mxc_isi(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mxc_isi_dev, ctrls.handler); +} + +static int mxc_isi_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mxc_isi_dev *mxc_isi = ctrl_to_mxc_isi(ctrl); + unsigned long flags; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + spin_lock_irqsave(&mxc_isi->slock, flags); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctrl->val < 0) + return -EINVAL; + mxc_isi->hflip = (ctrl->val > 0) ? 1 : 0; + break; + + case V4L2_CID_VFLIP: + if (ctrl->val < 0) + return -EINVAL; + mxc_isi->vflip = (ctrl->val > 0) ? 1 : 0; + break; + + case V4L2_CID_ALPHA_COMPONENT: + if (ctrl->val < 0 || ctrl->val > 255) + return -EINVAL; + mxc_isi->alpha = ctrl->val; + mxc_isi->alphaen = 1; + break; + + default: + dev_err(&mxc_isi->pdev->dev, + "%s: Not support %d CID\n", __func__, ctrl->id); + return -EINVAL; + } + + spin_unlock_irqrestore(&mxc_isi->slock, flags); + return 0; +} + +static const struct v4l2_ctrl_ops mxc_isi_ctrl_ops = { + .s_ctrl = mxc_isi_s_ctrl, +}; + +int mxc_isi_ctrls_create(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_ctrls *ctrls = &mxc_isi->ctrls; + struct v4l2_ctrl_handler *handler = &ctrls->handler; + + if (mxc_isi->ctrls.ready) + return 0; + + v4l2_ctrl_handler_init(handler, 4); + + ctrls->hflip = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + ctrls->alpha = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, + 0, 0xff, 1, 0); + + if (!handler->error) + ctrls->ready = true; + + return handler->error; +} + +void mxc_isi_ctrls_delete(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_ctrls *ctrls = &mxc_isi->ctrls; + + if (ctrls->ready) { + v4l2_ctrl_handler_free(&ctrls->handler); + ctrls->ready = false; + ctrls->alpha = NULL; + } +} + +static struct media_pad *mxc_isi_get_remote_source_pad(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_cap_dev *isi_cap = &mxc_isi->isi_cap; + struct v4l2_subdev *subdev = &isi_cap->sd; + struct media_pad *sink_pad, *source_pad; + int i; + + while (1) { + source_pad = NULL; + for (i = 0; i < subdev->entity.num_pads; i++) { + sink_pad = &subdev->entity.pads[i]; + + if (sink_pad->flags & MEDIA_PAD_FL_SINK) { + source_pad = media_entity_remote_pad(sink_pad); + if (source_pad) + break; + } + } + /* return first pad point in the loop */ + return source_pad; + } + + if (i == subdev->entity.num_pads) + v4l2_err(subdev, "(%d): No remote pad found!\n", __LINE__); + + return NULL; +} + +static struct v4l2_subdev *mxc_get_remote_subdev(struct mxc_isi_dev *mxc_isi, + const char * const label) +{ + struct media_pad *source_pad; + struct v4l2_subdev *sen_sd; + + /* Get remote source pad */ + source_pad = mxc_isi_get_remote_source_pad(mxc_isi); + if (!source_pad) { + v4l2_err(&mxc_isi->isi_cap.sd, + "%s, No remote pad found!\n", label); + return NULL; + } + + /* Get remote source pad subdev */ + sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); + if (!sen_sd) { + v4l2_err(&mxc_isi->isi_cap.sd, + "%s, No remote subdev found!\n", label); + return NULL; + } + + return sen_sd; +} + +static int mxc_isi_capture_open(struct file *file) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct v4l2_subdev *sd; + struct device *dev = &mxc_isi->pdev->dev; + int ret = -EBUSY; + + dev_dbg(&mxc_isi->pdev->dev, "ISI(%d)\n", mxc_isi->id); + + if (mxc_isi->is_m2m) { + v4l2_err(&mxc_isi->isi_cap.sd, + "ISI channel[%d] is busy\n", mxc_isi->id); + return -EBUSY; + } + atomic_inc(&mxc_isi->open_count); + mxc_isi->is_m2m = 0; + + sd = mxc_get_remote_subdev(mxc_isi, __func__); + if (!sd) + goto fail; + + mutex_lock(&mxc_isi->lock); + ret = v4l2_fh_open(file); + mutex_unlock(&mxc_isi->lock); + + pm_runtime_get_sync(dev); + + ret = v4l2_subdev_call(sd, core, s_power, 1); + if (ret) { + v4l2_err(&mxc_isi->isi_cap.sd, "Call subdev s_power fail!\n"); + pm_runtime_put(dev); + goto fail; + } + + return 0; + +fail: + atomic_dec(&mxc_isi->open_count); + return -EINVAL; +} + +static int mxc_isi_capture_release(struct file *file) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct v4l2_subdev *sd; + struct device *dev = &mxc_isi->pdev->dev; + int ret = -1; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + sd = mxc_get_remote_subdev(mxc_isi, __func__); + if (!sd) + goto label; + + mutex_lock(&mxc_isi->lock); + ret = _vb2_fop_release(file, NULL); + if (ret) { + v4l2_err(&mxc_isi->isi_cap.sd, "%s fail\n", __func__); + mutex_unlock(&mxc_isi->lock); + goto label; + } + mutex_unlock(&mxc_isi->lock); + + if (atomic_read(&mxc_isi->open_count) > 0 && + atomic_dec_and_test(&mxc_isi->open_count)) + mxc_isi_channel_deinit(mxc_isi); + + ret = v4l2_subdev_call(sd, core, s_power, 0); + if (ret < 0 && ret != -ENOIOCTLCMD) { + v4l2_err(&mxc_isi->isi_cap.sd, "%s s_power fail\n", __func__); + goto label; + } + +label: + pm_runtime_put(dev); + return (ret) ? ret : 0; +} + +static const struct v4l2_file_operations mxc_isi_capture_fops = { + .owner = THIS_MODULE, + .open = mxc_isi_capture_open, + .release = mxc_isi_capture_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* + * The video node ioctl operations + */ +static int mxc_isi_cap_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + + strlcpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver)); + strlcpy(cap->card, MXC_ISI_DRIVER_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d", + dev_name(&mxc_isi->pdev->dev), mxc_isi->id); + + cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int mxc_isi_cap_enum_fmt_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_fmt *fmt; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + if (f->index >= (int)ARRAY_SIZE(mxc_isi_out_formats)) + return -EINVAL; + + fmt = &mxc_isi_out_formats[f->index]; + if (!fmt) + return -EINVAL; + + strncpy(f->description, fmt->name, sizeof(f->description) - 1); + + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int mxc_isi_cap_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + int i; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + pix->width = dst_f->o_width; + pix->height = dst_f->o_height; + pix->field = V4L2_FIELD_NONE; + pix->pixelformat = dst_f->fmt->fourcc; + pix->colorspace = V4L2_COLORSPACE_JPEG; + pix->num_planes = dst_f->fmt->memplanes; + + for (i = 0; i < pix->num_planes; ++i) { + pix->plane_fmt[i].bytesperline = dst_f->bytesperline[i]; + pix->plane_fmt[i].sizeimage = dst_f->sizeimage[i]; + } + + return 0; +} + +static int mxc_isi_cap_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_fmt *fmt; + int i; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) { + fmt = &mxc_isi_out_formats[i]; + if (fmt->fourcc == pix->pixelformat) + break; + } + + if (i >= ARRAY_SIZE(mxc_isi_out_formats)) { + v4l2_err(&mxc_isi->isi_cap.sd, "format(%.4s) is not support!\n", + (char *)&pix->pixelformat); + return -EINVAL; + } + + if (pix->width <= 0 || pix->height <= 0) { + v4l2_err(&mxc_isi->isi_cap.sd, "%s, W/H=(%d, %d) is not valid\n" + , __func__, pix->width, pix->height); + return -EINVAL; + } + + return 0; +} + +/* Update input frame size and formate */ +static int mxc_isi_source_fmt_init(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_frame *src_f = &mxc_isi->isi_cap.src_f; + struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct v4l2_subdev_format src_fmt; + struct media_pad *source_pad; + struct v4l2_subdev *src_sd; + int ret; + + source_pad = mxc_isi_get_remote_source_pad(mxc_isi); + if (!source_pad) { + v4l2_err(&mxc_isi->isi_cap.sd, + "%s, No remote pad found!\n", __func__); + return -EINVAL; + } + + src_sd = mxc_get_remote_subdev(mxc_isi, __func__); + if (!src_sd) + return -EINVAL; + + src_fmt.pad = source_pad->index; + src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + src_fmt.format.code = MEDIA_BUS_FMT_UYVY8_2X8; + src_fmt.format.width = dst_f->width; + src_fmt.format.height = dst_f->height; + ret = v4l2_subdev_call(src_sd, pad, set_fmt, NULL, &src_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) { + v4l2_err(&mxc_isi->isi_cap.sd, "set remote fmt fail!\n"); + return -EINVAL; + } + + memset(&src_fmt, 0, sizeof(src_fmt)); + src_fmt.pad = source_pad->index; + src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(src_sd, pad, get_fmt, NULL, &src_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) { + v4l2_err(&mxc_isi->isi_cap.sd, "get remote fmt fail!\n"); + return -EINVAL; + } + + /* Pixel link master will transfer format to RGB32 or YUV32 */ + src_f->fmt = mxc_isi_get_src_fmt(&src_fmt); + + set_frame_bounds(src_f, src_fmt.format.width, src_fmt.format.height); + + if (dst_f->width > src_f->width || dst_f->height > src_f->height) { + dev_err(&mxc_isi->pdev->dev, + "%s: src:(%d,%d), dst:(%d,%d) Not support upscale\n", + __func__, + src_f->width, src_f->height, + dst_f->width, dst_f->height); + return -EINVAL; + } + + return 0; +} + +static int mxc_isi_cap_s_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_fmt *fmt; + int bpl; + int i; + + /* Step1: Check format with output support format list. + * Step2: Update output frame information. + * Step3: Checkout the format whether is supported by remote subdev + * Step3.1: If Yes, call remote subdev set_fmt. + * Step3.2: If NO, call remote subdev get_fmt. + * Step4: Update input frame information. + * Step5: Update mxc isi channel configuration. + */ + + dev_dbg(&mxc_isi->pdev->dev, "%s, fmt=0x%X\n", __func__, pix->pixelformat); + if (vb2_is_busy(&mxc_isi->isi_cap.vb2_q)) + return -EBUSY; + + /* Check out put format */ + for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) { + fmt = &mxc_isi_out_formats[i]; + if (pix && fmt->fourcc == pix->pixelformat) + break; + } + + if (i >= ARRAY_SIZE(mxc_isi_out_formats)) { + dev_dbg(&mxc_isi->pdev->dev, + "format(%.4s) is not support!\n", (char *)&pix->pixelformat); + return -EINVAL; + } + + /* update out put frame size and formate */ + if (pix->height <= 0 || pix->width <= 0) + return -EINVAL; + + dst_f->fmt = fmt; + dst_f->height = pix->height; + dst_f->width = pix->width; + + pix->num_planes = fmt->memplanes; + + for (i = 0; i < pix->num_planes; i++) { + bpl = pix->plane_fmt[i].bytesperline; + + if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width) + pix->plane_fmt[i].bytesperline = + (pix->width * fmt->depth[i]) >> 3; + + if (pix->plane_fmt[i].sizeimage == 0) { + if ((i == 1) && (pix->pixelformat == V4L2_PIX_FMT_NV12)) + pix->plane_fmt[i].sizeimage = + (pix->width * (pix->height >> 1) * fmt->depth[i] >> 3); + else + pix->plane_fmt[i].sizeimage = + (pix->width * pix->height * fmt->depth[i] >> 3); + } + } + + if (pix->num_planes > 1) { + for (i = 0; i < pix->num_planes; i++) { + dst_f->bytesperline[i] = pix->plane_fmt[i].bytesperline; + dst_f->sizeimage[i] = pix->plane_fmt[i].sizeimage; + } + } else { + dst_f->bytesperline[0] = dst_f->width * dst_f->fmt->depth[0] / 8; + dst_f->sizeimage[0] = dst_f->height * dst_f->bytesperline[0]; + } + + memcpy(&mxc_isi->pix, pix, sizeof(*pix)); + set_frame_bounds(dst_f, pix->width, pix->height); + + return 0; +} + +static int mxc_isi_config_parm(struct mxc_isi_dev *mxc_isi) +{ + int ret; + + ret = mxc_isi_source_fmt_init(mxc_isi); + if (ret < 0) + return -EINVAL; + + mxc_isi_channel_init(mxc_isi); + mxc_isi_channel_config(mxc_isi); + + return 0; +} + +static int mxc_isi_cap_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + int ret; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + ret = mxc_isi_config_parm(mxc_isi); + if (ret < 0) + return -EINVAL; + + ret = vb2_ioctl_streamon(file, priv, type); + mxc_isi_channel_enable(mxc_isi); + mxc_isi_pipeline_enable(mxc_isi, 1); + + mxc_isi->is_streaming = 1; + + return ret; +} + +static int mxc_isi_cap_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + int ret; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + mxc_isi_pipeline_enable(mxc_isi, 0); + mxc_isi_channel_disable(mxc_isi); + ret = vb2_ioctl_streamoff(file, priv, type); + + mxc_isi->is_streaming = 0; + + return ret; +} + +static int mxc_isi_cap_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_frame *f = &mxc_isi->isi_cap.src_f; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + f = &mxc_isi->isi_cap.dst_f; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = f->o_width; + s->r.height = f->o_height; + return 0; + + case V4L2_SEL_TGT_COMPOSE: + f = &mxc_isi->isi_cap.dst_f; + case V4L2_SEL_TGT_CROP: + s->r.left = f->h_off; + s->r.top = f->v_off; + s->r.width = f->width; + s->r.height = f->height; + return 0; + } + + return -EINVAL; +} + +static int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b) +{ + if (a->left < b->left || a->top < b->top) + return 0; + + if (a->left + a->width > b->left + b->width) + return 0; + + if (a->top + a->height > b->top + b->height) + return 0; + + return 1; +} + +static int mxc_isi_cap_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_frame *f; + struct v4l2_rect rect = s->r; + unsigned long flags; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + if (s->target == V4L2_SEL_TGT_COMPOSE) + f = &mxc_isi->isi_cap.dst_f; + else if (s->target == V4L2_SEL_TGT_CROP) + f = &mxc_isi->isi_cap.src_f; + else + return -EINVAL; + + if (s->flags & V4L2_SEL_FLAG_LE && + !enclosed_rectangle(&rect, &s->r)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && + !enclosed_rectangle(&s->r, &rect)) + return -ERANGE; + + s->r = rect; + spin_lock_irqsave(&mxc_isi->slock, flags); + set_frame_crop(f, s->r.left, s->r.top, s->r.width, + s->r.height); + spin_unlock_irqrestore(&mxc_isi->slock, flags); + + return 0; +} + +static int mxc_isi_cap_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct v4l2_subdev *sd; + struct mxc_isi_fmt *fmt; + struct v4l2_subdev_frame_size_enum fse = { + .index = fsize->index, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + fmt = mxc_isi_find_format(&fsize->pixel_format, NULL, 0); + if (!fmt || fmt->fourcc != fsize->pixel_format) + return -EINVAL; + fse.code = fmt->mbus_code; + + sd = mxc_get_remote_subdev(mxc_isi, __func__); + if (!sd) { + v4l2_err(&mxc_isi->isi_cap.sd, "Can't find subdev\n"); + return -ENODEV; + } + + ret = v4l2_subdev_call(sd, pad, enum_frame_size, NULL, &fse); + if (ret) + return ret; + + if (fse.min_width == fse.max_width && + fse.min_height == fse.max_height) { + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.min_width; + fsize->discrete.height = fse.min_height; + return 0; + } + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = fse.min_width; + fsize->stepwise.max_width = fse.max_width; + fsize->stepwise.min_height = fse.min_height; + fsize->stepwise.max_height = fse.max_height; + fsize->stepwise.step_width = 1; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int mxc_isi_cap_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *interval) +{ + struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct v4l2_subdev *sd; + struct mxc_isi_fmt *fmt; + struct v4l2_subdev_frame_interval_enum fie = { + .index = interval->index, + .width = interval->width, + .height = interval->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + fmt = mxc_isi_find_format(&interval->pixel_format, NULL, 0); + if (!fmt || fmt->fourcc != interval->pixel_format) + return -EINVAL; + fie.code = fmt->mbus_code; + + sd = mxc_get_remote_subdev(mxc_isi, __func__); + if (!sd) + return -EINVAL; + + ret = v4l2_subdev_call(sd, pad, enum_frame_interval, NULL, &fie); + if (ret) + return ret; + + interval->type = V4L2_FRMIVAL_TYPE_DISCRETE; + interval->discrete = fie.interval; + + return 0; +} + +static const struct v4l2_ioctl_ops mxc_isi_capture_ioctl_ops = { + .vidioc_querycap = mxc_isi_cap_querycap, + + .vidioc_enum_fmt_vid_cap_mplane = mxc_isi_cap_enum_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = mxc_isi_cap_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = mxc_isi_cap_s_fmt_mplane, + .vidioc_g_fmt_vid_cap_mplane = mxc_isi_cap_g_fmt_mplane, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + + .vidioc_streamon = mxc_isi_cap_streamon, + .vidioc_streamoff = mxc_isi_cap_streamoff, + + .vidioc_g_selection = mxc_isi_cap_g_selection, + .vidioc_s_selection = mxc_isi_cap_s_selection, + + .vidioc_enum_framesizes = mxc_isi_cap_enum_framesizes, + .vidioc_enum_frameintervals = mxc_isi_cap_enum_frameintervals, +}; + +/* Capture subdev media entity operations */ +static int mxc_isi_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + + if (WARN_ON(!mxc_isi)) + return 0; + + if (!(flags & MEDIA_LNK_FL_ENABLED)) + return 0; + + /* Add ISI source and sink pad link configuration */ + if (local->flags & MEDIA_PAD_FL_SOURCE) { + switch (local->index) { + case MXC_ISI_SD_PAD_SOURCE_DC0: + case MXC_ISI_SD_PAD_SOURCE_DC1: + break; + case MXC_ISI_SD_PAD_SOURCE_MEM: + break; + default: + dev_err(&mxc_isi->pdev->dev, "invalid source pad\n"); + return -EINVAL; + } + } else if (local->flags & MEDIA_PAD_FL_SINK) { + switch (local->index) { + case MXC_ISI_SD_PAD_SINK_MIPI0_VC0: + case MXC_ISI_SD_PAD_SINK_MIPI0_VC1: + case MXC_ISI_SD_PAD_SINK_MIPI0_VC2: + case MXC_ISI_SD_PAD_SINK_MIPI0_VC3: + case MXC_ISI_SD_PAD_SINK_MIPI1_VC0: + case MXC_ISI_SD_PAD_SINK_MIPI1_VC1: + case MXC_ISI_SD_PAD_SINK_MIPI1_VC2: + case MXC_ISI_SD_PAD_SINK_MIPI1_VC3: + case MXC_ISI_SD_PAD_SINK_HDMI: + case MXC_ISI_SD_PAD_SINK_DC0: + case MXC_ISI_SD_PAD_SINK_DC1: + case MXC_ISI_SD_PAD_SINK_MEM: + case MXC_ISI_SD_PAD_SINK_PARALLEL_CSI: + break; + default: + dev_err(&mxc_isi->pdev->dev, + "%s invalid sink pad\n", __func__); + return -EINVAL; + } + } + + return 0; +} + +static const struct media_entity_operations mxc_isi_sd_media_ops = { + .link_setup = mxc_isi_link_setup, +}; + +static int mxc_isi_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + return 0; +} + +static int mxc_isi_subdev_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct mxc_isi_frame *f; + struct v4l2_mbus_framefmt *mf; + + mutex_lock(&mxc_isi->lock); + + switch (fmt->pad) { + case MXC_ISI_SD_PAD_SOURCE_MEM: + case MXC_ISI_SD_PAD_SOURCE_DC0: + case MXC_ISI_SD_PAD_SOURCE_DC1: + f = &mxc_isi->isi_cap.dst_f; + break; + case MXC_ISI_SD_PAD_SINK_MIPI0_VC0: + case MXC_ISI_SD_PAD_SINK_MIPI0_VC1: + case MXC_ISI_SD_PAD_SINK_MIPI0_VC2: + case MXC_ISI_SD_PAD_SINK_MIPI0_VC3: + case MXC_ISI_SD_PAD_SINK_MIPI1_VC0: + case MXC_ISI_SD_PAD_SINK_MIPI1_VC1: + case MXC_ISI_SD_PAD_SINK_MIPI1_VC2: + case MXC_ISI_SD_PAD_SINK_MIPI1_VC3: + case MXC_ISI_SD_PAD_SINK_HDMI: + case MXC_ISI_SD_PAD_SINK_DC0: + case MXC_ISI_SD_PAD_SINK_DC1: + case MXC_ISI_SD_PAD_SINK_MEM: + f = &mxc_isi->isi_cap.src_f; + break; + default: + mutex_unlock(&mxc_isi->lock); + v4l2_err(&mxc_isi->isi_cap.sd, + "%s, Pad is not support now!\n", __func__); + return -1; + } + + if (!WARN_ON(!f->fmt)) + mf->code = f->fmt->mbus_code; + + /* Source/Sink pads crop rectangle size */ + mf->width = f->width; + mf->height = f->height; + + mutex_unlock(&mxc_isi->lock); + mf->colorspace = V4L2_COLORSPACE_JPEG; + + return 0; +} + +static int mxc_isi_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_fmt *out_fmt; + int i; + + if (fmt->pad < MXC_ISI_SD_PAD_SOURCE_MEM && + vb2_is_busy(&mxc_isi->isi_cap.vb2_q)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) { + out_fmt = &mxc_isi_out_formats[i]; + if (mf->code == out_fmt->mbus_code) + break; + } + if (i >= ARRAY_SIZE(mxc_isi_out_formats)) { + v4l2_err(&mxc_isi->isi_cap.sd, + "%s, format is not support!\n", __func__); + return -EINVAL; + } + + mutex_lock(&mxc_isi->lock); + /* update out put frame size and formate */ + dst_f->fmt = &mxc_isi_out_formats[i]; + set_frame_bounds(dst_f, mf->width, mf->height); + mutex_unlock(&mxc_isi->lock); + + dev_dbg(&mxc_isi->pdev->dev, "pad%d: code: 0x%x, %dx%d", + fmt->pad, mf->code, mf->width, mf->height); + + return 0; +} + +static int mxc_isi_subdev_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct mxc_isi_frame *f = &mxc_isi->isi_cap.src_f; + struct v4l2_rect *r = &sel->r; + struct v4l2_rect *try_sel; + + mutex_lock(&mxc_isi->lock); + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + f = &mxc_isi->isi_cap.dst_f; + case V4L2_SEL_TGT_CROP_BOUNDS: + r->width = f->o_width; + r->height = f->o_height; + r->left = 0; + r->top = 0; + mutex_unlock(&mxc_isi->lock); + return 0; + + case V4L2_SEL_TGT_CROP: + try_sel = v4l2_subdev_get_try_crop(sd, cfg, sel->pad); + break; + case V4L2_SEL_TGT_COMPOSE: + try_sel = v4l2_subdev_get_try_compose(sd, cfg, sel->pad); + f = &mxc_isi->isi_cap.dst_f; + break; + default: + mutex_unlock(&mxc_isi->lock); + return -EINVAL; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + sel->r = *try_sel; + } else { + r->left = f->h_off; + r->top = f->v_off; + r->width = f->width; + r->height = f->height; + } + + dev_dbg(&mxc_isi->pdev->dev, + "%s, target %#x: l:%d, t:%d, %dx%d, f_w: %d, f_h: %d", + __func__, sel->pad, r->left, r->top, r->width, r->height, + f->c_width, f->c_height); + + mutex_unlock(&mxc_isi->lock); + return 0; +} + +static int mxc_isi_subdev_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct mxc_isi_frame *f = &mxc_isi->isi_cap.src_f; + struct v4l2_rect *r = &sel->r; + struct v4l2_rect *try_sel; + unsigned long flags; + + mutex_lock(&mxc_isi->lock); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + try_sel = v4l2_subdev_get_try_crop(sd, cfg, sel->pad); + break; + case V4L2_SEL_TGT_COMPOSE: + try_sel = v4l2_subdev_get_try_compose(sd, cfg, sel->pad); + f = &mxc_isi->isi_cap.dst_f; + break; + default: + mutex_unlock(&mxc_isi->lock); + return -EINVAL; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + *try_sel = sel->r; + } else { + spin_lock_irqsave(&mxc_isi->slock, flags); + set_frame_crop(f, r->left, r->top, r->width, r->height); + spin_unlock_irqrestore(&mxc_isi->slock, flags); + } + + dev_dbg(&mxc_isi->pdev->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__, + sel->target, r->left, r->top, r->width, r->height); + + mutex_unlock(&mxc_isi->lock); + + return 0; +} + +static struct v4l2_subdev_pad_ops mxc_isi_subdev_pad_ops = { + .enum_mbus_code = mxc_isi_subdev_enum_mbus_code, + .get_selection = mxc_isi_subdev_get_selection, + .set_selection = mxc_isi_subdev_set_selection, + .get_fmt = mxc_isi_subdev_get_fmt, + .set_fmt = mxc_isi_subdev_set_fmt, +}; + +static struct v4l2_subdev_ops mxc_isi_subdev_ops = { + .pad = &mxc_isi_subdev_pad_ops, +}; + +static int mxc_isi_register_cap_device(struct mxc_isi_dev *mxc_isi, + struct v4l2_device *v4l2_dev) +{ + struct video_device *vdev = &mxc_isi->isi_cap.vdev; + struct vb2_queue *q = &mxc_isi->isi_cap.vb2_q; + struct mxc_isi_cap_dev *isi_cap = &mxc_isi->isi_cap; + int ret = -ENOMEM; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + memset(vdev, 0, sizeof(*vdev)); + snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", mxc_isi->id); + + vdev->fops = &mxc_isi_capture_fops; + vdev->ioctl_ops = &mxc_isi_capture_ioctl_ops; + vdev->v4l2_dev = v4l2_dev; + vdev->minor = -1; + vdev->release = video_device_release_empty; + vdev->queue = q; + vdev->lock = &mxc_isi->lock; + + video_set_drvdata(vdev, mxc_isi); + + INIT_LIST_HEAD(&mxc_isi->isi_cap.out_pending); + INIT_LIST_HEAD(&mxc_isi->isi_cap.out_active); + INIT_LIST_HEAD(&mxc_isi->isi_cap.out_discard); + + memset(q, 0, sizeof(*q)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = mxc_isi; + q->ops = &mxc_cap_vb2_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct mxc_isi_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &mxc_isi->lock; + + ret = vb2_queue_init(q); + if (ret) + goto err_free_ctx; + + /* Default configuration */ + isi_cap->dst_f.width = 1280; + isi_cap->dst_f.height = 800; + isi_cap->dst_f.fmt = &mxc_isi_out_formats[0]; + isi_cap->src_f.fmt = isi_cap->dst_f.fmt; + + isi_cap->cap_pad.flags = MEDIA_PAD_FL_SINK; + vdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + ret = media_entity_pads_init(&vdev->entity, 1, &isi_cap->cap_pad); + if (ret) + goto err_free_ctx; + + ret = mxc_isi_ctrls_create(mxc_isi); + if (ret) + goto err_me_cleanup; + + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret) + goto err_ctrl_free; + + vdev->ctrl_handler = &mxc_isi->ctrls.handler; + v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n", + vdev->name, video_device_node_name(vdev)); + + return 0; + +err_ctrl_free: + mxc_isi_ctrls_delete(mxc_isi); +err_me_cleanup: + media_entity_cleanup(&vdev->entity); +err_free_ctx: + return ret; +} + +static int mxc_isi_register_cap_and_m2m_device(struct mxc_isi_dev *mxc_isi, + struct v4l2_device *v4l2_dev) +{ + int ret; + + ret = mxc_isi_register_cap_device(mxc_isi, v4l2_dev); + if (ret) + return ret; + + return 0; +} + +static int mxc_isi_subdev_registered(struct v4l2_subdev *sd) +{ + struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + int ret; + + if (!mxc_isi) + return -ENXIO; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + ret = mxc_isi_register_cap_and_m2m_device(mxc_isi, sd->v4l2_dev); + if (ret < 0) + return ret; + + return 0; +} + +static void mxc_isi_subdev_unregistered(struct v4l2_subdev *sd) +{ + struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct video_device *vdev; + + if (!mxc_isi) + return; + + dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + + mutex_lock(&mxc_isi->lock); + vdev = &mxc_isi->isi_cap.vdev; + if (video_is_registered(vdev)) { + video_unregister_device(vdev); + mxc_isi_ctrls_delete(mxc_isi); + media_entity_cleanup(&vdev->entity); + } + mutex_unlock(&mxc_isi->lock); +} + +static const struct v4l2_subdev_internal_ops mxc_isi_capture_sd_internal_ops = { + .registered = mxc_isi_subdev_registered, + .unregistered = mxc_isi_subdev_unregistered, +}; + +int mxc_isi_initialize_capture_subdev(struct mxc_isi_dev *mxc_isi) +{ + struct v4l2_subdev *sd = &mxc_isi->isi_cap.sd; + int ret; + + v4l2_subdev_init(sd, &mxc_isi_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", mxc_isi->id); + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + + /* ISI Sink pads */ + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC0].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC1].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC2].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC3].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC0].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC1].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC2].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC3].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_DC0].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_DC1].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_HDMI].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MEM].flags = MEDIA_PAD_FL_SINK; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_PARALLEL_CSI].flags = MEDIA_PAD_FL_SINK; + + /* ISI source pads */ + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SOURCE_MEM].flags = MEDIA_PAD_FL_SOURCE; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SOURCE_DC0].flags = MEDIA_PAD_FL_SOURCE; + mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SOURCE_DC1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&sd->entity, MXC_ISI_SD_PADS_NUM, + mxc_isi->isi_cap.sd_pads); + if (ret) + return ret; + + sd->entity.ops = &mxc_isi_sd_media_ops; + sd->internal_ops = &mxc_isi_capture_sd_internal_ops; + v4l2_set_subdevdata(sd, mxc_isi); + + return 0; +} + +void mxc_isi_unregister_capture_subdev(struct mxc_isi_dev *mxc_isi) +{ + struct v4l2_subdev *sd = &mxc_isi->isi_cap.sd; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_set_subdevdata(sd, NULL); +} diff --git a/drivers/staging/media/imx/imx8-isi-core.c b/drivers/staging/media/imx/imx8-isi-core.c new file mode 100644 index 000000000000..eb05caeb5767 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-core.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 NXP Semiconductor + * + */ + +#include "imx8-isi-hw.h" + +static irqreturn_t mxc_isi_irq_handler(int irq, void *priv) +{ + struct mxc_isi_dev *mxc_isi = priv; + struct device *dev = &mxc_isi->pdev->dev; + u32 status; + + spin_lock(&mxc_isi->slock); + + status = mxc_isi_get_irq_status(mxc_isi); + mxc_isi_clean_irq_status(mxc_isi, status); + + if (status & CHNL_STS_FRM_STRD_MASK) + mxc_isi_cap_frame_write_done(mxc_isi); + + if (status & (CHNL_STS_AXI_WR_ERR_Y_MASK | + CHNL_STS_AXI_WR_ERR_U_MASK | + CHNL_STS_AXI_WR_ERR_V_MASK)) + dev_dbg(dev, "%s, IRQ AXI Error stat=0x%X\n", __func__, status); + + if (status & (CHNL_STS_OFLW_PANIC_Y_BUF_MASK | + CHNL_STS_OFLW_PANIC_U_BUF_MASK | + CHNL_STS_OFLW_PANIC_V_BUF_MASK)) + dev_dbg(dev, "%s, IRQ Panic OFLW Error stat=0x%X\n", __func__, status); + + if (status & (CHNL_STS_OFLW_Y_BUF_MASK | + CHNL_STS_OFLW_U_BUF_MASK | + CHNL_STS_OFLW_V_BUF_MASK)) + dev_dbg(dev, "%s, IRQ OFLW Error stat=0x%X\n", __func__, status); + + if (status & (CHNL_STS_EXCS_OFLW_Y_BUF_MASK | + CHNL_STS_EXCS_OFLW_U_BUF_MASK | + CHNL_STS_EXCS_OFLW_V_BUF_MASK)) + dev_dbg(dev, "%s, IRQ EXCS OFLW Error stat=0x%X\n", __func__, status); + + spin_unlock(&mxc_isi->slock); + return IRQ_HANDLED; +} + +static int mxc_isi_parse_dt(struct mxc_isi_dev *mxc_isi) +{ + struct device *dev = &mxc_isi->pdev->dev; + struct device_node *node = dev->of_node; + int ret = 0; + + mxc_isi->id = of_alias_get_id(node, "isi"); + mxc_isi->parallel_csi = of_property_read_bool(node, "parallel_csi"); + mxc_isi->chain_buf = of_property_read_bool(node, "fsl,chain_buf"); + + ret = of_property_read_u32_array(node, "interface", mxc_isi->interface, 3); + if (ret < 0) + return ret; + + dev_dbg(dev, "%s, isi_%d,interface(%d, %d, %d)\n", __func__, + mxc_isi->id, + mxc_isi->interface[0], + mxc_isi->interface[1], + mxc_isi->interface[2]); + return 0; +} + +static int mxc_isi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mxc_isi_dev *mxc_isi; + struct resource *res; + int ret = 0; + + mxc_isi = devm_kzalloc(dev, sizeof(*mxc_isi), GFP_KERNEL); + if (!mxc_isi) + return -ENOMEM; + + mxc_isi->pdev = pdev; + + ret = mxc_isi_parse_dt(mxc_isi); + if (ret < 0) + return ret; + + if (mxc_isi->id >= MXC_ISI_MAX_DEVS || mxc_isi->id < 0) { + dev_err(dev, "Invalid driver data or device id (%d)\n", mxc_isi->id); + return -EINVAL; + } + + init_waitqueue_head(&mxc_isi->irq_queue); + spin_lock_init(&mxc_isi->slock); + mutex_init(&mxc_isi->lock); + atomic_set(&mxc_isi->open_count, 0); + + mxc_isi->clk = devm_clk_get(dev, NULL); + if (IS_ERR(mxc_isi->clk)) { + dev_err(dev, "failed to get isi clk\n"); + return PTR_ERR(mxc_isi->clk); + } + + ret = clk_prepare_enable(mxc_isi->clk); + if (ret < 0) { + dev_err(dev, "Prepare and enable isi clk error (%d)\n", ret); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mxc_isi->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(mxc_isi->regs)) { + dev_err(dev, "Failed to get ISI register map\n"); + return PTR_ERR(mxc_isi->regs); + } + + mxc_isi_clean_registers(mxc_isi); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "Failed to get IRQ resource\n"); + return -ENXIO; + } + ret = devm_request_irq(dev, res->start, mxc_isi_irq_handler, + 0, dev_name(dev), mxc_isi); + if (ret < 0) { + dev_err(dev, "failed to install irq (%d)\n", ret); + return -EINVAL; + } + + ret = mxc_isi_initialize_capture_subdev(mxc_isi); + if (ret < 0) { + dev_err(dev, "failed to init cap subdev (%d)\n", ret); + return -EINVAL; + } + + platform_set_drvdata(pdev, mxc_isi); + + mxc_isi_channel_set_chain_buf(mxc_isi); + + clk_disable_unprepare(mxc_isi->clk); + pm_runtime_enable(dev); + + dev_info(dev, "mxc_isi.%d registered successfully\n", mxc_isi->id); + + return 0; +} + +static int mxc_isi_remove(struct platform_device *pdev) +{ + struct mxc_isi_dev *mxc_isi = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + mxc_isi_unregister_capture_subdev(mxc_isi); + pm_runtime_disable(dev); + + return 0; +} + +static int mxc_isi_pm_suspend(struct device *dev) +{ + struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev); + + if (mxc_isi->is_streaming) { + dev_warn(dev, "running, prevent entering suspend.\n"); + return -EAGAIN; + } + + return pm_runtime_force_suspend(dev); +} + +static int mxc_isi_pm_resume(struct device *dev) +{ + return pm_runtime_force_resume(dev); +} + +static int mxc_isi_runtime_suspend(struct device *dev) +{ + struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev); + + clk_disable_unprepare(mxc_isi->clk); + return 0; +} + +static int mxc_isi_runtime_resume(struct device *dev) +{ + struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(mxc_isi->clk); + if (ret) + dev_err(dev, "%s clk enable fail\n", __func__); + + return (ret) ? ret : 0; +} + +static const struct dev_pm_ops mxc_isi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume) + SET_RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL) +}; + +static const struct of_device_id mxc_isi_of_match[] = { + {.compatible = "fsl,imx8-isi",}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mxc_isi_of_match); + +static struct platform_driver mxc_isi_driver = { + .probe = mxc_isi_probe, + .remove = mxc_isi_remove, + .driver = { + .of_match_table = mxc_isi_of_match, + .name = MXC_ISI_DRIVER_NAME, + .pm = &mxc_isi_pm_ops, + } +}; +module_platform_driver(mxc_isi_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX8 Image Subsystem driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ISI"); +MODULE_VERSION("1.0"); diff --git a/drivers/staging/media/imx/imx8-isi-core.h b/drivers/staging/media/imx/imx8-isi-core.h new file mode 100644 index 000000000000..1bc7a7263777 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-core.h @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 NXP Semiconductor + */ + +#ifndef __MXC_ISI_CORE_H__ +#define __MXC_ISI_CORE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx8-common.h" + +#define MXC_ISI_DRIVER_NAME "mxc-isi" +#define MXC_MAX_PLANES 3 + +enum mxc_isi_out_fmt { + MXC_ISI_OUT_FMT_RGBA32 = 0x0, + MXC_ISI_OUT_FMT_ABGR32, + MXC_ISI_OUT_FMT_ARGB32, + MXC_ISI_OUT_FMT_RGBX32, + MXC_ISI_OUT_FMT_XBGR32, + MXC_ISI_OUT_FMT_XRGB32, + MXC_ISI_OUT_FMT_RGB32P, + MXC_ISI_OUT_FMT_BGR32P, + MXC_ISI_OUT_FMT_A2BGR10, + MXC_ISI_OUT_FMT_A2RGB10, + MXC_ISI_OUT_FMT_RGB565, + MXC_ISI_OUT_FMT_RAW8, + MXC_ISI_OUT_FMT_RAW10, + MXC_ISI_OUT_FMT_RAW10P, + MXC_ISI_OUT_FMT_RAW12, + MXC_ISI_OUT_FMT_RAW16, + MXC_ISI_OUT_FMT_YUV444_1P8P, + MXC_ISI_OUT_FMT_YUV444_2P8P, + MXC_ISI_OUT_FMT_YUV444_3P8P, + MXC_ISI_OUT_FMT_YUV444_1P8, + MXC_ISI_OUT_FMT_YUV444_1P10, + MXC_ISI_OUT_FMT_YUV444_2P10, + MXC_ISI_OUT_FMT_YUV444_3P10, + MXC_ISI_OUT_FMT_YUV444_1P10P = 0x18, + MXC_ISI_OUT_FMT_YUV444_2P10P, + MXC_ISI_OUT_FMT_YUV444_3P10P, + MXC_ISI_OUT_FMT_YUV444_1P12 = 0x1C, + MXC_ISI_OUT_FMT_YUV444_2P12, + MXC_ISI_OUT_FMT_YUV444_3P12, + MXC_ISI_OUT_FMT_YUV422_1P8P = 0x20, + MXC_ISI_OUT_FMT_YUV422_2P8P, + MXC_ISI_OUT_FMT_YUV422_3P8P, + MXC_ISI_OUT_FMT_YUV422_1P10 = 0x24, + MXC_ISI_OUT_FMT_YUV422_2P10, + MXC_ISI_OUT_FMT_YUV422_3P10, + MXC_ISI_OUT_FMT_YUV422_1P10P = 0x28, + MXC_ISI_OUT_FMT_YUV422_2P10P, + MXC_ISI_OUT_FMT_YUV422_3P10P, + MXC_ISI_OUT_FMT_YUV422_1P12 = 0x2C, + MXC_ISI_OUT_FMT_YUV422_2P12, + MXC_ISI_OUT_FMT_YUV422_3P12, + MXC_ISI_OUT_FMT_YUV420_2P8P = 0x31, + MXC_ISI_OUT_FMT_YUV420_3P8P, + MXC_ISI_OUT_FMT_YUV420_2P10 = 0x35, + MXC_ISI_OUT_FMT_YUV420_3P10, + MXC_ISI_OUT_FMT_YUV420_2P10P = 0x39, + MXC_ISI_OUT_FMT_YUV420_3P10P, + MXC_ISI_OUT_FMT_YUV420_2P12 = 0x3D, + MXC_ISI_OUT_FMT_YUV420_3P12, +}; + +enum mxc_isi_in_fmt { + MXC_ISI_IN_FMT_BGR8P = 0x0, +}; + +struct mxc_isi_fmt { + char *name; + u32 mbus_code; + u32 fourcc; + u32 color; + u16 memplanes; + u16 colplanes; + u8 colorspace; + u8 depth[MXC_MAX_PLANES]; + u16 mdataplanes; + u16 flags; +}; + +struct mxc_isi_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *alpha; + bool ready; +}; + +/** + * struct addr - physical address set for DMA + * @y: luminance plane physical address + * @cb: Cb plane physical address + * @cr: Cr plane physical address + */ +struct frame_addr { + u32 y; + u32 cb; + u32 cr; +}; + +/** + * struct mxc_isi_frame - source/target frame properties + * o_width: original image width from sensor + * o_height: original image height from sensor + * c_width: crop image width set by g_selection + * c_height: crop image height set by g_selection + * h_off: crop horizontal pixel offset + * v_off: crop vertical pixel offset + * width: out image pixel width + * height: out image pixel weight + * bytesperline: bytesperline value for each plane + * paddr: image frame buffer physical addresses + * fmt: color format pointer + */ +struct mxc_isi_frame { + u32 o_width; + u32 o_height; + u32 c_width; + u32 c_height; + u32 h_off; + u32 v_off; + u32 width; + u32 height; + unsigned int sizeimage[MXC_MAX_PLANES]; + unsigned int bytesperline[MXC_MAX_PLANES]; + struct mxc_isi_fmt *fmt; +}; + +struct mxc_isi_roi_alpha { + u8 alpha; + struct v4l2_rect rect; +}; + +struct mxc_isi_buffer { + struct vb2_v4l2_buffer v4l2_buf; + struct list_head list; + struct frame_addr paddr; + bool discard; +}; + +struct mxc_isi_ctx { + struct mxc_isi_dev *isi_dev; + struct v4l2_fh fh; +}; + +struct mxc_isi_cap_dev { + struct v4l2_subdev sd; + struct video_device vdev; + struct v4l2_fh fh; + struct media_pad cap_pad; + struct media_pad sd_pads[MXC_ISI_SD_PADS_NUM]; + struct vb2_queue vb2_q; + struct list_head out_pending; + struct list_head out_active; + struct list_head out_discard; + + struct mxc_isi_frame src_f; + struct mxc_isi_frame dst_f; + u32 frame_count; + + u32 buf_index; +}; + +struct mxc_isi_dev { + struct mxc_isi_cap_dev isi_cap; + struct platform_device *pdev; + struct v4l2_device *v4l2_dev; + struct clk *clk; + + struct mutex lock; + wait_queue_head_t irq_queue; + spinlock_t slock; + + int id; + void __iomem *regs; + unsigned long state; + + u32 interface[MAX_PORTS]; + u32 flags; + u32 skip_m2m; + u8 chain_buf; + + atomic_t open_count; + + /* scale factor */ + u32 xfactor; + u32 yfactor; + u32 pre_dec_x; + u32 pre_dec_y; + + unsigned int hflip:1; + unsigned int vflip:1; + + unsigned int cscen:1; + unsigned int scale:1; + unsigned int alphaen:1; + unsigned int crop:1; + unsigned int deinterlace:1; + unsigned int parallel_csi:1; + unsigned int is_m2m:1; + unsigned int is_streaming:1; + + struct mxc_isi_ctrls ctrls; + struct mxc_isi_roi_alpha alpha_roi[5]; + struct v4l2_pix_format_mplane pix; + u8 alpha; + + size_t discard_size[MXC_MAX_PLANES]; + void *discard_buffer[MXC_MAX_PLANES]; + dma_addr_t discard_buffer_dma[MXC_MAX_PLANES]; + struct mxc_isi_buffer buf_discard[2]; +}; + +static inline void set_frame_bounds(struct mxc_isi_frame *f, + u32 width, u32 height) +{ + f->o_width = width; + f->o_height = height; + f->c_width = width; + f->c_height = height; + f->width = width; + f->height = height; +} + +static inline void set_frame_out(struct mxc_isi_frame *f, + u32 width, u32 height) +{ + f->c_width = width; + f->c_height = height; + f->width = width; + f->height = height; +} + +static inline void set_frame_crop(struct mxc_isi_frame *f, + u32 left, u32 top, u32 width, u32 height) +{ + f->h_off = left; + f->v_off = top; + f->c_width = width; + f->c_height = height; +} + +int mxc_isi_initialize_capture_subdev(struct mxc_isi_dev *mxc_isi); +void mxc_isi_unregister_capture_subdev(struct mxc_isi_dev *mxc_isi); + +#endif /* __MXC_ISI_CORE_H__ */ diff --git a/drivers/staging/media/imx/imx8-isi-hw.c b/drivers/staging/media/imx/imx8-isi-hw.c new file mode 100644 index 000000000000..462c49855174 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-hw.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 NXP Semiconductor + * + */ + +#include + +#include "imx8-isi-hw.h" +#include "imx8-common.h" + +#define ISI_DOWNSCALE_THRESHOLD 0x4000 + +#ifdef DEBUG +void dump_isi_regs(struct mxc_isi_dev *mxc_isi) +{ + struct device *dev = &mxc_isi->pdev->dev; + struct { + u32 offset; + const char *const name[64]; + } registers[] = { + { 0x00h, "CHNL_CTRL" }, + { 0x04h, "CHNL_IMG_CTRL" }, + { 0x08h, "CHNL_OUT_BUF_CTRL" }, + { 0x0Ch, "CHNL_IMG_CFG" }, + { 0x10h, "CHNL_IER" }, + { 0x14h, "CHNL_STS" }, + { 0x18h, "CHNL_SCALE_FACTOR" }, + { 0x1Ch, "CHNL_SCALE_OFFSET" }, + { 0x20h, "CHNL_CROP_ULC" }, + { 0x24h, "CHNL_CROP_LRC" }, + { 0x28h, "CHNL_CSC_COEFF0" }, + { 0x2Ch, "CHNL_CSC_COEFF1" }, + { 0x30h, "CHNL_CSC_COEFF2" }, + { 0x34h, "CHNL_CSC_COEFF3" }, + { 0x38h, "CHNL_CSC_COEFF4" }, + { 0x3Ch, "CHNL_CSC_COEFF5" }, + { 0x40h, "CHNL_ROI_0_ALPHA" }, + { 0x44h, "CHNL_ROI_0_ULC" }, + { 0x48h, "CHNL_ROI_0_LRC" }, + { 0x4Ch, "CHNL_ROI_1_ALPHA" }, + { 0x50h, "CHNL_ROI_1_ULC" }, + { 0x54h, "CHNL_ROI_1_LRC" }, + { 0x58h, "CHNL_ROI_2_ALPHA" }, + { 0x5Ch, "CHNL_ROI_2_ULC" }, + { 0x60h, "CHNL_ROI_2_LRC" }, + { 0x64h, "CHNL_ROI_3_ALPHA" }, + { 0x68h, "CHNL_ROI_3_ULC" }, + { 0x6Ch, "CHNL_ROI_3_LRC" }, + { 0x70h, "CHNL_OUT_BUF1_ADDR_Y" }, + { 0x74h, "CHNL_OUT_BUF1_ADDR_U" }, + { 0x78h, "CHNL_OUT_BUF1_ADDR_V" }, + { 0x7Ch, "CHNL_OUT_BUF_PITCH" }, + { 0x80h, "CHNL_IN_BUF_ADDR" }, + { 0x84h, "CHNL_IN_BUF_PITCH" }, + { 0x88h, "CHNL_MEM_RD_CTRL" }, + { 0x8Ch, "CHNL_OUT_BUF2_ADDR_Y" }, + { 0x90h, "CHNL_OUT_BUF2_ADDR_U" }, + { 0x94h, "CHNL_OUT_BUF2_ADDR_V" }, + { 0x98h, "CHNL_SCL_IMG_CFG" }, + { 0x9Ch, "CHNL_FLOW_CTRL" }, + }; + u32 i; + + dev_dbg(dev, "ISI CHNLC register dump, isi%d\n", mxc_isi->id); + for (i = 0; i < ARRAY_SIZE(registers); i++) { + u32 reg = readl(mxc_isi->regs + registers.offset); + dev_dbg(dev, "%20s[0x%.2x]: %.2x\n", + registers.name, registers.offset, reg); + } +} +#else +void dump_isi_regs(struct mxc_isi_dev *mxc_isi) +{ +} +#endif + +/* + * A2,A1, B1, A3, B3, B2, + * C2, C1, D1, C3, D3, D2 + */ +static const u32 coeffs[2][6] = { + /* YUV2RGB */ + { 0x0000012A, 0x012A0198, 0x0730079C, + 0x0204012A, 0x01F00000, 0x01800180 }, + + /* RGB->YUV */ + { 0x00810041, 0x07db0019, 0x007007b6, + 0x07a20070, 0x001007ee, 0x00800080 }, +}; + +static void printk_pixelformat(char *prefix, int val) +{ + pr_info("%s %c%c%c%c\n", prefix ? prefix : "pixelformat", + val & 0xff, + (val >> 8) & 0xff, + (val >> 16) & 0xff, + (val >> 24) & 0xff); +} + +static bool is_rgb(u32 pix_fmt) +{ + if ((pix_fmt == V4L2_PIX_FMT_RGB565) || + (pix_fmt == V4L2_PIX_FMT_RGB24) || + (pix_fmt == V4L2_PIX_FMT_RGB32) || + (pix_fmt == V4L2_PIX_FMT_BGR32) || + (pix_fmt == V4L2_PIX_FMT_XRGB32) || + (pix_fmt == V4L2_PIX_FMT_XBGR32) || + (pix_fmt == V4L2_PIX_FMT_BGR24) || + (pix_fmt == V4L2_PIX_FMT_ABGR32) || + (pix_fmt == V4L2_PIX_FMT_ARGB32)) + return true; + else + return false; +} + +static bool is_yuv(u32 pix_fmt) +{ + if ((pix_fmt == V4L2_PIX_FMT_YUYV) || + (pix_fmt == V4L2_PIX_FMT_YUV32) || + (pix_fmt == V4L2_PIX_FMT_YUV444M) || + (pix_fmt == V4L2_PIX_FMT_NV12)) + return true; + else + return false; +} + +static void chain_buf(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_frame *src_f; + u32 val; + + src_f = &mxc_isi->isi_cap.src_f; + + if (src_f->o_width > 2048) { + val = readl(mxc_isi->regs + CHNL_CTRL); + val &= ~CHNL_CTRL_CHAIN_BUF_MASK; + val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET); + writel(val, mxc_isi->regs + CHNL_CTRL); + } else if (!mxc_isi->chain_buf) { + val = readl(mxc_isi->regs + CHNL_CTRL); + val &= ~CHNL_CTRL_CHAIN_BUF_MASK; + writel(val, mxc_isi->regs + CHNL_CTRL); + } +} + +void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_buffer *buf) +{ + struct vb2_buffer *vb2_buf = &buf->v4l2_buf.vb2_buf; + struct frame_addr *paddr = &buf->paddr; + struct v4l2_pix_format_mplane *pix = &mxc_isi->pix; + u32 framecount = buf->v4l2_buf.sequence; + int val = 0; + + if (buf->discard) { + paddr->y = mxc_isi->discard_buffer_dma[0]; + if (pix->num_planes == 2) + paddr->cb = mxc_isi->discard_buffer_dma[1]; + if (pix->num_planes == 3) { + paddr->cb = mxc_isi->discard_buffer_dma[1]; + paddr->cr = mxc_isi->discard_buffer_dma[2]; + } + } else { + paddr->y = vb2_dma_contig_plane_dma_addr(vb2_buf, 0); + + if (vb2_buf->num_planes == 2) + paddr->cb = vb2_dma_contig_plane_dma_addr(vb2_buf, 1); + if (vb2_buf->num_planes == 3) { + paddr->cb = vb2_dma_contig_plane_dma_addr(vb2_buf, 1); + paddr->cr = vb2_dma_contig_plane_dma_addr(vb2_buf, 2); + } + } + + val = readl(mxc_isi->regs + CHNL_OUT_BUF_CTRL); + + if (framecount % 2 == 0) { + writel(paddr->y, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_Y); + writel(paddr->cb, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_U); + writel(paddr->cr, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_V); + val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_MASK; + } else if (framecount % 2 == 1) { + writel(paddr->y, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_Y); + writel(paddr->cb, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_U); + writel(paddr->cr, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_V); + val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_MASK; + } + writel(val, mxc_isi->regs + CHNL_OUT_BUF_CTRL); +} + +void mxc_isi_channel_sw_reset(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + val = readl(mxc_isi->regs + CHNL_CTRL); + val |= CHNL_CTRL_SW_RST; + writel(val, mxc_isi->regs + CHNL_CTRL); + mdelay(5); + val &= ~CHNL_CTRL_SW_RST; + writel(val, mxc_isi->regs + CHNL_CTRL); +} + +void mxc_isi_channel_source_config(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + val = readl(mxc_isi->regs + CHNL_CTRL); + val &= ~(CHNL_CTRL_MIPI_VC_ID_MASK | + CHNL_CTRL_SRC_INPUT_MASK | CHNL_CTRL_SRC_TYPE_MASK); + + switch (mxc_isi->interface[IN_PORT]) { + case ISI_INPUT_INTERFACE_MIPI0_CSI2: + val |= CHNL_CTRL_SRC_INPUT_MIPI0; + if (mxc_isi->interface[SUB_IN_PORT] <= CHNL_CTRL_MIPI_VC_ID_VC3 && + mxc_isi->interface[SUB_IN_PORT] >= CHNL_CTRL_MIPI_VC_ID_VC0) + val |= (mxc_isi->interface[SUB_IN_PORT] << CHNL_CTRL_MIPI_VC_ID_OFFSET); + break; + case ISI_INPUT_INTERFACE_MIPI1_CSI2: + val |= CHNL_CTRL_SRC_INPUT_MIPI1; + if (mxc_isi->interface[SUB_IN_PORT] <= CHNL_CTRL_MIPI_VC_ID_VC3 && + mxc_isi->interface[SUB_IN_PORT] >= CHNL_CTRL_MIPI_VC_ID_VC0) + val |= (mxc_isi->interface[SUB_IN_PORT] << CHNL_CTRL_MIPI_VC_ID_OFFSET); + break; + case ISI_INPUT_INTERFACE_DC0: + val |= CHNL_CTRL_SRC_INPUT_DC0; + break; + case ISI_INPUT_INTERFACE_DC1: + val |= CHNL_CTRL_SRC_INPUT_DC1; + break; + case ISI_INPUT_INTERFACE_HDMI: + val |= CHNL_CTRL_SRC_INPUT_HDMI; + break; + case ISI_INPUT_INTERFACE_PARALLEL_CSI: + val |= CHNL_CTRL_SRC_INPUT_CSI; + break; + case ISI_INPUT_INTERFACE_MEM: + val |= CHNL_CTRL_SRC_INPUT_MEMORY; + val |= (CHNL_CTRL_SRC_TYPE_MEMORY << CHNL_CTRL_SRC_TYPE_OFFSET); + break; + default: + dev_err(&mxc_isi->pdev->dev, "invalid interface\n"); + break; + } + + writel(val, mxc_isi->regs + CHNL_CTRL); +} + +void mxc_isi_channel_set_flip(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + val = readl(mxc_isi->regs + CHNL_IMG_CTRL); + val &= ~(CHNL_IMG_CTRL_VFLIP_EN_MASK | CHNL_IMG_CTRL_HFLIP_EN_MASK); + + if (mxc_isi->vflip) + val |= (CHNL_IMG_CTRL_VFLIP_EN_ENABLE << CHNL_IMG_CTRL_VFLIP_EN_OFFSET); + if (mxc_isi->hflip) + val |= (CHNL_IMG_CTRL_HFLIP_EN_ENABLE << CHNL_IMG_CTRL_HFLIP_EN_OFFSET); + + writel(val, mxc_isi->regs + CHNL_IMG_CTRL); +} + +void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_fmt *dst_fmt = mxc_isi->isi_cap.dst_f.fmt; + struct mxc_isi_fmt *src_fmt = mxc_isi->isi_cap.src_f.fmt; + u32 val, csc = 0; + + val = readl(mxc_isi->regs + CHNL_IMG_CTRL); + val &= ~(CHNL_IMG_CTRL_FORMAT_MASK | + CHNL_IMG_CTRL_YCBCR_MODE_MASK | + CHNL_IMG_CTRL_CSC_BYPASS_MASK | + CHNL_IMG_CTRL_CSC_MODE_MASK); + + /* set outbuf format */ + val |= dst_fmt->color << CHNL_IMG_CTRL_FORMAT_OFFSET; + + mxc_isi->cscen = 1; + + if (is_yuv(src_fmt->fourcc) && is_rgb(dst_fmt->fourcc)) { + /* YUV2RGB */ + csc = YUV2RGB; + /* YCbCr enable??? */ + val |= (CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB << CHNL_IMG_CTRL_CSC_MODE_OFFSET); + val |= (CHNL_IMG_CTRL_YCBCR_MODE_ENABLE << CHNL_IMG_CTRL_YCBCR_MODE_OFFSET); + } else if (is_rgb(src_fmt->fourcc) && is_yuv(dst_fmt->fourcc)) { + /* RGB2YUV */ + csc = RGB2YUV; + val |= (CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR << CHNL_IMG_CTRL_CSC_MODE_OFFSET); + } else { + /* Bypass CSC */ + pr_info("bypass csc\n"); + mxc_isi->cscen = 0; + val |= CHNL_IMG_CTRL_CSC_BYPASS_ENABLE; + } + + printk_pixelformat("input fmt", src_fmt->fourcc); + printk_pixelformat("output fmt", dst_fmt->fourcc); + + if (mxc_isi->cscen) { + writel(coeffs[csc][0], mxc_isi->regs + CHNL_CSC_COEFF0); + writel(coeffs[csc][1], mxc_isi->regs + CHNL_CSC_COEFF1); + writel(coeffs[csc][2], mxc_isi->regs + CHNL_CSC_COEFF2); + writel(coeffs[csc][3], mxc_isi->regs + CHNL_CSC_COEFF3); + writel(coeffs[csc][4], mxc_isi->regs + CHNL_CSC_COEFF4); + writel(coeffs[csc][5], mxc_isi->regs + CHNL_CSC_COEFF5); + } + + writel(val, mxc_isi->regs + CHNL_IMG_CTRL); +} + +void mxc_isi_channel_set_alpha_roi0(struct mxc_isi_dev *mxc_isi, + struct v4l2_rect *rect) +{ + u32 val0, val1; + + val0 = (rect->left << 16) | rect->top; + writel(val0, mxc_isi->regs + CHNL_ROI_0_ULC); + val1 = (rect->width << 16) | rect->height; + writel(val0 + val1, mxc_isi->regs + CHNL_ROI_0_LRC); +} + +void mxc_isi_channel_set_alpha(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + val = readl(mxc_isi->regs + CHNL_IMG_CTRL); + val &= ~(CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK | CHNL_IMG_CTRL_GBL_ALPHA_EN_MASK); + val |= ((mxc_isi->alpha << CHNL_IMG_CTRL_GBL_ALPHA_VAL_OFFSET) | + (CHNL_IMG_CTRL_GBL_ALPHA_EN_ENABLE << CHNL_IMG_CTRL_GBL_ALPHA_EN_OFFSET)); + + writel(val, mxc_isi->regs + CHNL_IMG_CTRL); +} + +void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + if (mxc_isi->chain_buf) { + pr_info("%s\n", __func__); + val = readl(mxc_isi->regs + CHNL_CTRL); + val &= ~CHNL_CTRL_CHAIN_BUF_MASK; + val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET); + + writel(val, mxc_isi->regs + CHNL_CTRL); + } +} + +void mxc_isi_channel_deinterlace_init(struct mxc_isi_dev *mxc_isi) +{ + /* Config for Blending deinterlace */ +} + +void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi) +{ + /* de-interlacing method + * Weaving-------------Yes + * Line Doubling-------No + * Blending -----------TODO + */ + u32 val; + + val = readl(mxc_isi->regs + CHNL_IMG_CTRL); + val &= ~CHNL_IMG_CTRL_DEINT_MASK; + if (mxc_isi->deinterlace) + val |= mxc_isi->deinterlace << CHNL_IMG_CTRL_DEINT_OFFSET; + if (mxc_isi->deinterlace == CHNL_IMG_CTRL_DEINT_LDOUBLE_ODD_EVEN || + mxc_isi->deinterlace == CHNL_IMG_CTRL_DEINT_LDOUBLE_EVEN_ODD) + mxc_isi_channel_deinterlace_init(mxc_isi); + + writel(val, mxc_isi->regs + CHNL_IMG_CTRL); +} + +void mxc_isi_channel_set_crop(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_frame *src_f = &mxc_isi->isi_cap.src_f; + struct v4l2_rect crop; + u32 val, val0, val1, temp; + + val = readl(mxc_isi->regs + CHNL_IMG_CTRL); + val &= ~CHNL_IMG_CTRL_CROP_EN_MASK; + + if ((src_f->o_height == src_f->height) && + (src_f->o_width == src_f->width)) { + mxc_isi->crop = 0; + writel(val, mxc_isi->regs + CHNL_IMG_CTRL); + return; + } + + if (mxc_isi->scale) { + temp = (src_f->h_off << 12) / mxc_isi->xfactor; + crop.left = temp >> mxc_isi->pre_dec_x; + temp = (src_f->v_off << 12) / mxc_isi->yfactor; + crop.top = temp >> mxc_isi->pre_dec_y; + temp = (src_f->width << 12) / mxc_isi->xfactor; + crop.width = temp >> mxc_isi->pre_dec_x; + temp = (src_f->height << 12) / mxc_isi->yfactor; + crop.height = temp >> mxc_isi->pre_dec_y; + } else { + crop.left = src_f->h_off; + crop.top = src_f->v_off; + crop.width = src_f->width; + crop.height = src_f->height; + } + + mxc_isi->crop = 1; + val |= (CHNL_IMG_CTRL_CROP_EN_ENABLE << CHNL_IMG_CTRL_CROP_EN_OFFSET); + val0 = crop.top | (crop.left << CHNL_CROP_ULC_X_OFFSET); + val1 = crop.height | (crop.width << CHNL_CROP_LRC_X_OFFSET); + + writel(val0, mxc_isi->regs + CHNL_CROP_ULC); + writel((val1 + val0), mxc_isi->regs + CHNL_CROP_LRC); + writel(val, mxc_isi->regs + CHNL_IMG_CTRL); +} + +static void mxc_isi_channel_clear_scaling(struct mxc_isi_dev *mxc_isi) +{ + u32 val0; + + writel(0x10001000, mxc_isi->regs + CHNL_SCALE_FACTOR); + + val0 = readl(mxc_isi->regs + CHNL_IMG_CTRL); + val0 &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK); + writel(val0, mxc_isi->regs + CHNL_IMG_CTRL); +} + +void mxc_isi_channel_set_scaling(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_frame *src_f = &mxc_isi->isi_cap.src_f; + u32 decx, decy; + u32 xscale, yscale; + u32 xdec = 0, ydec = 0; + u32 val0, val1; + + if (dst_f->height == src_f->height || + dst_f->width == src_f->width) { + mxc_isi->scale = 0; + mxc_isi_channel_clear_scaling(mxc_isi); + dev_dbg(&mxc_isi->pdev->dev, "%s: no scale\n", __func__); + return; + } + + dev_info(&mxc_isi->pdev->dev, "input_size(%d,%d), output_size(%d,%d)\n", + src_f->width, src_f->height, dst_f->width, dst_f->height); + + mxc_isi->scale = 1; + + decx = src_f->width / dst_f->width; + decy = src_f->height / dst_f->height; + + if (decx > 1) { + /* Down */ + if (decx >= 2 && decx < 4) { + decx = 2; + xdec = 1; + } else if (decx >= 4 && decx < 8) { + decx = 4; + xdec = 2; + } else if (decx >= 8) { + decx = 8; + xdec = 3; + } + xscale = src_f->width * 0x1000 / (dst_f->width * decx); + } else { + /* Up */ + xscale = src_f->width * 0x1000 / dst_f->width; + } + + if (decy > 1) { + if (decy >= 2 && decy < 4) { + decy = 2; + ydec = 1; + } else if (decy >= 4 && decy < 8) { + decy = 4; + ydec = 2; + } else if (decy >= 8) { + decy = 8; + ydec = 3; + } + yscale = src_f->height * 0x1000 / (dst_f->height * decy); + } else { + yscale = src_f->height * 0x1000 / dst_f->height; + } + + val0 = readl(mxc_isi->regs + CHNL_IMG_CTRL); + val0 |= CHNL_IMG_CTRL_YCBCR_MODE_MASK;//YCbCr Sandor??? + val0 &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK); + val0 |= (xdec << CHNL_IMG_CTRL_DEC_X_OFFSET) | + (ydec << CHNL_IMG_CTRL_DEC_Y_OFFSET); + writel(val0, mxc_isi->regs + CHNL_IMG_CTRL); + + if (xscale > ISI_DOWNSCALE_THRESHOLD) + xscale = ISI_DOWNSCALE_THRESHOLD; + if (yscale > ISI_DOWNSCALE_THRESHOLD) + yscale = ISI_DOWNSCALE_THRESHOLD; + + val1 = xscale | (yscale << CHNL_SCALE_FACTOR_Y_SCALE_OFFSET); + + writel(val1, mxc_isi->regs + CHNL_SCALE_FACTOR); + + /* Update scale config if scaling enabled */ + val1 = dst_f->o_width | (dst_f->o_height << CHNL_SCL_IMG_CFG_HEIGHT_OFFSET); + writel(val1, mxc_isi->regs + CHNL_SCL_IMG_CFG); + + writel(0, mxc_isi->regs + CHNL_SCALE_OFFSET); + + return; +} + +void mxc_isi_channel_init(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + /* sw reset */ + mxc_isi_channel_sw_reset(mxc_isi); + + /* Init channel clk first */ + val = readl(mxc_isi->regs + CHNL_CTRL); + val |= (CHNL_CTRL_CLK_EN_ENABLE << CHNL_CTRL_CLK_EN_OFFSET); + writel(val, mxc_isi->regs + CHNL_CTRL); +} + +void mxc_isi_channel_deinit(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + /* sw reset */ + mxc_isi_channel_sw_reset(mxc_isi); + + /* deinit channel clk first */ + val = (CHNL_CTRL_CLK_EN_ENABLE << CHNL_CTRL_CLK_EN_OFFSET); + writel(val, mxc_isi->regs + CHNL_CTRL); +} + +void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_frame *src_f = &mxc_isi->isi_cap.src_f; + u32 val; + + /* images having higher than 2048 horizontal resolution */ + chain_buf(mxc_isi); + + /* config output frame size and format */ + val = src_f->o_width | (src_f->o_height << CHNL_IMG_CFG_HEIGHT_OFFSET); + writel(val, mxc_isi->regs + CHNL_IMG_CFG); + + /* scale size need to equal input size when scaling disabled*/ + writel(val, mxc_isi->regs + CHNL_SCL_IMG_CFG); + + /* check csc and scaling */ + mxc_isi_channel_set_csc(mxc_isi); + + mxc_isi_channel_set_scaling(mxc_isi); + + /* select the source input / src type / virtual channel for mipi*/ + mxc_isi_channel_source_config(mxc_isi); + + /* line pitch */ + val = dst_f->bytesperline[0]; + writel(val, mxc_isi->regs + CHNL_OUT_BUF_PITCH); + + /* TODO */ + mxc_isi_channel_set_flip(mxc_isi); + + if (mxc_isi->alphaen) + mxc_isi_channel_set_alpha(mxc_isi); + + val = readl(mxc_isi->regs + CHNL_CTRL); + val &= ~CHNL_CTRL_CHNL_BYPASS_MASK; + + /* Bypass channel */ + if (!mxc_isi->cscen && !mxc_isi->scale) + val |= (CHNL_CTRL_CHNL_BYPASS_ENABLE << CHNL_CTRL_CHNL_BYPASS_OFFSET); + + writel(val, mxc_isi->regs + CHNL_CTRL); +} + +void mxc_isi_clean_registers(struct mxc_isi_dev *mxc_isi) +{ + u32 status; + + status = mxc_isi_get_irq_status(mxc_isi); + mxc_isi_clean_irq_status(mxc_isi, status); +} + +void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + val = readl(mxc_isi->regs + CHNL_CTRL); + val |= (CHNL_CTRL_CHNL_EN_ENABLE << CHNL_CTRL_CHNL_EN_OFFSET); + val |= 0xff << CHNL_CTRL_BLANK_PXL_OFFSET; + writel(val, mxc_isi->regs + CHNL_CTRL); + + mxc_isi_clean_registers(mxc_isi); + mxc_isi_enable_irq(mxc_isi); + msleep(300); + dump_isi_regs(mxc_isi); +} + +void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + mxc_isi_disable_irq(mxc_isi); + + val = readl(mxc_isi->regs + CHNL_CTRL); + val &= ~(CHNL_CTRL_CHNL_EN_MASK | CHNL_CTRL_CLK_EN_MASK); + val |= (CHNL_CTRL_CHNL_EN_DISABLE << CHNL_CTRL_CHNL_EN_OFFSET); + val |= (CHNL_CTRL_CLK_EN_DISABLE << CHNL_CTRL_CLK_EN_OFFSET); + writel(val, mxc_isi->regs + CHNL_CTRL); +} + +void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + val = CHNL_IER_FRM_RCVD_EN_MASK | + CHNL_IER_OFLW_Y_BUF_EN_MASK | + CHNL_IER_AXI_WR_ERR_U_EN_MASK | + CHNL_IER_AXI_WR_ERR_V_EN_MASK | + CHNL_IER_AXI_WR_ERR_Y_EN_MASK | + CHNL_IER_OFLW_PANIC_V_BUF_EN_MASK | + CHNL_IER_EXCS_OFLW_V_BUF_EN_MASK | + CHNL_IER_OFLW_V_BUF_EN_MASK | + CHNL_IER_OFLW_PANIC_U_BUF_EN_MASK | + CHNL_IER_EXCS_OFLW_U_BUF_EN_MASK | + CHNL_IER_OFLW_U_BUF_EN_MASK | + CHNL_IER_OFLW_PANIC_Y_BUF_EN_MASK | + CHNL_IER_EXCS_OFLW_Y_BUF_EN_MASK | + CHNL_IER_OFLW_Y_BUF_EN_MASK; + + writel(val, mxc_isi->regs + CHNL_IER); +} + +void mxc_isi_disable_irq(struct mxc_isi_dev *mxc_isi) +{ + writel(0, mxc_isi->regs + CHNL_IER); +} + +u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi) +{ + return readl(mxc_isi->regs + CHNL_STS); +} + +void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val) +{ + writel(val, mxc_isi->regs + CHNL_STS); +} diff --git a/drivers/staging/media/imx/imx8-isi-hw.h b/drivers/staging/media/imx/imx8-isi-hw.h new file mode 100644 index 000000000000..e15a9abb743c --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-hw.h @@ -0,0 +1,477 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 NXP Semiconductor + * + */ + +#ifndef __MXC_ISI_HW_H__ +#define __MXC_ISI_HW_H__ + +#include +#include +#include +#include +#include +#include + +#include "imx8-isi-core.h" + +/* ISI Registers Define */ +/* Channel Control Register */ +#define CHNL_CTRL 0x0 +#define CHNL_CTRL_CHNL_EN_OFFSET 31 +#define CHNL_CTRL_CHNL_EN_MASK 0x80000000 +#define CHNL_CTRL_CHNL_EN_DISABLE 0 +#define CHNL_CTRL_CHNL_EN_ENABLE 1 +#define CHNL_CTRL_CLK_EN_OFFSET 30 +#define CHNL_CTRL_CLK_EN_MASK 0x40000000 +#define CHNL_CTRL_CLK_EN_DISABLE 0 +#define CHNL_CTRL_CLK_EN_ENABLE 1 +#define CHNL_CTRL_CHNL_BYPASS_OFFSET 29 +#define CHNL_CTRL_CHNL_BYPASS_MASK 0x20000000 +#define CHNL_CTRL_CHNL_BYPASS_ENABLE 1 +#define CHNL_CTRL_CHAIN_BUF_OFFSET 25 +#define CHNL_CTRL_CHAIN_BUF_MASK 0x60000 +#define CHNL_CTRL_CHAIN_BUF_NO_CHAIN 0 +#define CHNL_CTRL_CHAIN_BUF_2_CHAIN 1 +#define CHNL_CTRL_SW_RST_OFFSET 24 +#define CHNL_CTRL_SW_RST_MASK 0x1000000 +#define CHNL_CTRL_SW_RST 0x1000000 +#define CHNL_CTRL_BLANK_PXL_OFFSET 16 +#define CHNL_CTRL_MIPI_VC_ID_OFFSET 6 +#define CHNL_CTRL_MIPI_VC_ID_MASK 0xc0 +#define CHNL_CTRL_MIPI_VC_ID_VC0 0 +#define CHNL_CTRL_MIPI_VC_ID_VC1 1 +#define CHNL_CTRL_MIPI_VC_ID_VC2 2 +#define CHNL_CTRL_MIPI_VC_ID_VC3 3 +#define CHNL_CTRL_SRC_TYPE_OFFSET 4 +#define CHNL_CTRL_SRC_TYPE_MASK 0x10 +#define CHNL_CTRL_SRC_TYPE_DEVICE 0 +#define CHNL_CTRL_SRC_TYPE_MEMORY 1 +#define CHNL_CTRL_SRC_INPUT_OFFSET 0 +#define CHNL_CTRL_SRC_INPUT_MASK 0x7 +#define CHNL_CTRL_SRC_INPUT_DC0 0 +#define CHNL_CTRL_SRC_INPUT_DC1 1 +#define CHNL_CTRL_SRC_INPUT_MIPI0 2 +#define CHNL_CTRL_SRC_INPUT_MIPI1 3 +#define CHNL_CTRL_SRC_INPUT_HDMI 4 +#define CHNL_CTRL_SRC_INPUT_CSI 4 +#define CHNL_CTRL_SRC_INPUT_MEMORY 5 + +/* Channel Image Control Register */ +#define CHNL_IMG_CTRL 0x4 +#define CHNL_IMG_CTRL_FORMAT_OFFSET 24 +#define CHNL_IMG_CTRL_FORMAT_MASK 0x3F000000 +#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_OFFSET 16 +#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK 0xFF0000 +#define CHNL_IMG_CTRL_GBL_ALPHA_EN_OFFSET 15 +#define CHNL_IMG_CTRL_GBL_ALPHA_EN_ENABLE 1 +#define CHNL_IMG_CTRL_GBL_ALPHA_EN_MASK 0x8000 +#define CHNL_IMG_CTRL_DEINT_OFFSET 12 +#define CHNL_IMG_CTRL_DEINT_MASK 0x7000 +#define CHNL_IMG_CTRL_DEINT_WEAVE_ODD_EVEN 2 +#define CHNL_IMG_CTRL_DEINT_WEAVE_EVEN_ODD 3 +#define CHNL_IMG_CTRL_DEINT_BLEND_ODD_EVEN 4 +#define CHNL_IMG_CTRL_DEINT_BLEND_EVEN_ODD 5 +#define CHNL_IMG_CTRL_DEINT_LDOUBLE_ODD_EVEN 6 +#define CHNL_IMG_CTRL_DEINT_LDOUBLE_EVEN_ODD 7 +#define CHNL_IMG_CTRL_DEC_X_OFFSET 10 +#define CHNL_IMG_CTRL_DEC_X_MASK 0xC00 +#define CHNL_IMG_CTRL_DEC_X_0 0 +#define CHNL_IMG_CTRL_DEC_X_2 1 +#define CHNL_IMG_CTRL_DEC_X_4 2 +#define CHNL_IMG_CTRL_DEC_X_8 3 +#define CHNL_IMG_CTRL_DEC_Y_OFFSET 8 +#define CHNL_IMG_CTRL_DEC_Y_MASK 0x300 +#define CHNL_IMG_CTRL_DEC_Y_0 0 +#define CHNL_IMG_CTRL_DEC_Y_2 1 +#define CHNL_IMG_CTRL_DEC_Y_4 2 +#define CHNL_IMG_CTRL_DEC_Y_8 3 +#define CHNL_IMG_CTRL_CROP_EN_OFFSET 7 +#define CHNL_IMG_CTRL_CROP_EN_MASK 0x80 +#define CHNL_IMG_CTRL_CROP_EN_ENABLE 1 +#define CHNL_IMG_CTRL_VFLIP_EN_OFFSET 6 +#define CHNL_IMG_CTRL_VFLIP_EN_MASK 0x40 +#define CHNL_IMG_CTRL_VFLIP_EN_ENABLE 1 +#define CHNL_IMG_CTRL_HFLIP_EN_OFFSET 5 +#define CHNL_IMG_CTRL_HFLIP_EN_MASK 0x20 +#define CHNL_IMG_CTRL_HFLIP_EN_ENABLE 1 +#define CHNL_IMG_CTRL_YCBCR_MODE_OFFSET 3 +#define CHNL_IMG_CTRL_YCBCR_MODE_MASK 0x8 +#define CHNL_IMG_CTRL_YCBCR_MODE_ENABLE 1 +#define CHNL_IMG_CTRL_CSC_MODE_OFFSET 1 +#define CHNL_IMG_CTRL_CSC_MODE_MASK 0x6 +#define CHNL_IMG_CTRL_CSC_MODE_YUV2RGB 0 +#define CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB 1 +#define CHNL_IMG_CTRL_CSC_MODE_RGB2YUV 2 +#define CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR 3 +#define CHNL_IMG_CTRL_CSC_BYPASS_OFFSET 0 +#define CHNL_IMG_CTRL_CSC_BYPASS_MASK 0x1 +#define CHNL_IMG_CTRL_CSC_BYPASS_ENABLE 0x1 + +/* Channel Output Buffer Control Register */ +#define CHNL_OUT_BUF_CTRL 0x8 +#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_OFFSET 15 +#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_MASK 0x8000 +#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_OFFSET 14 +#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_MASK 0x4000 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_OFFSET 6 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_MASK 0xC0 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_NO_PANIC 0 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_25 1 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_50 2 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_75 3 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_OFFSET 3 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_MASK 0x18 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_NO_PANIC 0 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_25 1 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_50 2 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_75 3 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_OFFSET 0 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_MASK 0x3 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_NO_PANIC 0 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_25 1 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_50 2 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_75 3 + +/* Channel Image Configuration */ +#define CHNL_IMG_CFG 0xC +#define CHNL_IMG_CFG_HEIGHT_OFFSET 16 +#define CHNL_IMG_CFG_HEIGHT_MASK 0x1FFF0000 +#define CHNL_IMG_CFG_WIDTH_OFFSET 0 +#define CHNL_IMG_CFG_WIDTH_MASK 0x1FFF + +/* Channel Interrupt Enable Register */ +#define CHNL_IER 0x10 +#define CHNL_IER_MEM_RD_DONE_EN_OFFSET 31 +#define CHNL_IER_MEM_RD_DONE_EN_MASK 0x80000000 +#define CHNL_IER_MEM_RD_DONE_EN_ENABLE 1 +#define CHNL_IER_LINE_RCVD_EN_OFFSET 30 +#define CHNL_IER_LINE_RCVD_EN_MASK 0x40000000 +#define CHNL_IER_LINE_RCVD_EN_ENABLE 1 +#define CHNL_IER_FRM_RCVD_EN_OFFSET 29 +#define CHNL_IER_FRM_RCVD_EN_MASK 0x20000000 +#define CHNL_IER_FRM_RCVD_EN_ENABLE 1 +#define CHNL_IER_AXI_WR_ERR_V_EN_OFFSET 28 +#define CHNL_IER_AXI_WR_ERR_V_EN_MASK 0x10000000 +#define CHNL_IER_AXI_WR_ERR_V_EN_ENABLE 1 +#define CHNL_IER_AXI_WR_ERR_U_EN_OFFSET 27 +#define CHNL_IER_AXI_WR_ERR_U_EN_MASK 0x8000000 +#define CHNL_IER_AXI_WR_ERR_U_EN_ENABLE 1 +#define CHNL_IER_AXI_WR_ERR_Y_EN_OFFSET 26 +#define CHNL_IER_AXI_WR_ERR_Y_EN_MASK 0x4000000 +#define CHNL_IER_AXI_WR_ERR_Y_EN_ENABLE 1 +#define CHNL_IER_AXI_RD_ERR_EN_OFFSET 25 +#define CHNL_IER_AXI_RD_ERR_EN_MASK 0x2000000 +#define CHNL_IER_AXI_RD_ERR_EN_ENABLE 1 +#define CHNL_IER_OFLW_PANIC_V_BUF_EN_OFFSET 24 +#define CHNL_IER_OFLW_PANIC_V_BUF_EN_MASK 0x1000000 +#define CHNL_IER_OFLW_PANIC_V_BUF_EN_ENABLE 1 +#define CHNL_IER_EXCS_OFLW_V_BUF_EN_OFFSET 23 +#define CHNL_IER_EXCS_OFLW_V_BUF_EN_MASK 0x800000 +#define CHNL_IER_EXCS_OFLW_V_BUF_EN_ENABLE 1 +#define CHNL_IER_OFLW_V_BUF_EN_OFFSET 22 +#define CHNL_IER_OFLW_V_BUF_EN_MASK 0x400000 +#define CHNL_IER_OFLW_V_BUF_EN_ENABLE 1 +#define CHNL_IER_OFLW_PANIC_U_BUF_EN_OFFSET 21 +#define CHNL_IER_OFLW_PANIC_U_BUF_EN_MASK 0x200000 +#define CHNL_IER_OFLW_PANIC_U_BUF_EN_ENABLE 1 +#define CHNL_IER_EXCS_OFLW_U_BUF_EN_OFFSET 20 +#define CHNL_IER_EXCS_OFLW_U_BUF_EN_MASK 0x100000 +#define CHNL_IER_EXCS_OFLW_U_BUF_EN_ENABLE 1 +#define CHNL_IER_OFLW_U_BUF_EN_OFFSET 19 +#define CHNL_IER_OFLW_U_BUF_EN_MASK 0x80000 +#define CHNL_IER_OFLW_U_BUF_EN_ENABLE 1 +#define CHNL_IER_OFLW_PANIC_Y_BUF_EN_OFFSET 18 +#define CHNL_IER_OFLW_PANIC_Y_BUF_EN_MASK 0x40000 +#define CHNL_IER_OFLW_PANIC_Y_BUF_EN_ENABLE 1 +#define CHNL_IER_EXCS_OFLW_Y_BUF_EN_OFFSET 17 +#define CHNL_IER_EXCS_OFLW_Y_BUF_EN_MASK 0x20000 +#define CHNL_IER_EXCS_OFLW_Y_BUF_EN_ENABLE 1 +#define CHNL_IER_OFLW_Y_BUF_EN_OFFSET 16 +#define CHNL_IER_OFLW_Y_BUF_EN_MASK 0x10000 +#define CHNL_IER_OFLW_Y_BUF_EN_ENABLE 1 + +/* Channel Status Register */ +#define CHNL_STS 0x14 +#define CHNL_STS_MEM_RD_DONE_OFFSET 31 +#define CHNL_STS_MEM_RD_DONE_MASK 0x80000000 +#define CHNL_STS_MEM_RD_DONE_ENABLE 1 +#define CHNL_STS_LINE_STRD_OFFSET 30 +#define CHNL_STS_LINE_STRD_MASK 0x40000000 +#define CHNL_STS_LINE_STRD_ENABLE 1 +#define CHNL_STS_FRM_STRD_OFFSET 29 +#define CHNL_STS_FRM_STRD_MASK 0x20000000 +#define CHNL_STS_FRM_STRD_ENABLE 1 +#define CHNL_STS_AXI_WR_ERR_V_OFFSET 28 +#define CHNL_STS_AXI_WR_ERR_V_MASK 0x10000000 +#define CHNL_STS_AXI_WR_ERR_V_ENABLE 1 +#define CHNL_STS_AXI_WR_ERR_U_OFFSET 27 +#define CHNL_STS_AXI_WR_ERR_U_MASK 0x8000000 +#define CHNL_STS_AXI_WR_ERR_U_ENABLE 1 +#define CHNL_STS_AXI_WR_ERR_Y_OFFSET 26 +#define CHNL_STS_AXI_WR_ERR_Y_MASK 0x4000000 +#define CHNL_STS_AXI_WR_ERR_Y_ENABLE 1 +#define CHNL_STS_AXI_RD_ERR_OFFSET 25 +#define CHNL_STS_AXI_RD_ERR_MASK 0x2000000 +#define CHNL_STS_AXI_RD_ERR_ENABLE 1 +#define CHNL_STS_OFLW_PANIC_V_BUF_OFFSET 24 +#define CHNL_STS_OFLW_PANIC_V_BUF_MASK 0x1000000 +#define CHNL_STS_OFLW_PANIC_V_BUF_ENABLE 1 +#define CHNL_STS_EXCS_OFLW_V_BUF_OFFSET 23 +#define CHNL_STS_EXCS_OFLW_V_BUF_MASK 0x800000 +#define CHNL_STS_EXCS_OFLW_V_BUF_ENABLE 1 +#define CHNL_STS_OFLW_V_BUF_OFFSET 22 +#define CHNL_STS_OFLW_V_BUF_MASK 0x400000 +#define CHNL_STS_OFLW_V_BUF_ENABLE 1 +#define CHNL_STS_OFLW_PANIC_U_BUF_OFFSET 21 +#define CHNL_STS_OFLW_PANIC_U_BUF_MASK 0x200000 +#define CHNL_STS_OFLW_PANIC_U_BUF_ENABLE 1 +#define CHNL_STS_EXCS_OFLW_U_BUF_OFFSET 20 +#define CHNL_STS_EXCS_OFLW_U_BUF_MASK 0x100000 +#define CHNL_STS_EXCS_OFLW_U_BUF_ENABLE 1 +#define CHNL_STS_OFLW_U_BUF_OFFSET 19 +#define CHNL_STS_OFLW_U_BUF_MASK 0x80000 +#define CHNL_STS_OFLW_U_BUF_ENABLE 1 +#define CHNL_STS_OFLW_PANIC_Y_BUF_OFFSET 18 +#define CHNL_STS_OFLW_PANIC_Y_BUF_MASK 0x40000 +#define CHNL_STS_OFLW_PANIC_Y_BUF_ENABLE 1 +#define CHNL_STS_EXCS_OFLW_Y_BUF_OFFSET 17 +#define CHNL_STS_EXCS_OFLW_Y_BUF_MASK 0x20000 +#define CHNL_STS_EXCS_OFLW_Y_BUF_ENABLE 1 +#define CHNL_STS_OFLW_Y_BUF_OFFSET 16 +#define CHNL_STS_OFLW_Y_BUF_MASK 0x10000 +#define CHNL_STS_OFLW_Y_BUF_ENABLE 1 +#define CHNL_STS_OFLW_BYTES_OFFSET 0 +#define CHNL_STS_OFLW_BYTES_MASK 0xFF + +/* Channel Scale Factor Register */ +#define CHNL_SCALE_FACTOR 0x18 +#define CHNL_SCALE_FACTOR_Y_SCALE_OFFSET 16 +#define CHNL_SCALE_FACTOR_Y_SCALE_MASK 0x3FFF0000 +#define CHNL_SCALE_FACTOR_X_SCALE_OFFSET 0 +#define CHNL_SCALE_FACTOR_X_SCALE_MASK 0x3FFF + +/* Channel Scale Offset Register */ +#define CHNL_SCALE_OFFSET 0x1C +#define CHNL_SCALE_OFFSET_Y_SCALE_OFFSET 16 +#define CHNL_SCALE_OFFSET_Y_SCALE_MASK 0xFFF0000 +#define CHNL_SCALE_OFFSET_X_SCALE_OFFSET 0 +#define CHNL_SCALE_OFFSET_X_SCALE_MASK 0xFFF + +/* Channel Crop Upper Left Corner Coordinate Register */ +#define CHNL_CROP_ULC 0x20 +#define CHNL_CROP_ULC_X_OFFSET 16 +#define CHNL_CROP_ULC_X_MASK 0xFFF0000 +#define CHNL_CROP_ULC_Y_OFFSET 0 +#define CHNL_CROP_ULC_Y_MASK 0xFFF + +/* Channel Crop Lower Right Corner Coordinate Register */ +#define CHNL_CROP_LRC 0x24 +#define CHNL_CROP_LRC_X_OFFSET 16 +#define CHNL_CROP_LRC_X_MASK 0xFFF0000 +#define CHNL_CROP_LRC_Y_OFFSET 0 +#define CHNL_CROP_LRC_Y_MASK 0xFFF + +/* Channel Color Space Conversion Coefficient Register 0 */ +#define CHNL_CSC_COEFF0 0x28 +#define CHNL_CSC_COEFF0_A2_OFFSET 16 +#define CHNL_CSC_COEFF0_A2_MASK 0x7FF0000 +#define CHNL_CSC_COEFF0_A1_OFFSET 0 +#define CHNL_CSC_COEFF0_A1_MASK 0x7FF + +/* Channel Color Space Conversion Coefficient Register 1 */ +#define CHNL_CSC_COEFF1 0x2C +#define CHNL_CSC_COEFF1_B1_OFFSET 16 +#define CHNL_CSC_COEFF1_B1_MASK 0x7FF0000 +#define CHNL_CSC_COEFF1_A3_OFFSET 0 +#define CHNL_CSC_COEFF1_A3_MASK 0x7FF + +/* Channel Color Space Conversion Coefficient Register 2 */ +#define CHNL_CSC_COEFF2 0x30 +#define CHNL_CSC_COEFF2_B3_OFFSET 16 +#define CHNL_CSC_COEFF2_B3_MASK 0x7FF0000 +#define CHNL_CSC_COEFF2_B2_OFFSET 0 +#define CHNL_CSC_COEFF2_B2_MASK 0x7FF + +/* Channel Color Space Conversion Coefficient Register 3 */ +#define CHNL_CSC_COEFF3 0x34 +#define CHNL_CSC_COEFF3_C2_OFFSET 16 +#define CHNL_CSC_COEFF3_C2_MASK 0x7FF0000 +#define CHNL_CSC_COEFF3_C1_OFFSET 0 +#define CHNL_CSC_COEFF3_C1_MASK 0x7FF + +/* Channel Color Space Conversion Coefficient Register 4 */ +#define CHNL_CSC_COEFF4 0x38 +#define CHNL_CSC_COEFF4_D1_OFFSET 16 +#define CHNL_CSC_COEFF4_D1_MASK 0x1FF0000 +#define CHNL_CSC_COEFF4_C3_OFFSET 0 +#define CHNL_CSC_COEFF4_C3_MASK 0x7FF + +/* Channel Color Space Conversion Coefficient Register 5 */ +#define CHNL_CSC_COEFF5 0x3C +#define CHNL_CSC_COEFF5_D3_OFFSET 16 +#define CHNL_CSC_COEFF5_D3_MASK 0x1FF0000 +#define CHNL_CSC_COEFF5_D2_OFFSET 0 +#define CHNL_CSC_COEFF5_D2_MASK 0x1FF + +/* Channel Alpha Value Register for ROI 0 */ +#define CHNL_ROI_0_ALPHA 0x40 +#define CHNL_ROI_0_ALPHA_OFFSET 24 +#define CHNL_ROI_0_ALPHA_MASK 0xFF000000 +#define CHNL_ROI_0_ALPHA_EN_OFFSET 16 +#define CHNL_ROI_0_ALPHA_EN_MASK 0x10000 + +/* Channel Upper Left Coordinate Register for ROI 0 */ +#define CHNL_ROI_0_ULC 0x44 +#define CHNL_ROI_0_ULC_X_OFFSET 16 +#define CHNL_ROI_0_ULC_X_MASK 0xFFF0000 +#define CHNL_ROI_0_ULC_Y_OFFSET 0 +#define CHNL_ROI_0_ULC_Y_MASK 0xFFF + +/* Channel Lower Right Coordinate Register for ROI 0 */ +#define CHNL_ROI_0_LRC 0x48 +#define CHNL_ROI_0_LRC_X_OFFSET 16 +#define CHNL_ROI_0_LRC_X_MASK 0xFFF0000 +#define CHNL_ROI_0_LRC_Y_OFFSET 0 +#define CHNL_ROI_0_LRC_Y_MASK 0xFFF + +/* Channel Alpha Value Register for ROI 1 */ +#define CHNL_ROI_1_ALPHA 0x4C +#define CHNL_ROI_1_ALPHA_OFFSET 24 +#define CHNL_ROI_1_ALPHA_MASK 0xFF000000 +#define CHNL_ROI_1_ALPHA_EN_OFFSET 16 +#define CHNL_ROI_1_ALPHA_EN_MASK 0x10000 + +/* Channel Upper Left Coordinate Register for ROI 1 */ +#define CHNL_ROI_1_ULC 0x50 +#define CHNL_ROI_1_ULC_X_OFFSET 16 +#define CHNL_ROI_1_ULC_X_MASK 0xFFF0000 +#define CHNL_ROI_1_ULC_Y_OFFSET 0 +#define CHNL_ROI_1_ULC_Y_MASK 0xFFF + +/* Channel Lower Right Coordinate Register for ROI 1 */ +#define CHNL_ROI_1_LRC 0x54 +#define CHNL_ROI_1_LRC_X_OFFSET 16 +#define CHNL_ROI_1_LRC_X_MASK 0xFFF0000 +#define CHNL_ROI_1_LRC_Y_OFFSET 0 +#define CHNL_ROI_1_LRC_Y_MASK 0xFFF + +/* Channel Alpha Value Register for ROI 2 */ +#define CHNL_ROI_2_ALPHA 0x58 +#define CHNL_ROI_2_ALPHA_OFFSET 24 +#define CHNL_ROI_2_ALPHA_MASK 0xFF000000 +#define CHNL_ROI_2_ALPHA_EN_OFFSET 16 +#define CHNL_ROI_2_ALPHA_EN_MASK 0x10000 + +/* Channel Upper Left Coordinate Register for ROI 2 */ +#define CHNL_ROI_2_ULC 0x5C +#define CHNL_ROI_2_ULC_X_OFFSET 16 +#define CHNL_ROI_2_ULC_X_MASK 0xFFF0000 +#define CHNL_ROI_2_ULC_Y_OFFSET 0 +#define CHNL_ROI_2_ULC_Y_MASK 0xFFF + +/* Channel Lower Right Coordinate Register for ROI 2 */ +#define CHNL_ROI_2_LRC 0x60 +#define CHNL_ROI_2_LRC_X_OFFSET 16 +#define CHNL_ROI_2_LRC_X_MASK 0xFFF0000 +#define CHNL_ROI_2_LRC_Y_OFFSET 0 +#define CHNL_ROI_2_LRC_Y_MASK 0xFFF + +/* Channel Alpha Value Register for ROI 3 */ +#define CHNL_ROI_3_ALPHA 0x64 +#define CHNL_ROI_3_ALPHA_OFFSET 24 +#define CHNL_ROI_3_ALPHA_MASK 0xFF000000 +#define CHNL_ROI_3_ALPHA_EN_OFFSET 16 +#define CHNL_ROI_3_ALPHA_EN_MASK 0x10000 + +/* Channel Upper Left Coordinate Register for ROI 3 */ +#define CHNL_ROI_3_ULC 0x68 +#define CHNL_ROI_3_ULC_X_OFFSET 16 +#define CHNL_ROI_3_ULC_X_MASK 0xFFF0000 +#define CHNL_ROI_3_ULC_Y_OFFSET 0 +#define CHNL_ROI_3_ULC_Y_MASK 0xFFF + +/* Channel Lower Right Coordinate Register for ROI 3 */ +#define CHNL_ROI_3_LRC 0x6C +#define CHNL_ROI_3_LRC_X_OFFSET 16 +#define CHNL_ROI_3_LRC_X_MASK 0xFFF0000 +#define CHNL_ROI_3_LRC_Y_OFFSET 0 +#define CHNL_ROI_3_LRC_Y_MASK 0xFFF + +/* Channel RGB or Luma (Y) Output Buffer 1 Address */ +#define CHNL_OUT_BUF1_ADDR_Y 0x70 + +/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 1 Address */ +#define CHNL_OUT_BUF1_ADDR_U 0x74 + +/* Channel Chroma (V/Cr) Output Buffer 1 Address */ +#define CHNL_OUT_BUF1_ADDR_V 0x78 + +/* Channel Output Buffer Pitch */ +#define CHNL_OUT_BUF_PITCH 0x7C +#define CHNL_OUT_BUF_PITCH_LINE_PITCH_OFFSET 0 +#define CHNL_OUT_BUF_PITCH_LINE_PITCH_MASK 0xFFFF + +/* Channel Input Buffer Address */ +#define CHNL_IN_BUF_ADDR 0x80 + +/* Channel Input Buffer Pitch */ +#define CHNL_IN_BUF_PITCH 0x84 +#define CHNL_IN_BUF_PITCH_FRM_PITCH_OFFSET 16 +#define CHNL_IN_BUF_PITCH_FRM_PITCH_MASK 0xFFFF0000 +#define CHNL_IN_BUF_PITCH_LINE_PITCH_OFFSET 0 +#define CHNL_IN_BUF_PITCH_LINE_PITCH_MASK 0xFFFF + +/* Channel RGB or Luma (Y) Output Buffer 2 Address */ +#define CHNL_OUT_BUF2_ADDR_Y 0x8C + +/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 2 Address */ +#define CHNL_OUT_BUF2_ADDR_U 0x90 + +/* Channel Chroma (V/Cr) Output Buffer 2 Address */ +#define CHNL_OUT_BUF2_ADDR_V 0x94 + +/* Channel scale image config */ +#define CHNL_SCL_IMG_CFG 0x98 +#define CHNL_SCL_IMG_CFG_HEIGHT_OFFSET 16 +#define CHNL_SCL_IMG_CFG_HEIGHT_MASK 0x1FFF0000 +#define CHNL_SCL_IMG_CFG_WIDTH_OFFSET 0 +#define CHNL_SCL_IMG_CFG_WIDTH_MASK 0x1FFF + +enum isi_csi_coeff { + YUV2RGB = 0, + RGB2YUV, +}; + +void mxc_isi_channel_init(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_deinit(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_buffer *buf); +void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_sw_reset(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_hw_reset(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_source_config(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_flip(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_alpha_roi0(struct mxc_isi_dev *mxc_isi, + struct v4l2_rect *rect); +void mxc_isi_channel_set_alpha(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_crop(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_memory_image(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_scaler(struct mxc_isi_dev *mxc_isi); + +void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val); +u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi); +void mxc_isi_clean_registers(struct mxc_isi_dev *mxc_isi); +void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi); +void mxc_isi_disable_irq(struct mxc_isi_dev *mxc_isi); + +void dump_isi_regs(struct mxc_isi_dev *mxc_isi); +#endif /* __MXC_ISI_HW_H__ */ From 6a2aaf9f11eda7df260eeb62688e1626b9ef322b Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Tue, 11 Jun 2019 13:05:46 +0800 Subject: [PATCH 3/9] MLK-21985-8 media: isi: fix build for next 20190607 upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API change due to commit: 7e98b7b542a4 ("media: v4l2: Get rid of ->vidioc_enum_fmt_vid_{cap, out}_mplane") ../drivers/staging/media/imx/imx8-isi-cap.c:1211:2: error: unknown field ‘vidioc_enum_fmt_vid_cap_mplane’ specified in initializer .vidioc_enum_fmt_vid_cap_mplane = mxc_isi_cap_enum_fmt_mplane, Signed-off-by: Dong Aisheng --- drivers/staging/media/imx/imx8-isi-cap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/staging/media/imx/imx8-isi-cap.c b/drivers/staging/media/imx/imx8-isi-cap.c index 35f3659e5643..5976c6b756bc 100644 --- a/drivers/staging/media/imx/imx8-isi-cap.c +++ b/drivers/staging/media/imx/imx8-isi-cap.c @@ -774,7 +774,7 @@ static int mxc_isi_cap_querycap(struct file *file, void *priv, return 0; } -static int mxc_isi_cap_enum_fmt_mplane(struct file *file, void *priv, +static int mxc_isi_cap_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f) { struct mxc_isi_dev *mxc_isi = video_drvdata(file); @@ -1208,7 +1208,7 @@ static int mxc_isi_cap_enum_frameintervals(struct file *file, void *fh, static const struct v4l2_ioctl_ops mxc_isi_capture_ioctl_ops = { .vidioc_querycap = mxc_isi_cap_querycap, - .vidioc_enum_fmt_vid_cap_mplane = mxc_isi_cap_enum_fmt_mplane, + .vidioc_enum_fmt_vid_cap = mxc_isi_cap_enum_fmt, .vidioc_try_fmt_vid_cap_mplane = mxc_isi_cap_try_fmt_mplane, .vidioc_s_fmt_vid_cap_mplane = mxc_isi_cap_s_fmt_mplane, .vidioc_g_fmt_vid_cap_mplane = mxc_isi_cap_g_fmt_mplane, From 4bcce30760417c4016053ec52c18900b56ae52e4 Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Wed, 31 Jul 2019 20:14:57 +0800 Subject: [PATCH 4/9] media: isi: fix boot warning by intialize device_caps Caused by: 049e684 media: v4l2-dev: fix WARN_ON(!vdev->device_caps) 3c13505 media: v4l2-dev/ioctl: require non-zero device_caps, verify sane querycap results [ 2.085781] WARNING: CPU: 2 PID: 32 at ../drivers/media/v4l2-core/v4l2-dev.c:864 __video_register_device+0x70/0x1524 [ 2.096305] Modules linked in: [ 2.099369] CPU: 2 PID: 32 Comm: kworker/2:1 Not tainted 5.3.0-rc2-next-20190730-01098-g446d4cd #475 [ 2.108500] Hardware name: Freescale i.MX8QM MEK (DT) [ 2.113559] Workqueue: events deferred_probe_work_func [ 2.118703] pstate: 60000005 (nZCv daif -PAN -UAO) [ 2.123495] pc : __video_register_device+0x70/0x1524 [ 2.128466] lr : __video_register_device+0x70/0x1524 [ 2.133433] sp : ffff000011d8b9d0 [ 2.136742] x29: ffff000011d8b9d0 x28: ffff8008ff45edc0 [ 2.142057] x27: ffff8008f6f35580 x26: ffff000011b2b148 [ 2.147374] x25: ffff8008f6e381c0 x24: 0000000000000001 [ 2.152691] x23: 0000000000000000 x22: ffff8008f6f35818 [ 2.158008] x21: ffff8008f6e381c0 x20: 0000000000000001 [ 2.163324] x19: ffff000011989000 x18: ffff000011cfdfff [ 2.168641] x17: 0000000000000000 x16: ffff000011cfdfff [ 2.173958] x15: ffff7e0023dbecc0 x14: 0000000000000000 [ 2.179274] x13: 0000000000000000 x12: 0000000000000028 [ 2.184591] x11: 0101010101010101 x10: 00000000000009a0 [ 2.189908] x9 : ffff000011d8b700 x8 : ffff8008f813c100 [ 2.195225] x7 : 0000000000000002 x6 : 00000000000000ed [ 2.200533] x5 : 0000000000000000 x4 : 000000000199047f [ 2.205849] x3 : 0000000000000001 x2 : ddfc02a91bc52500 [ 2.211166] x1 : 0000000000000000 x0 : 0000000000000024 [ 2.216488] Call trace: [ 2.218940] __video_register_device+0x70/0x1524 [ 2.223563] mxc_isi_subdev_registered+0x1f8/0x230 [ 2.228357] v4l2_device_register_subdev+0xb0/0x160 [ 2.233236] mxc_md_probe+0x2b0/0x6b8 [ 2.236897] platform_drv_probe+0x4c/0xb0 [ 2.240907] really_probe+0x1c4/0x2d0 [ 2.244571] driver_probe_device+0x58/0xfc [ 2.248665] __device_attach_driver+0x90/0xac [ 2.253020] bus_for_each_drv+0x68/0xbc [ 2.256855] __device_attach+0xe0/0x138 [ 2.260686] device_initial_probe+0x10/0x18 [ 2.264867] bus_probe_device+0x90/0x98 [ 2.268696] deferred_probe_work_func+0x70/0xa4 [ 2.273230] process_one_work+0x13c/0x2b4 [ 2.277236] worker_thread+0x35c/0x3e4 [ 2.280985] kthread+0xf8/0x124 [ 2.284132] ret_from_fork+0x10/0x18 Signed-off-by: Dong Aisheng --- drivers/staging/media/imx/imx8-isi-cap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/staging/media/imx/imx8-isi-cap.c b/drivers/staging/media/imx/imx8-isi-cap.c index 5976c6b756bc..495a5b7ff2cf 100644 --- a/drivers/staging/media/imx/imx8-isi-cap.c +++ b/drivers/staging/media/imx/imx8-isi-cap.c @@ -1506,6 +1506,7 @@ static int mxc_isi_register_cap_device(struct mxc_isi_dev *mxc_isi, vdev->queue = q; vdev->lock = &mxc_isi->lock; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE; video_set_drvdata(vdev, mxc_isi); INIT_LIST_HEAD(&mxc_isi->isi_cap.out_pending); From 4b44b37dab4d990a210aa64257c6eaa3876e45c1 Mon Sep 17 00:00:00 2001 From: "Guoniu.zhou" Date: Thu, 17 Oct 2019 15:01:53 +0800 Subject: [PATCH 5/9] media: staging: imx: add isi core driver support for imx8qm/qxp Because ISI can be used to capture data from camera or memory. Split ISI driver to two parts. One for ISI core and hardware, the other is for ISI capture function. Core driver will create virtual device for them and be shared by capture and memory to memory driver. Signed-off-by: Guoniu.zhou --- drivers/staging/media/imx/Kconfig | 5 +- drivers/staging/media/imx/Makefile | 4 +- drivers/staging/media/imx/imx8-isi-core.c | 69 +++++++-- drivers/staging/media/imx/imx8-isi-core.h | 172 +++++++++++++++------- drivers/staging/media/imx/imx8-isi-hw.c | 170 +++++++++++++++------ drivers/staging/media/imx/imx8-isi-hw.h | 57 +++++-- 6 files changed, 346 insertions(+), 131 deletions(-) diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig index 76f61762a005..faffed394abc 100644 --- a/drivers/staging/media/imx/Kconfig +++ b/drivers/staging/media/imx/Kconfig @@ -47,10 +47,9 @@ config VIDEO_IMX_CAPTURE if VIDEO_IMX_CAPTURE menu "i.MX8QXP/QM Camera ISI/MIPI Features support" -config IMX8_CAPTURE_DRIVER - tristate "IMX8 Camera Controller" +config IMX8_ISI_CORE + bool "IMX8 Image Sensor Interface Core Driver" depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API - select VIDEOBUF2_DMA_CONTIG default y endmenu diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile index 5d5494c946b7..031292b19089 100644 --- a/drivers/staging/media/imx/Makefile +++ b/drivers/staging/media/imx/Makefile @@ -3,7 +3,7 @@ imx6-media-objs := imx-media-dev.o imx-media-internal-sd.o \ imx-ic-common.o imx-ic-prp.o imx-ic-prpencvf.o imx-media-vdic.o \ imx-media-csc-scaler.o -imx8-capture-objs := imx8-isi-core.o imx8-isi-cap.o imx8-isi-hw.o +imx8-capture-objs := imx8-isi-core.o imx8-isi-hw.o imx-media-common-objs := imx-media-capture.o imx-media-dev-common.o \ imx-media-of.o imx-media-utils.o @@ -18,4 +18,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o -obj-$(CONFIG_IMX8_CAPTURE_DRIVER) += imx8-capture.o +obj-$(CONFIG_IMX8_ISI_CORE) += imx8-capture.o diff --git a/drivers/staging/media/imx/imx8-isi-core.c b/drivers/staging/media/imx/imx8-isi-core.c index eb05caeb5767..28ce6155ad9a 100644 --- a/drivers/staging/media/imx/imx8-isi-core.c +++ b/drivers/staging/media/imx/imx8-isi-core.c @@ -6,6 +6,46 @@ #include "imx8-isi-hw.h" +struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev) +{ + struct mxc_isi_dev *mxc_isi; + + if (!pdev || !pdev->dev.parent) + return NULL; + + device_lock(pdev->dev.parent); + mxc_isi = (struct mxc_isi_dev *)dev_get_drvdata(pdev->dev.parent); + if (!mxc_isi) { + dev_err(&pdev->dev, "Cann't get host data\n"); + device_unlock(pdev->dev.parent); + return NULL; + } + device_unlock(pdev->dev.parent); + + return mxc_isi; +} + +struct device *mxc_isi_dev_get_parent(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *parent; + struct platform_device *parent_pdev; + + if (!pdev) + return NULL; + + /* Get parent for isi capture device */ + parent = of_get_parent(dev->of_node); + parent_pdev = of_find_device_by_node(parent); + if (!parent_pdev) { + of_node_put(parent); + return NULL; + } + of_node_put(parent); + + return &parent_pdev->dev; +} + static irqreturn_t mxc_isi_irq_handler(int irq, void *priv) { struct mxc_isi_dev *mxc_isi = priv; @@ -17,8 +57,12 @@ static irqreturn_t mxc_isi_irq_handler(int irq, void *priv) status = mxc_isi_get_irq_status(mxc_isi); mxc_isi_clean_irq_status(mxc_isi, status); - if (status & CHNL_STS_FRM_STRD_MASK) - mxc_isi_cap_frame_write_done(mxc_isi); + if (status & CHNL_STS_FRM_STRD_MASK) { + if (mxc_isi->m2m_enabled) + mxc_isi_m2m_frame_write_done(mxc_isi); + else + mxc_isi_cap_frame_write_done(mxc_isi); + } if (status & (CHNL_STS_AXI_WR_ERR_Y_MASK | CHNL_STS_AXI_WR_ERR_U_MASK | @@ -51,7 +95,6 @@ static int mxc_isi_parse_dt(struct mxc_isi_dev *mxc_isi) int ret = 0; mxc_isi->id = of_alias_get_id(node, "isi"); - mxc_isi->parallel_csi = of_property_read_bool(node, "parallel_csi"); mxc_isi->chain_buf = of_property_read_bool(node, "fsl,chain_buf"); ret = of_property_read_u32_array(node, "interface", mxc_isi->interface, 3); @@ -88,10 +131,9 @@ static int mxc_isi_probe(struct platform_device *pdev) return -EINVAL; } - init_waitqueue_head(&mxc_isi->irq_queue); spin_lock_init(&mxc_isi->slock); mutex_init(&mxc_isi->lock); - atomic_set(&mxc_isi->open_count, 0); + atomic_set(&mxc_isi->usage_count, 0); mxc_isi->clk = devm_clk_get(dev, NULL); if (IS_ERR(mxc_isi->clk)) { @@ -126,30 +168,25 @@ static int mxc_isi_probe(struct platform_device *pdev) return -EINVAL; } - ret = mxc_isi_initialize_capture_subdev(mxc_isi); - if (ret < 0) { - dev_err(dev, "failed to init cap subdev (%d)\n", ret); - return -EINVAL; - } - - platform_set_drvdata(pdev, mxc_isi); - mxc_isi_channel_set_chain_buf(mxc_isi); + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret < 0) + dev_warn(dev, "Populate child platform device fail\n"); + clk_disable_unprepare(mxc_isi->clk); + + platform_set_drvdata(pdev, mxc_isi); pm_runtime_enable(dev); dev_info(dev, "mxc_isi.%d registered successfully\n", mxc_isi->id); - return 0; } static int mxc_isi_remove(struct platform_device *pdev) { - struct mxc_isi_dev *mxc_isi = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; - mxc_isi_unregister_capture_subdev(mxc_isi); pm_runtime_disable(dev); return 0; diff --git a/drivers/staging/media/imx/imx8-isi-core.h b/drivers/staging/media/imx/imx8-isi-core.h index 1bc7a7263777..24fbfbd9c3b9 100644 --- a/drivers/staging/media/imx/imx8-isi-core.h +++ b/drivers/staging/media/imx/imx8-isi-core.h @@ -37,6 +37,8 @@ #include "imx8-common.h" #define MXC_ISI_DRIVER_NAME "mxc-isi" +#define MXC_ISI_CAPTURE "mxc-isi-cap" +#define MXC_ISI_M2M "mxc-isi-m2m" #define MXC_MAX_PLANES 3 enum mxc_isi_out_fmt { @@ -95,6 +97,25 @@ enum mxc_isi_in_fmt { MXC_ISI_IN_FMT_BGR8P = 0x0, }; +enum mxc_isi_m2m_in_fmt { + MXC_ISI_M2M_IN_FMT_BGR8P = 0x0, + MXC_ISI_M2M_IN_FMT_RGB8P, + MXC_ISI_M2M_IN_FMT_XRGB8, + MXC_ISI_M2M_IN_FMT_RGBX8, + MXC_ISI_M2M_IN_FMT_XBGR8, + MXC_ISI_M2M_IN_FMT_RGB565, + MXC_ISI_M2M_IN_FMT_A2BGR10, + MXC_ISI_M2M_IN_FMT_A2RGB10, + MXC_ISI_M2M_IN_FMT_YUV444_1P8P, + MXC_ISI_M2M_IN_FMT_YUV444_1P10, + MXC_ISI_M2M_IN_FMT_YUV444_1P10P, + MXC_ISI_M2M_IN_FMT_YUV444_1P12, + MXC_ISI_M2M_IN_FMT_YUV444_1P8, + MXC_ISI_M2M_IN_FMT_YUV422_1P8P, + MXC_ISI_M2M_IN_FMT_YUV422_1P10, + MXC_ISI_M2M_IN_FMT_YUV422_1P10P, +}; + struct mxc_isi_fmt { char *name; u32 mbus_code; @@ -113,6 +134,8 @@ struct mxc_isi_ctrls { struct v4l2_ctrl *hflip; struct v4l2_ctrl *vflip; struct v4l2_ctrl *alpha; + struct v4l2_ctrl *num_cap_buf; + struct v4l2_ctrl *num_out_buf; bool ready; }; @@ -168,77 +191,111 @@ struct mxc_isi_buffer { bool discard; }; +struct mxc_isi_m2m_dev { + struct platform_device *pdev; + + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct v4l2_fh fh; + struct v4l2_pix_format_mplane pix; + + struct list_head out_active; + struct mxc_isi_ctrls ctrls; + + struct mxc_isi_frame src_f; + struct mxc_isi_frame dst_f; + + struct mutex lock; + spinlock_t slock; + + unsigned int aborting; + unsigned int frame_count; + + u32 req_cap_buf_num; + u32 req_out_buf_num; + + u8 id; +}; + struct mxc_isi_ctx { - struct mxc_isi_dev *isi_dev; + struct mxc_isi_m2m_dev *isi_m2m; struct v4l2_fh fh; }; struct mxc_isi_cap_dev { - struct v4l2_subdev sd; - struct video_device vdev; - struct v4l2_fh fh; - struct media_pad cap_pad; - struct media_pad sd_pads[MXC_ISI_SD_PADS_NUM]; - struct vb2_queue vb2_q; - struct list_head out_pending; - struct list_head out_active; - struct list_head out_discard; + struct v4l2_subdev sd; + struct video_device vdev; + struct v4l2_fh fh; + struct vb2_queue vb2_q; + struct v4l2_pix_format_mplane pix; - struct mxc_isi_frame src_f; - struct mxc_isi_frame dst_f; - u32 frame_count; + struct mxc_isi_dev *mxc_isi; + struct platform_device *pdev; + struct mxc_isi_ctrls ctrls; + struct mxc_isi_buffer buf_discard[2]; - u32 buf_index; + struct media_pad cap_pad; + struct media_pad sd_pads[MXC_ISI_SD_PADS_NUM]; + + struct list_head out_pending; + struct list_head out_active; + struct list_head out_discard; + + struct mxc_isi_frame src_f; + struct mxc_isi_frame dst_f; + + u32 frame_count; + u32 id; + + struct mutex lock; + spinlock_t slock; + + /* dirty buffer */ + size_t discard_size[MXC_MAX_PLANES]; + void *discard_buffer[MXC_MAX_PLANES]; + dma_addr_t discard_buffer_dma[MXC_MAX_PLANES]; }; struct mxc_isi_dev { - struct mxc_isi_cap_dev isi_cap; - struct platform_device *pdev; - struct v4l2_device *v4l2_dev; - struct clk *clk; + /* Pointer to isi capture child device driver data */ + struct mxc_isi_cap_dev *isi_cap; - struct mutex lock; - wait_queue_head_t irq_queue; - spinlock_t slock; + /* Pointer to isi m2m child device driver data */ + struct mxc_isi_m2m_dev *isi_m2m; - int id; - void __iomem *regs; - unsigned long state; + struct platform_device *pdev; + struct clk *clk; - u32 interface[MAX_PORTS]; - u32 flags; - u32 skip_m2m; - u8 chain_buf; + struct mutex lock; + spinlock_t slock; - atomic_t open_count; + void __iomem *regs; + + u8 chain_buf; + u8 alpha; + bool m2m_enabled; + + /* manage share ISI channel resource */ + atomic_t usage_count; /* scale factor */ - u32 xfactor; - u32 yfactor; - u32 pre_dec_x; - u32 pre_dec_y; + u32 xfactor; + u32 yfactor; + u32 pre_dec_x; + u32 pre_dec_y; - unsigned int hflip:1; - unsigned int vflip:1; + u32 interface[MAX_PORTS]; + int id; - unsigned int cscen:1; - unsigned int scale:1; - unsigned int alphaen:1; - unsigned int crop:1; - unsigned int deinterlace:1; - unsigned int parallel_csi:1; - unsigned int is_m2m:1; - unsigned int is_streaming:1; - - struct mxc_isi_ctrls ctrls; - struct mxc_isi_roi_alpha alpha_roi[5]; - struct v4l2_pix_format_mplane pix; - u8 alpha; - - size_t discard_size[MXC_MAX_PLANES]; - void *discard_buffer[MXC_MAX_PLANES]; - dma_addr_t discard_buffer_dma[MXC_MAX_PLANES]; - struct mxc_isi_buffer buf_discard[2]; + unsigned int hflip:1; + unsigned int vflip:1; + unsigned int cscen:1; + unsigned int scale:1; + unsigned int alphaen:1; + unsigned int crop:1; + unsigned int deinterlace:1; + unsigned int is_streaming:1; }; static inline void set_frame_bounds(struct mxc_isi_frame *f, @@ -270,7 +327,12 @@ static inline void set_frame_crop(struct mxc_isi_frame *f, f->c_height = height; } -int mxc_isi_initialize_capture_subdev(struct mxc_isi_dev *mxc_isi); -void mxc_isi_unregister_capture_subdev(struct mxc_isi_dev *mxc_isi); +#if defined(CONFIG_IMX8_ISI_CORE) +struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev); +struct device *mxc_isi_dev_get_parent(struct platform_device *pdev); +#else +static inline struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev) {} +static inline struct struct device *mxc_isi_dev_get_parent(struct platform_device *pdev) {} +#endif #endif /* __MXC_ISI_CORE_H__ */ diff --git a/drivers/staging/media/imx/imx8-isi-hw.c b/drivers/staging/media/imx/imx8-isi-hw.c index 462c49855174..e39cb47ed81c 100644 --- a/drivers/staging/media/imx/imx8-isi-hw.c +++ b/drivers/staging/media/imx/imx8-isi-hw.c @@ -125,14 +125,11 @@ static bool is_yuv(u32 pix_fmt) return false; } -static void chain_buf(struct mxc_isi_dev *mxc_isi) +static void chain_buf(struct mxc_isi_dev *mxc_isi, struct mxc_isi_frame *frm) { - struct mxc_isi_frame *src_f; u32 val; - src_f = &mxc_isi->isi_cap.src_f; - - if (src_f->o_width > 2048) { + if (frm->o_width > 2048) { val = readl(mxc_isi->regs + CHNL_CTRL); val &= ~CHNL_CTRL_CHAIN_BUF_MASK; val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET); @@ -148,18 +145,21 @@ void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi, struct mxc_isi_buffer *buf) { struct vb2_buffer *vb2_buf = &buf->v4l2_buf.vb2_buf; - struct frame_addr *paddr = &buf->paddr; - struct v4l2_pix_format_mplane *pix = &mxc_isi->pix; u32 framecount = buf->v4l2_buf.sequence; + struct frame_addr *paddr = &buf->paddr; + struct mxc_isi_cap_dev *isi_cap; + struct v4l2_pix_format_mplane *pix; int val = 0; if (buf->discard) { - paddr->y = mxc_isi->discard_buffer_dma[0]; + isi_cap = mxc_isi->isi_cap; + pix = &isi_cap->pix; + paddr->y = isi_cap->discard_buffer_dma[0]; if (pix->num_planes == 2) - paddr->cb = mxc_isi->discard_buffer_dma[1]; + paddr->cb = isi_cap->discard_buffer_dma[1]; if (pix->num_planes == 3) { - paddr->cb = mxc_isi->discard_buffer_dma[1]; - paddr->cr = mxc_isi->discard_buffer_dma[2]; + paddr->cb = isi_cap->discard_buffer_dma[1]; + paddr->cr = isi_cap->discard_buffer_dma[2]; } } else { paddr->y = vb2_dma_contig_plane_dma_addr(vb2_buf, 0); @@ -188,6 +188,17 @@ void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi, writel(val, mxc_isi->regs + CHNL_OUT_BUF_CTRL); } +void mxc_isi_channel_set_m2m_src_addr(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_buffer *buf) +{ + struct vb2_buffer *vb2_buf = &buf->v4l2_buf.vb2_buf; + struct frame_addr *paddr = &buf->paddr; + + /* Only support one plane */ + paddr->y = vb2_dma_contig_plane_dma_addr(vb2_buf, 0); + writel(paddr->y, mxc_isi->regs + CHNL_IN_BUF_ADDR); +} + void mxc_isi_channel_sw_reset(struct mxc_isi_dev *mxc_isi) { u32 val; @@ -260,10 +271,12 @@ void mxc_isi_channel_set_flip(struct mxc_isi_dev *mxc_isi) writel(val, mxc_isi->regs + CHNL_IMG_CTRL); } -void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi) +void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *src_f, + struct mxc_isi_frame *dst_f) { - struct mxc_isi_fmt *dst_fmt = mxc_isi->isi_cap.dst_f.fmt; - struct mxc_isi_fmt *src_fmt = mxc_isi->isi_cap.src_f.fmt; + struct mxc_isi_fmt *src_fmt = src_f->fmt; + struct mxc_isi_fmt *dst_fmt = dst_f->fmt; u32 val, csc = 0; val = readl(mxc_isi->regs + CHNL_IMG_CTRL); @@ -326,8 +339,10 @@ void mxc_isi_channel_set_alpha(struct mxc_isi_dev *mxc_isi) val = readl(mxc_isi->regs + CHNL_IMG_CTRL); val &= ~(CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK | CHNL_IMG_CTRL_GBL_ALPHA_EN_MASK); - val |= ((mxc_isi->alpha << CHNL_IMG_CTRL_GBL_ALPHA_VAL_OFFSET) | - (CHNL_IMG_CTRL_GBL_ALPHA_EN_ENABLE << CHNL_IMG_CTRL_GBL_ALPHA_EN_OFFSET)); + + if (mxc_isi->alphaen) + val |= ((mxc_isi->alpha << CHNL_IMG_CTRL_GBL_ALPHA_VAL_OFFSET) | + (CHNL_IMG_CTRL_GBL_ALPHA_EN_ENABLE << CHNL_IMG_CTRL_GBL_ALPHA_EN_OFFSET)); writel(val, mxc_isi->regs + CHNL_IMG_CTRL); } @@ -337,7 +352,6 @@ void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi) u32 val; if (mxc_isi->chain_buf) { - pr_info("%s\n", __func__); val = readl(mxc_isi->regs + CHNL_CTRL); val &= ~CHNL_CTRL_CHAIN_BUF_MASK; val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET); @@ -373,7 +387,7 @@ void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi) void mxc_isi_channel_set_crop(struct mxc_isi_dev *mxc_isi) { - struct mxc_isi_frame *src_f = &mxc_isi->isi_cap.src_f; + struct mxc_isi_frame *src_f = &mxc_isi->isi_cap->src_f; struct v4l2_rect crop; u32 val, val0, val1, temp; @@ -424,10 +438,10 @@ static void mxc_isi_channel_clear_scaling(struct mxc_isi_dev *mxc_isi) writel(val0, mxc_isi->regs + CHNL_IMG_CTRL); } -void mxc_isi_channel_set_scaling(struct mxc_isi_dev *mxc_isi) +void mxc_isi_channel_set_scaling(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *src_f, + struct mxc_isi_frame *dst_f) { - struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; - struct mxc_isi_frame *src_f = &mxc_isi->isi_cap.src_f; u32 decx, decy; u32 xscale, yscale; u32 xdec = 0, ydec = 0; @@ -533,14 +547,14 @@ void mxc_isi_channel_deinit(struct mxc_isi_dev *mxc_isi) writel(val, mxc_isi->regs + CHNL_CTRL); } -void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi) +void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *src_f, + struct mxc_isi_frame *dst_f) { - struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; - struct mxc_isi_frame *src_f = &mxc_isi->isi_cap.src_f; u32 val; /* images having higher than 2048 horizontal resolution */ - chain_buf(mxc_isi); + chain_buf(mxc_isi, src_f); /* config output frame size and format */ val = src_f->o_width | (src_f->o_height << CHNL_IMG_CFG_HEIGHT_OFFSET); @@ -550,9 +564,9 @@ void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi) writel(val, mxc_isi->regs + CHNL_SCL_IMG_CFG); /* check csc and scaling */ - mxc_isi_channel_set_csc(mxc_isi); + mxc_isi_channel_set_csc(mxc_isi, src_f, dst_f); - mxc_isi_channel_set_scaling(mxc_isi); + mxc_isi_channel_set_scaling(mxc_isi, src_f, dst_f); /* select the source input / src type / virtual channel for mipi*/ mxc_isi_channel_source_config(mxc_isi); @@ -564,8 +578,7 @@ void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi) /* TODO */ mxc_isi_channel_set_flip(mxc_isi); - if (mxc_isi->alphaen) - mxc_isi_channel_set_alpha(mxc_isi); + mxc_isi_channel_set_alpha(mxc_isi); val = readl(mxc_isi->regs + CHNL_CTRL); val &= ~CHNL_CTRL_CHNL_BYPASS_MASK; @@ -585,19 +598,33 @@ void mxc_isi_clean_registers(struct mxc_isi_dev *mxc_isi) mxc_isi_clean_irq_status(mxc_isi, status); } -void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi) +void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi, bool m2m_enabled) { u32 val; val = readl(mxc_isi->regs + CHNL_CTRL); - val |= (CHNL_CTRL_CHNL_EN_ENABLE << CHNL_CTRL_CHNL_EN_OFFSET); val |= 0xff << CHNL_CTRL_BLANK_PXL_OFFSET; + + if (m2m_enabled) { + val &= ~(CHNL_CTRL_SRC_TYPE_MASK | CHNL_CTRL_SRC_INPUT_MASK); + val |= (CHNL_CTRL_SRC_INPUT_MEMORY << CHNL_CTRL_SRC_INPUT_OFFSET | + CHNL_CTRL_SRC_TYPE_MEMORY << CHNL_CTRL_SRC_TYPE_OFFSET); + } + + val &= ~CHNL_CTRL_CHNL_EN_MASK; + val |= CHNL_CTRL_CHNL_EN_ENABLE << CHNL_CTRL_CHNL_EN_OFFSET; writel(val, mxc_isi->regs + CHNL_CTRL); mxc_isi_clean_registers(mxc_isi); mxc_isi_enable_irq(mxc_isi); - msleep(300); + + if (m2m_enabled) { + mxc_isi_m2m_start_read(mxc_isi); + return; + } + dump_isi_regs(mxc_isi); + msleep(300); } void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi) @@ -618,19 +645,19 @@ void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi) u32 val; val = CHNL_IER_FRM_RCVD_EN_MASK | - CHNL_IER_OFLW_Y_BUF_EN_MASK | - CHNL_IER_AXI_WR_ERR_U_EN_MASK | - CHNL_IER_AXI_WR_ERR_V_EN_MASK | - CHNL_IER_AXI_WR_ERR_Y_EN_MASK | - CHNL_IER_OFLW_PANIC_V_BUF_EN_MASK | - CHNL_IER_EXCS_OFLW_V_BUF_EN_MASK | - CHNL_IER_OFLW_V_BUF_EN_MASK | - CHNL_IER_OFLW_PANIC_U_BUF_EN_MASK | - CHNL_IER_EXCS_OFLW_U_BUF_EN_MASK | - CHNL_IER_OFLW_U_BUF_EN_MASK | - CHNL_IER_OFLW_PANIC_Y_BUF_EN_MASK | - CHNL_IER_EXCS_OFLW_Y_BUF_EN_MASK | - CHNL_IER_OFLW_Y_BUF_EN_MASK; + CHNL_IER_OFLW_Y_BUF_EN_MASK | + CHNL_IER_AXI_WR_ERR_U_EN_MASK | + CHNL_IER_AXI_WR_ERR_V_EN_MASK | + CHNL_IER_AXI_WR_ERR_Y_EN_MASK | + CHNL_IER_OFLW_PANIC_V_BUF_EN_MASK | + CHNL_IER_EXCS_OFLW_V_BUF_EN_MASK | + CHNL_IER_OFLW_V_BUF_EN_MASK | + CHNL_IER_OFLW_PANIC_U_BUF_EN_MASK | + CHNL_IER_EXCS_OFLW_U_BUF_EN_MASK | + CHNL_IER_OFLW_U_BUF_EN_MASK | + CHNL_IER_OFLW_PANIC_Y_BUF_EN_MASK | + CHNL_IER_EXCS_OFLW_Y_BUF_EN_MASK | + CHNL_IER_OFLW_Y_BUF_EN_MASK; writel(val, mxc_isi->regs + CHNL_IER); } @@ -649,3 +676,56 @@ void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val) { writel(val, mxc_isi->regs + CHNL_STS); } + +void mxc_isi_m2m_config_src(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *src_f) +{ + u32 val; + + /* source format */ + val = readl(mxc_isi->regs + CHNL_MEM_RD_CTRL); + val &= ~CHNL_MEM_RD_CTRL_IMG_TYPE_MASK; + val |= src_f->fmt->color << CHNL_MEM_RD_CTRL_IMG_TYPE_OFFSET; + writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL); + + /* source image width and height */ + val = (src_f->width << CHNL_IMG_CFG_WIDTH_OFFSET | + src_f->height << CHNL_IMG_CFG_HEIGHT_OFFSET); + writel(val, mxc_isi->regs + CHNL_IMG_CFG); + + /* source pitch */ + val = src_f->bytesperline[0] << CHNL_IN_BUF_PITCH_LINE_PITCH_OFFSET; + writel(val, mxc_isi->regs + CHNL_IN_BUF_PITCH); +} + +void mxc_isi_m2m_config_dst(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *dst_f) +{ + /*struct mxc_isi_frame *dst_f = &mxc_isi->isi_m2m->dst_f;*/ + u32 val; + + /* out format */ + val = readl(mxc_isi->regs + CHNL_IMG_CTRL); + val &= ~CHNL_IMG_CTRL_FORMAT_MASK; + val |= dst_f->fmt->color << CHNL_IMG_CTRL_FORMAT_OFFSET; + writel(val, mxc_isi->regs + CHNL_IMG_CTRL); + + /* out pitch */ + val = readl(mxc_isi->regs + CHNL_OUT_BUF_PITCH); + val &= ~CHNL_IN_BUF_PITCH_LINE_PITCH_MASK; + val |= dst_f->bytesperline[0] << CHNL_OUT_BUF_PITCH_LINE_PITCH_OFFSET; + writel(val, mxc_isi->regs + CHNL_OUT_BUF_PITCH); +} + +void mxc_isi_m2m_start_read(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + val = readl(mxc_isi->regs + CHNL_MEM_RD_CTRL); + val &= ~ CHNL_MEM_RD_CTRL_READ_MEM_MASK; + writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL); + udelay(300); + + val |= CHNL_MEM_RD_CTRL_READ_MEM_ENABLE << CHNL_MEM_RD_CTRL_READ_MEM_OFFSET; + writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL); +} diff --git a/drivers/staging/media/imx/imx8-isi-hw.h b/drivers/staging/media/imx/imx8-isi-hw.h index e15a9abb743c..eefb18d81cea 100644 --- a/drivers/staging/media/imx/imx8-isi-hw.h +++ b/drivers/staging/media/imx/imx8-isi-hw.h @@ -423,6 +423,14 @@ #define CHNL_IN_BUF_PITCH_LINE_PITCH_OFFSET 0 #define CHNL_IN_BUF_PITCH_LINE_PITCH_MASK 0xFFFF +/* Channel Memory Read Control */ +#define CHNL_MEM_RD_CTRL 0x88 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_OFFSET 28 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_MASK 0xF0000000 +#define CHNL_MEM_RD_CTRL_READ_MEM_OFFSET 0 +#define CHNL_MEM_RD_CTRL_READ_MEM_MASK 1 +#define CHNL_MEM_RD_CTRL_READ_MEM_ENABLE 1 + /* Channel RGB or Luma (Y) Output Buffer 2 Address */ #define CHNL_OUT_BUF2_ADDR_Y 0x8C @@ -446,32 +454,61 @@ enum isi_csi_coeff { void mxc_isi_channel_init(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_deinit(struct mxc_isi_dev *mxc_isi); -void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi); -void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi, bool m2m_enabled); void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi); -void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi, - struct mxc_isi_buffer *buf); +#if defined(CONFIG_IMX8_ISI_CAPTURE) void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi); +#else +static inline void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi) {} +#endif void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_sw_reset(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_hw_reset(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_source_config(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_set_flip(struct mxc_isi_dev *mxc_isi); -void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi); -void mxc_isi_channel_set_alpha_roi0(struct mxc_isi_dev *mxc_isi, - struct v4l2_rect *rect); void mxc_isi_channel_set_alpha(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_set_crop(struct mxc_isi_dev *mxc_isi); void mxc_isi_channel_set_memory_image(struct mxc_isi_dev *mxc_isi); -void mxc_isi_channel_set_scaler(struct mxc_isi_dev *mxc_isi); +void mxc_isi_channel_set_scaling(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *src_f, + struct mxc_isi_frame *dst_f); + +void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_buffer *buf); + +void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *src_f, + struct mxc_isi_frame *dst_f); + +void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *src_f, + struct mxc_isi_frame *dst_f); + +void mxc_isi_channel_set_alpha_roi0(struct mxc_isi_dev *mxc_isi, + struct v4l2_rect *rect); +void mxc_isi_channel_set_m2m_src_addr(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_buffer *buf); + +void mxc_isi_m2m_config_src(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *src_f); +void mxc_isi_m2m_config_dst(struct mxc_isi_dev *mxc_isi, + struct mxc_isi_frame *dst_f); + +void mxc_isi_m2m_start_read(struct mxc_isi_dev *mxc_isi); +#if defined(CONFIG_IMX8_ISI_M2M) +void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi); +#else +static inline void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi) {} +#endif void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val); -u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi); void mxc_isi_clean_registers(struct mxc_isi_dev *mxc_isi); void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi); void mxc_isi_disable_irq(struct mxc_isi_dev *mxc_isi); - void dump_isi_regs(struct mxc_isi_dev *mxc_isi); + +u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi); + #endif /* __MXC_ISI_HW_H__ */ From 69a93d7039336a2321f47bfa9996734316a5a7e5 Mon Sep 17 00:00:00 2001 From: "Guoniu.zhou" Date: Thu, 17 Oct 2019 15:09:16 +0800 Subject: [PATCH 6/9] media: staging: imx: add isi capture driver support for imx8qm/qxp ISI is an image sensor interface which used to process image data from camera sensor or memory. Add ISI capture function driver for it. Signed-off-by: Guoniu.zhou --- drivers/staging/media/imx/Kconfig | 6 + drivers/staging/media/imx/Makefile | 1 + drivers/staging/media/imx/imx8-isi-cap.c | 635 ++++++++++++----------- 3 files changed, 348 insertions(+), 294 deletions(-) diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig index faffed394abc..7ea840bcd711 100644 --- a/drivers/staging/media/imx/Kconfig +++ b/drivers/staging/media/imx/Kconfig @@ -52,5 +52,11 @@ config IMX8_ISI_CORE depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API default y +config IMX8_ISI_CAPTURE + bool "IMX8 Image Sensor Interface Capture Device Driver" + depends on IMX8_ISI_CORE + select VIDEOBUF2_DMA_CONTIG + default y + endmenu endif #VIDEO_IMX_CAPTURE diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile index 031292b19089..31f981f54416 100644 --- a/drivers/staging/media/imx/Makefile +++ b/drivers/staging/media/imx/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o obj-$(CONFIG_IMX8_ISI_CORE) += imx8-capture.o +obj-$(CONFIG_IMX8_ISI_CAPTURE) += imx8-isi-cap.o diff --git a/drivers/staging/media/imx/imx8-isi-cap.c b/drivers/staging/media/imx/imx8-isi-cap.c index 495a5b7ff2cf..cbf82bae0eaf 100644 --- a/drivers/staging/media/imx/imx8-isi-cap.c +++ b/drivers/staging/media/imx/imx8-isi-cap.c @@ -30,6 +30,8 @@ #include "imx8-isi-hw.h" #include "imx8-common.h" +#define sd_to_cap_dev(ptr) container_of(ptr, struct mxc_isi_cap_dev, sd) + struct mxc_isi_fmt mxc_isi_out_formats[] = { { .name = "RGB565", @@ -179,10 +181,10 @@ static inline struct mxc_isi_buffer *to_isi_buffer(struct vb2_v4l2_buffer *v4l2_ /* * mxc_isi_pipeline_enable() - Enable streaming on a pipeline */ -static int mxc_isi_pipeline_enable(struct mxc_isi_dev *mxc_isi, bool enable) +static int mxc_isi_pipeline_enable(struct mxc_isi_cap_dev *isi_cap, bool enable) { - struct device *dev = &mxc_isi->pdev->dev; - struct media_entity *entity = &mxc_isi->isi_cap.vdev.entity; + struct device *dev = &isi_cap->pdev->dev; + struct media_entity *entity = &isi_cap->vdev.entity; struct media_device *mdev = entity->graph_obj.mdev; struct media_graph graph; struct v4l2_subdev *subdev; @@ -230,7 +232,6 @@ static int mxc_isi_update_buf_paddr(struct mxc_isi_buffer *buf, int memplanes) { struct frame_addr *paddr = &buf->paddr; struct vb2_buffer *vb2 = &buf->v4l2_buf.vb2_buf; - int ret = 0; paddr->cb = 0; paddr->cr = 0; @@ -238,8 +239,10 @@ static int mxc_isi_update_buf_paddr(struct mxc_isi_buffer *buf, int memplanes) switch (memplanes) { case 3: paddr->cr = vb2_dma_contig_plane_dma_addr(vb2, 2); + /* fall through */ case 2: paddr->cb = vb2_dma_contig_plane_dma_addr(vb2, 1); + /* fall through */ case 1: paddr->y = vb2_dma_contig_plane_dma_addr(vb2, 0); break; @@ -247,26 +250,25 @@ static int mxc_isi_update_buf_paddr(struct mxc_isi_buffer *buf, int memplanes) return -EINVAL; } - return ret; + return 0; } void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi) { - struct device *dev = &mxc_isi->pdev->dev; + struct mxc_isi_cap_dev *isi_cap = mxc_isi->isi_cap; + struct device *dev = &isi_cap->pdev->dev; struct mxc_isi_buffer *buf; struct vb2_buffer *vb2; - if (list_empty(&mxc_isi->isi_cap.out_active)) { + if (list_empty(&isi_cap->out_active)) { dev_warn(dev, "trying to access empty active list\n"); return; } - buf = list_first_entry(&mxc_isi->isi_cap.out_active, - struct mxc_isi_buffer, list); + buf = list_first_entry(&isi_cap->out_active, struct mxc_isi_buffer, list); if (buf->discard) { - list_move_tail(mxc_isi->isi_cap.out_active.next, - &mxc_isi->isi_cap.out_discard); + list_move_tail(isi_cap->out_active.next, &isi_cap->out_discard); } else { vb2 = &buf->v4l2_buf.vb2_buf; list_del_init(&buf->list); @@ -274,32 +276,29 @@ void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi) vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE); } - mxc_isi->isi_cap.frame_count++; + isi_cap->frame_count++; - if (list_empty(&mxc_isi->isi_cap.out_pending)) { - if (list_empty(&mxc_isi->isi_cap.out_discard)) { + if (list_empty(&isi_cap->out_pending)) { + if (list_empty(&isi_cap->out_discard)) { dev_warn(dev, "trying to access empty discard list\n"); return; } - buf = list_first_entry(&mxc_isi->isi_cap.out_discard, + buf = list_first_entry(&isi_cap->out_discard, struct mxc_isi_buffer, list); - buf->v4l2_buf.sequence = mxc_isi->isi_cap.frame_count; + buf->v4l2_buf.sequence = isi_cap->frame_count; mxc_isi_channel_set_outbuf(mxc_isi, buf); - list_move_tail(mxc_isi->isi_cap.out_discard.next, - &mxc_isi->isi_cap.out_active); + list_move_tail(isi_cap->out_discard.next, &isi_cap->out_active); return; } /* ISI channel output buffer */ - buf = list_first_entry(&mxc_isi->isi_cap.out_pending, - struct mxc_isi_buffer, list); - buf->v4l2_buf.sequence = mxc_isi->isi_cap.frame_count; + buf = list_first_entry(&isi_cap->out_pending, struct mxc_isi_buffer, list); + buf->v4l2_buf.sequence = isi_cap->frame_count; mxc_isi_channel_set_outbuf(mxc_isi, buf); vb2 = &buf->v4l2_buf.vb2_buf; vb2->state = VB2_BUF_STATE_ACTIVE; - list_move_tail(mxc_isi->isi_cap.out_pending.next, - &mxc_isi->isi_cap.out_active); + list_move_tail(isi_cap->out_pending.next, &isi_cap->out_active); } static int cap_vb2_queue_setup(struct vb2_queue *q, @@ -308,8 +307,8 @@ static int cap_vb2_queue_setup(struct vb2_queue *q, unsigned int sizes[], struct device *alloc_devs[]) { - struct mxc_isi_dev *mxc_isi = q->drv_priv; - struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q); + struct mxc_isi_frame *dst_f = &isi_cap->dst_f; struct mxc_isi_fmt *fmt = dst_f->fmt; unsigned long wh; int i; @@ -318,7 +317,7 @@ static int cap_vb2_queue_setup(struct vb2_queue *q, return -EINVAL; for (i = 0; i < fmt->memplanes; i++) - alloc_devs[i] = &mxc_isi->pdev->dev; + alloc_devs[i] = &isi_cap->pdev->dev; wh = dst_f->width * dst_f->height; @@ -331,7 +330,7 @@ static int cap_vb2_queue_setup(struct vb2_queue *q, size >>= 1; sizes[i] = max_t(u32, size, dst_f->sizeimage[i]); } - dev_dbg(&mxc_isi->pdev->dev, "%s, buf_n=%d, size=%d\n", + dev_dbg(&isi_cap->pdev->dev, "%s, buf_n=%d, size=%d\n", __func__, *num_buffers, sizes[0]); return 0; @@ -340,20 +339,20 @@ static int cap_vb2_queue_setup(struct vb2_queue *q, static int cap_vb2_buffer_prepare(struct vb2_buffer *vb2) { struct vb2_queue *q = vb2->vb2_queue; - struct mxc_isi_dev *mxc_isi = q->drv_priv; - struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q); + struct mxc_isi_frame *dst_f = &isi_cap->dst_f; int i; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); - if (!mxc_isi->isi_cap.dst_f.fmt) + if (!isi_cap->dst_f.fmt) return -EINVAL; for (i = 0; i < dst_f->fmt->memplanes; i++) { unsigned long size = dst_f->sizeimage[i]; if (vb2_plane_size(vb2, i) < size) { - v4l2_err(&mxc_isi->isi_cap.vdev, + v4l2_err(&isi_cap->vdev, "User buffer too small (%ld < %ld)\n", vb2_plane_size(vb2, i), size); return -EINVAL; @@ -369,109 +368,108 @@ static void cap_vb2_buffer_queue(struct vb2_buffer *vb2) { struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb2); struct mxc_isi_buffer *buf = to_isi_buffer(v4l2_buf); - struct mxc_isi_dev *mxc_isi = vb2_get_drv_priv(vb2->vb2_queue); + struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(vb2->vb2_queue); unsigned long flags; - spin_lock_irqsave(&mxc_isi->slock, flags); + spin_lock_irqsave(&isi_cap->slock, flags); - mxc_isi_update_buf_paddr(buf, mxc_isi->isi_cap.dst_f.fmt->mdataplanes); - list_add_tail(&buf->list, &mxc_isi->isi_cap.out_pending); + mxc_isi_update_buf_paddr(buf, isi_cap->dst_f.fmt->mdataplanes); + list_add_tail(&buf->list, &isi_cap->out_pending); - spin_unlock_irqrestore(&mxc_isi->slock, flags); + spin_unlock_irqrestore(&isi_cap->slock, flags); } static int cap_vb2_start_streaming(struct vb2_queue *q, unsigned int count) { - struct mxc_isi_dev *mxc_isi = q->drv_priv; + struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev); struct mxc_isi_buffer *buf; struct vb2_buffer *vb2; unsigned long flags; int i, j; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); if (count < 2) return -ENOBUFS; + if (!mxc_isi) + return -EINVAL; + /* Create a buffer for discard operation */ - for (i = 0; i < mxc_isi->pix.num_planes; i++) { - mxc_isi->discard_size[i] = mxc_isi->isi_cap.dst_f.sizeimage[i]; - mxc_isi->discard_buffer[i] = - dma_alloc_coherent(&mxc_isi->pdev->dev, - PAGE_ALIGN(mxc_isi->discard_size[i]), - &mxc_isi->discard_buffer_dma[i], + for (i = 0; i < isi_cap->pix.num_planes; i++) { + isi_cap->discard_size[i] = isi_cap->dst_f.sizeimage[i]; + isi_cap->discard_buffer[i] = + dma_alloc_coherent(&isi_cap->pdev->dev, + PAGE_ALIGN(isi_cap->discard_size[i]), + &isi_cap->discard_buffer_dma[i], GFP_DMA | GFP_KERNEL); - if (!mxc_isi->discard_buffer[i]) { + if (!isi_cap->discard_buffer[i]) { for (j = 0; j < i; j++) { - dma_free_coherent(&mxc_isi->pdev->dev, - mxc_isi->discard_size[j], - mxc_isi->discard_buffer[j], - mxc_isi->discard_buffer_dma[j]); - dev_err(&mxc_isi->pdev->dev, + dma_free_coherent(&isi_cap->pdev->dev, + isi_cap->discard_size[j], + isi_cap->discard_buffer[j], + isi_cap->discard_buffer_dma[j]); + dev_err(&isi_cap->pdev->dev, "alloc dma buffer(%d) fail\n", j); } return -ENOMEM; } - dev_dbg(&mxc_isi->pdev->dev, + dev_dbg(&isi_cap->pdev->dev, "%s: num_plane=%d discard_size=%d discard_buffer=%p\n" , __func__, i, - (int)mxc_isi->discard_size[i], - mxc_isi->discard_buffer[i]); + (int)isi_cap->discard_size[i], + isi_cap->discard_buffer[i]); } - spin_lock_irqsave(&mxc_isi->slock, flags); + spin_lock_irqsave(&isi_cap->slock, flags); /* add two list member to out_discard list head */ - mxc_isi->buf_discard[0].discard = true; - list_add_tail(&mxc_isi->buf_discard[0].list, - &mxc_isi->isi_cap.out_discard); + isi_cap->buf_discard[0].discard = true; + list_add_tail(&isi_cap->buf_discard[0].list, &isi_cap->out_discard); - mxc_isi->buf_discard[1].discard = true; - list_add_tail(&mxc_isi->buf_discard[1].list, - &mxc_isi->isi_cap.out_discard); + isi_cap->buf_discard[1].discard = true; + list_add_tail(&isi_cap->buf_discard[1].list, &isi_cap->out_discard); /* ISI channel output buffer 1 */ - buf = list_first_entry(&mxc_isi->isi_cap.out_discard, - struct mxc_isi_buffer, list); + buf = list_first_entry(&isi_cap->out_discard, struct mxc_isi_buffer, list); buf->v4l2_buf.sequence = 0; vb2 = &buf->v4l2_buf.vb2_buf; vb2->state = VB2_BUF_STATE_ACTIVE; mxc_isi_channel_set_outbuf(mxc_isi, buf); - list_move_tail(mxc_isi->isi_cap.out_discard.next, - &mxc_isi->isi_cap.out_active); + list_move_tail(isi_cap->out_discard.next, &isi_cap->out_active); /* ISI channel output buffer 2 */ - buf = list_first_entry(&mxc_isi->isi_cap.out_pending, - struct mxc_isi_buffer, list); + buf = list_first_entry(&isi_cap->out_pending, struct mxc_isi_buffer, list); buf->v4l2_buf.sequence = 1; vb2 = &buf->v4l2_buf.vb2_buf; vb2->state = VB2_BUF_STATE_ACTIVE; mxc_isi_channel_set_outbuf(mxc_isi, buf); - list_move_tail(mxc_isi->isi_cap.out_pending.next, - &mxc_isi->isi_cap.out_active); + list_move_tail(isi_cap->out_pending.next, &isi_cap->out_active); /* Clear frame count */ - mxc_isi->isi_cap.frame_count = 1; - spin_unlock_irqrestore(&mxc_isi->slock, flags); + isi_cap->frame_count = 1; + spin_unlock_irqrestore(&isi_cap->slock, flags); return 0; } static void cap_vb2_stop_streaming(struct vb2_queue *q) { - struct mxc_isi_dev *mxc_isi = q->drv_priv; + struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev); struct mxc_isi_buffer *buf, *tmp; unsigned long flags; int i; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); mxc_isi_channel_disable(mxc_isi); - spin_lock_irqsave(&mxc_isi->slock, flags); + spin_lock_irqsave(&isi_cap->slock, flags); - while (!list_empty(&mxc_isi->isi_cap.out_active)) { - buf = list_entry(mxc_isi->isi_cap.out_active.next, + while (!list_empty(&isi_cap->out_active)) { + buf = list_entry(isi_cap->out_active.next, struct mxc_isi_buffer, list); list_del(&buf->list); if (buf->discard) @@ -480,40 +478,40 @@ static void cap_vb2_stop_streaming(struct vb2_queue *q) vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); } - while (!list_empty(&mxc_isi->isi_cap.out_pending)) { - buf = list_entry(mxc_isi->isi_cap.out_pending.next, + while (!list_empty(&isi_cap->out_pending)) { + buf = list_entry(isi_cap->out_pending.next, struct mxc_isi_buffer, list); list_del(&buf->list); vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); } - while (!list_empty(&mxc_isi->isi_cap.out_discard)) { - buf = list_entry(mxc_isi->isi_cap.out_discard.next, + while (!list_empty(&isi_cap->out_discard)) { + buf = list_entry(isi_cap->out_discard.next, struct mxc_isi_buffer, list); list_del(&buf->list); } - list_for_each_entry_safe(buf, tmp, &mxc_isi->isi_cap.out_active, list) { + list_for_each_entry_safe(buf, tmp, &isi_cap->out_active, list) { list_del(&buf->list); vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); } - list_for_each_entry_safe(buf, tmp, &mxc_isi->isi_cap.out_pending, list) { + list_for_each_entry_safe(buf, tmp, &isi_cap->out_pending, list) { list_del(&buf->list); vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); } - INIT_LIST_HEAD(&mxc_isi->isi_cap.out_active); - INIT_LIST_HEAD(&mxc_isi->isi_cap.out_pending); - INIT_LIST_HEAD(&mxc_isi->isi_cap.out_discard); + INIT_LIST_HEAD(&isi_cap->out_active); + INIT_LIST_HEAD(&isi_cap->out_pending); + INIT_LIST_HEAD(&isi_cap->out_discard); - spin_unlock_irqrestore(&mxc_isi->slock, flags); + spin_unlock_irqrestore(&isi_cap->slock, flags); - for (i = 0; i < mxc_isi->pix.num_planes; i++) - dma_free_coherent(&mxc_isi->pdev->dev, - mxc_isi->discard_size[i], - mxc_isi->discard_buffer[i], - mxc_isi->discard_buffer_dma[i]); + for (i = 0; i < isi_cap->pix.num_planes; i++) + dma_free_coherent(&isi_cap->pdev->dev, + isi_cap->discard_size[i], + isi_cap->discard_buffer[i], + isi_cap->discard_buffer_dma[i]); } static struct vb2_ops mxc_cap_vb2_qops = { @@ -529,17 +527,18 @@ static struct vb2_ops mxc_cap_vb2_qops = { /* * V4L2 controls handling */ -static inline struct mxc_isi_dev *ctrl_to_mxc_isi(struct v4l2_ctrl *ctrl) +static inline struct mxc_isi_cap_dev *ctrl_to_isi_cap(struct v4l2_ctrl *ctrl) { - return container_of(ctrl->handler, struct mxc_isi_dev, ctrls.handler); + return container_of(ctrl->handler, struct mxc_isi_cap_dev, ctrls.handler); } static int mxc_isi_s_ctrl(struct v4l2_ctrl *ctrl) { - struct mxc_isi_dev *mxc_isi = ctrl_to_mxc_isi(ctrl); + struct mxc_isi_cap_dev *isi_cap = ctrl_to_isi_cap(ctrl); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev); unsigned long flags; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) return 0; @@ -567,7 +566,7 @@ static int mxc_isi_s_ctrl(struct v4l2_ctrl *ctrl) break; default: - dev_err(&mxc_isi->pdev->dev, + dev_err(&isi_cap->pdev->dev, "%s: Not support %d CID\n", __func__, ctrl->id); return -EINVAL; } @@ -580,12 +579,12 @@ static const struct v4l2_ctrl_ops mxc_isi_ctrl_ops = { .s_ctrl = mxc_isi_s_ctrl, }; -int mxc_isi_ctrls_create(struct mxc_isi_dev *mxc_isi) +int mxc_isi_ctrls_create(struct mxc_isi_cap_dev *isi_cap) { - struct mxc_isi_ctrls *ctrls = &mxc_isi->ctrls; + struct mxc_isi_ctrls *ctrls = &isi_cap->ctrls; struct v4l2_ctrl_handler *handler = &ctrls->handler; - if (mxc_isi->ctrls.ready) + if (isi_cap->ctrls.ready) return 0; v4l2_ctrl_handler_init(handler, 4); @@ -604,9 +603,9 @@ int mxc_isi_ctrls_create(struct mxc_isi_dev *mxc_isi) return handler->error; } -void mxc_isi_ctrls_delete(struct mxc_isi_dev *mxc_isi) +void mxc_isi_ctrls_delete(struct mxc_isi_cap_dev *isi_cap) { - struct mxc_isi_ctrls *ctrls = &mxc_isi->ctrls; + struct mxc_isi_ctrls *ctrls = &isi_cap->ctrls; if (ctrls->ready) { v4l2_ctrl_handler_free(&ctrls->handler); @@ -615,9 +614,9 @@ void mxc_isi_ctrls_delete(struct mxc_isi_dev *mxc_isi) } } -static struct media_pad *mxc_isi_get_remote_source_pad(struct mxc_isi_dev *mxc_isi) +static struct media_pad +*mxc_isi_get_remote_source_pad(struct mxc_isi_cap_dev *isi_cap) { - struct mxc_isi_cap_dev *isi_cap = &mxc_isi->isi_cap; struct v4l2_subdev *subdev = &isi_cap->sd; struct media_pad *sink_pad, *source_pad; int i; @@ -643,16 +642,16 @@ static struct media_pad *mxc_isi_get_remote_source_pad(struct mxc_isi_dev *mxc_i return NULL; } -static struct v4l2_subdev *mxc_get_remote_subdev(struct mxc_isi_dev *mxc_isi, +static struct v4l2_subdev *mxc_get_remote_subdev(struct mxc_isi_cap_dev *isi_cap, const char * const label) { struct media_pad *source_pad; struct v4l2_subdev *sen_sd; /* Get remote source pad */ - source_pad = mxc_isi_get_remote_source_pad(mxc_isi); + source_pad = mxc_isi_get_remote_source_pad(isi_cap); if (!source_pad) { - v4l2_err(&mxc_isi->isi_cap.sd, + v4l2_err(&isi_cap->sd, "%s, No remote pad found!\n", label); return NULL; } @@ -660,8 +659,7 @@ static struct v4l2_subdev *mxc_get_remote_subdev(struct mxc_isi_dev *mxc_isi, /* Get remote source pad subdev */ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); if (!sen_sd) { - v4l2_err(&mxc_isi->isi_cap.sd, - "%s, No remote subdev found!\n", label); + v4l2_err(&isi_cap->sd, "%s, No remote subdev found!\n", label); return NULL; } @@ -670,74 +668,75 @@ static struct v4l2_subdev *mxc_get_remote_subdev(struct mxc_isi_dev *mxc_isi, static int mxc_isi_capture_open(struct file *file) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev); + struct device *dev = &isi_cap->pdev->dev; struct v4l2_subdev *sd; - struct device *dev = &mxc_isi->pdev->dev; int ret = -EBUSY; - dev_dbg(&mxc_isi->pdev->dev, "ISI(%d)\n", mxc_isi->id); - - if (mxc_isi->is_m2m) { - v4l2_err(&mxc_isi->isi_cap.sd, - "ISI channel[%d] is busy\n", mxc_isi->id); - return -EBUSY; + if (mxc_isi->m2m_enabled) { + dev_err(dev, "ISI channel[%d] is busy\n", isi_cap->id); + return ret; } - atomic_inc(&mxc_isi->open_count); - mxc_isi->is_m2m = 0; - sd = mxc_get_remote_subdev(mxc_isi, __func__); + sd = mxc_get_remote_subdev(isi_cap, __func__); if (!sd) - goto fail; + return -ENODEV; - mutex_lock(&mxc_isi->lock); + mutex_lock(&isi_cap->lock); ret = v4l2_fh_open(file); - mutex_unlock(&mxc_isi->lock); + if (ret) { + mutex_unlock(&isi_cap->lock); + return ret; + } + mutex_unlock(&isi_cap->lock); pm_runtime_get_sync(dev); ret = v4l2_subdev_call(sd, core, s_power, 1); if (ret) { - v4l2_err(&mxc_isi->isi_cap.sd, "Call subdev s_power fail!\n"); + dev_err(dev, "Call subdev s_power fail!\n"); pm_runtime_put(dev); - goto fail; + return ret; } - return 0; + /* increase usage count for ISI channel */ + mutex_lock(&mxc_isi->lock); + atomic_inc(&mxc_isi->usage_count); + mxc_isi->m2m_enabled = false; + mutex_unlock(&mxc_isi->lock); -fail: - atomic_dec(&mxc_isi->open_count); - return -EINVAL; + return 0; } static int mxc_isi_capture_release(struct file *file) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev); + struct device *dev = &isi_cap->pdev->dev; struct v4l2_subdev *sd; - struct device *dev = &mxc_isi->pdev->dev; int ret = -1; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); - - sd = mxc_get_remote_subdev(mxc_isi, __func__); + sd = mxc_get_remote_subdev(isi_cap, __func__); if (!sd) goto label; - mutex_lock(&mxc_isi->lock); + mutex_lock(&isi_cap->lock); ret = _vb2_fop_release(file, NULL); if (ret) { - v4l2_err(&mxc_isi->isi_cap.sd, "%s fail\n", __func__); - mutex_unlock(&mxc_isi->lock); + dev_err(dev, "%s fail\n", __func__); + mutex_unlock(&isi_cap->lock); goto label; } - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); - if (atomic_read(&mxc_isi->open_count) > 0 && - atomic_dec_and_test(&mxc_isi->open_count)) + if (atomic_read(&mxc_isi->usage_count) > 0 && + atomic_dec_and_test(&mxc_isi->usage_count)) mxc_isi_channel_deinit(mxc_isi); ret = v4l2_subdev_call(sd, core, s_power, 0); if (ret < 0 && ret != -ENOIOCTLCMD) { - v4l2_err(&mxc_isi->isi_cap.sd, "%s s_power fail\n", __func__); + dev_err(dev, "%s s_power fail\n", __func__); goto label; } @@ -761,12 +760,12 @@ static const struct v4l2_file_operations mxc_isi_capture_fops = { static int mxc_isi_cap_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); - strlcpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver)); - strlcpy(cap->card, MXC_ISI_DRIVER_NAME, sizeof(cap->card)); + strlcpy(cap->driver, MXC_ISI_CAPTURE, sizeof(cap->driver)); + strlcpy(cap->card, MXC_ISI_CAPTURE, sizeof(cap->card)); snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d", - dev_name(&mxc_isi->pdev->dev), mxc_isi->id); + dev_name(&isi_cap->pdev->dev), isi_cap->id); cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE; cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; @@ -777,10 +776,10 @@ static int mxc_isi_cap_querycap(struct file *file, void *priv, static int mxc_isi_cap_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); struct mxc_isi_fmt *fmt; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); if (f->index >= (int)ARRAY_SIZE(mxc_isi_out_formats)) return -EINVAL; @@ -798,12 +797,12 @@ static int mxc_isi_cap_enum_fmt(struct file *file, void *priv, static int mxc_isi_cap_g_fmt_mplane(struct file *file, void *fh, struct v4l2_format *f) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; - struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_frame *dst_f = &isi_cap->dst_f; int i; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); pix->width = dst_f->o_width; pix->height = dst_f->o_height; @@ -823,12 +822,12 @@ static int mxc_isi_cap_g_fmt_mplane(struct file *file, void *fh, static int mxc_isi_cap_try_fmt_mplane(struct file *file, void *fh, struct v4l2_format *f) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; struct mxc_isi_fmt *fmt; int i; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) { fmt = &mxc_isi_out_formats[i]; @@ -837,13 +836,13 @@ static int mxc_isi_cap_try_fmt_mplane(struct file *file, void *fh, } if (i >= ARRAY_SIZE(mxc_isi_out_formats)) { - v4l2_err(&mxc_isi->isi_cap.sd, "format(%.4s) is not support!\n", + v4l2_err(&isi_cap->sd, "format(%.4s) is not support!\n", (char *)&pix->pixelformat); return -EINVAL; } if (pix->width <= 0 || pix->height <= 0) { - v4l2_err(&mxc_isi->isi_cap.sd, "%s, W/H=(%d, %d) is not valid\n" + v4l2_err(&isi_cap->sd, "%s, W/H=(%d, %d) is not valid\n" , __func__, pix->width, pix->height); return -EINVAL; } @@ -852,23 +851,23 @@ static int mxc_isi_cap_try_fmt_mplane(struct file *file, void *fh, } /* Update input frame size and formate */ -static int mxc_isi_source_fmt_init(struct mxc_isi_dev *mxc_isi) +static int mxc_isi_source_fmt_init(struct mxc_isi_cap_dev *isi_cap) { - struct mxc_isi_frame *src_f = &mxc_isi->isi_cap.src_f; - struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_frame *src_f = &isi_cap->src_f; + struct mxc_isi_frame *dst_f = &isi_cap->dst_f; struct v4l2_subdev_format src_fmt; struct media_pad *source_pad; struct v4l2_subdev *src_sd; int ret; - source_pad = mxc_isi_get_remote_source_pad(mxc_isi); + source_pad = mxc_isi_get_remote_source_pad(isi_cap); if (!source_pad) { - v4l2_err(&mxc_isi->isi_cap.sd, + v4l2_err(&isi_cap->sd, "%s, No remote pad found!\n", __func__); return -EINVAL; } - src_sd = mxc_get_remote_subdev(mxc_isi, __func__); + src_sd = mxc_get_remote_subdev(isi_cap, __func__); if (!src_sd) return -EINVAL; @@ -879,7 +878,7 @@ static int mxc_isi_source_fmt_init(struct mxc_isi_dev *mxc_isi) src_fmt.format.height = dst_f->height; ret = v4l2_subdev_call(src_sd, pad, set_fmt, NULL, &src_fmt); if (ret < 0 && ret != -ENOIOCTLCMD) { - v4l2_err(&mxc_isi->isi_cap.sd, "set remote fmt fail!\n"); + v4l2_err(&isi_cap->sd, "set remote fmt fail!\n"); return -EINVAL; } @@ -888,7 +887,7 @@ static int mxc_isi_source_fmt_init(struct mxc_isi_dev *mxc_isi) src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; ret = v4l2_subdev_call(src_sd, pad, get_fmt, NULL, &src_fmt); if (ret < 0 && ret != -ENOIOCTLCMD) { - v4l2_err(&mxc_isi->isi_cap.sd, "get remote fmt fail!\n"); + v4l2_err(&isi_cap->sd, "get remote fmt fail!\n"); return -EINVAL; } @@ -898,7 +897,7 @@ static int mxc_isi_source_fmt_init(struct mxc_isi_dev *mxc_isi) set_frame_bounds(src_f, src_fmt.format.width, src_fmt.format.height); if (dst_f->width > src_f->width || dst_f->height > src_f->height) { - dev_err(&mxc_isi->pdev->dev, + dev_err(&isi_cap->pdev->dev, "%s: src:(%d,%d), dst:(%d,%d) Not support upscale\n", __func__, src_f->width, src_f->height, @@ -912,9 +911,9 @@ static int mxc_isi_source_fmt_init(struct mxc_isi_dev *mxc_isi) static int mxc_isi_cap_s_fmt_mplane(struct file *file, void *priv, struct v4l2_format *f) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; - struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_frame *dst_f = &isi_cap->dst_f; struct mxc_isi_fmt *fmt; int bpl; int i; @@ -928,8 +927,8 @@ static int mxc_isi_cap_s_fmt_mplane(struct file *file, void *priv, * Step5: Update mxc isi channel configuration. */ - dev_dbg(&mxc_isi->pdev->dev, "%s, fmt=0x%X\n", __func__, pix->pixelformat); - if (vb2_is_busy(&mxc_isi->isi_cap.vb2_q)) + dev_dbg(&isi_cap->pdev->dev, "%s, fmt=0x%X\n", __func__, pix->pixelformat); + if (vb2_is_busy(&isi_cap->vb2_q)) return -EBUSY; /* Check out put format */ @@ -940,7 +939,7 @@ static int mxc_isi_cap_s_fmt_mplane(struct file *file, void *priv, } if (i >= ARRAY_SIZE(mxc_isi_out_formats)) { - dev_dbg(&mxc_isi->pdev->dev, + dev_dbg(&isi_cap->pdev->dev, "format(%.4s) is not support!\n", (char *)&pix->pixelformat); return -EINVAL; } @@ -982,22 +981,23 @@ static int mxc_isi_cap_s_fmt_mplane(struct file *file, void *priv, dst_f->sizeimage[0] = dst_f->height * dst_f->bytesperline[0]; } - memcpy(&mxc_isi->pix, pix, sizeof(*pix)); + memcpy(&isi_cap->pix, pix, sizeof(*pix)); set_frame_bounds(dst_f, pix->width, pix->height); return 0; } -static int mxc_isi_config_parm(struct mxc_isi_dev *mxc_isi) +static int mxc_isi_config_parm(struct mxc_isi_cap_dev *isi_cap) { + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev); int ret; - ret = mxc_isi_source_fmt_init(mxc_isi); + ret = mxc_isi_source_fmt_init(isi_cap); if (ret < 0) return -EINVAL; mxc_isi_channel_init(mxc_isi); - mxc_isi_channel_config(mxc_isi); + mxc_isi_channel_config(mxc_isi, &isi_cap->src_f, &isi_cap->dst_f); return 0; } @@ -1005,18 +1005,19 @@ static int mxc_isi_config_parm(struct mxc_isi_dev *mxc_isi) static int mxc_isi_cap_streamon(struct file *file, void *priv, enum v4l2_buf_type type) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev); int ret; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); - ret = mxc_isi_config_parm(mxc_isi); + ret = mxc_isi_config_parm(isi_cap); if (ret < 0) return -EINVAL; ret = vb2_ioctl_streamon(file, priv, type); - mxc_isi_channel_enable(mxc_isi); - mxc_isi_pipeline_enable(mxc_isi, 1); + mxc_isi_channel_enable(mxc_isi, mxc_isi->m2m_enabled); + mxc_isi_pipeline_enable(isi_cap, 1); mxc_isi->is_streaming = 1; @@ -1026,12 +1027,13 @@ static int mxc_isi_cap_streamon(struct file *file, void *priv, static int mxc_isi_cap_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev); int ret; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); - mxc_isi_pipeline_enable(mxc_isi, 0); + mxc_isi_pipeline_enable(isi_cap, 0); mxc_isi_channel_disable(mxc_isi); ret = vb2_ioctl_streamoff(file, priv, type); @@ -1043,10 +1045,10 @@ static int mxc_isi_cap_streamoff(struct file *file, void *priv, static int mxc_isi_cap_g_selection(struct file *file, void *fh, struct v4l2_selection *s) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); - struct mxc_isi_frame *f = &mxc_isi->isi_cap.src_f; + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct mxc_isi_frame *f = &isi_cap->src_f; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) return -EINVAL; @@ -1054,7 +1056,8 @@ static int mxc_isi_cap_g_selection(struct file *file, void *fh, switch (s->target) { case V4L2_SEL_TGT_COMPOSE_DEFAULT: case V4L2_SEL_TGT_COMPOSE_BOUNDS: - f = &mxc_isi->isi_cap.dst_f; + f = &isi_cap->dst_f; + /* fall through */ case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_CROP_DEFAULT: s->r.left = 0; @@ -1064,7 +1067,8 @@ static int mxc_isi_cap_g_selection(struct file *file, void *fh, return 0; case V4L2_SEL_TGT_COMPOSE: - f = &mxc_isi->isi_cap.dst_f; + f = &isi_cap->dst_f; + /* fall through */ case V4L2_SEL_TGT_CROP: s->r.left = f->h_off; s->r.top = f->v_off; @@ -1093,19 +1097,19 @@ static int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b) static int mxc_isi_cap_s_selection(struct file *file, void *fh, struct v4l2_selection *s) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); struct mxc_isi_frame *f; struct v4l2_rect rect = s->r; unsigned long flags; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) return -EINVAL; if (s->target == V4L2_SEL_TGT_COMPOSE) - f = &mxc_isi->isi_cap.dst_f; + f = &isi_cap->dst_f; else if (s->target == V4L2_SEL_TGT_CROP) - f = &mxc_isi->isi_cap.src_f; + f = &isi_cap->src_f; else return -EINVAL; @@ -1118,10 +1122,10 @@ static int mxc_isi_cap_s_selection(struct file *file, void *fh, return -ERANGE; s->r = rect; - spin_lock_irqsave(&mxc_isi->slock, flags); + spin_lock_irqsave(&isi_cap->slock, flags); set_frame_crop(f, s->r.left, s->r.top, s->r.width, s->r.height); - spin_unlock_irqrestore(&mxc_isi->slock, flags); + spin_unlock_irqrestore(&isi_cap->slock, flags); return 0; } @@ -1129,7 +1133,7 @@ static int mxc_isi_cap_s_selection(struct file *file, void *fh, static int mxc_isi_cap_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); struct v4l2_subdev *sd; struct mxc_isi_fmt *fmt; struct v4l2_subdev_frame_size_enum fse = { @@ -1143,9 +1147,9 @@ static int mxc_isi_cap_enum_framesizes(struct file *file, void *priv, return -EINVAL; fse.code = fmt->mbus_code; - sd = mxc_get_remote_subdev(mxc_isi, __func__); + sd = mxc_get_remote_subdev(isi_cap, __func__); if (!sd) { - v4l2_err(&mxc_isi->isi_cap.sd, "Can't find subdev\n"); + v4l2_err(&isi_cap->sd, "Can't find subdev\n"); return -ENODEV; } @@ -1175,7 +1179,7 @@ static int mxc_isi_cap_enum_framesizes(struct file *file, void *priv, static int mxc_isi_cap_enum_frameintervals(struct file *file, void *fh, struct v4l2_frmivalenum *interval) { - struct mxc_isi_dev *mxc_isi = video_drvdata(file); + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); struct v4l2_subdev *sd; struct mxc_isi_fmt *fmt; struct v4l2_subdev_frame_interval_enum fie = { @@ -1191,7 +1195,7 @@ static int mxc_isi_cap_enum_frameintervals(struct file *file, void *fh, return -EINVAL; fie.code = fmt->mbus_code; - sd = mxc_get_remote_subdev(mxc_isi, __func__); + sd = mxc_get_remote_subdev(isi_cap, __func__); if (!sd) return -EINVAL; @@ -1237,9 +1241,9 @@ static int mxc_isi_link_setup(struct media_entity *entity, const struct media_pad *remote, u32 flags) { struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); - struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd); - if (WARN_ON(!mxc_isi)) + if (WARN_ON(!isi_cap)) return 0; if (!(flags & MEDIA_LNK_FL_ENABLED)) @@ -1254,7 +1258,7 @@ static int mxc_isi_link_setup(struct media_entity *entity, case MXC_ISI_SD_PAD_SOURCE_MEM: break; default: - dev_err(&mxc_isi->pdev->dev, "invalid source pad\n"); + dev_err(&isi_cap->pdev->dev, "invalid source pad\n"); return -EINVAL; } } else if (local->flags & MEDIA_PAD_FL_SINK) { @@ -1274,7 +1278,7 @@ static int mxc_isi_link_setup(struct media_entity *entity, case MXC_ISI_SD_PAD_SINK_PARALLEL_CSI: break; default: - dev_err(&mxc_isi->pdev->dev, + dev_err(&isi_cap->pdev->dev, "%s invalid sink pad\n", __func__); return -EINVAL; } @@ -1298,17 +1302,17 @@ static int mxc_isi_subdev_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { - struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd); struct mxc_isi_frame *f; struct v4l2_mbus_framefmt *mf; - mutex_lock(&mxc_isi->lock); + mutex_lock(&isi_cap->lock); switch (fmt->pad) { case MXC_ISI_SD_PAD_SOURCE_MEM: case MXC_ISI_SD_PAD_SOURCE_DC0: case MXC_ISI_SD_PAD_SOURCE_DC1: - f = &mxc_isi->isi_cap.dst_f; + f = &isi_cap->dst_f; break; case MXC_ISI_SD_PAD_SINK_MIPI0_VC0: case MXC_ISI_SD_PAD_SINK_MIPI0_VC1: @@ -1322,11 +1326,11 @@ static int mxc_isi_subdev_get_fmt(struct v4l2_subdev *sd, case MXC_ISI_SD_PAD_SINK_DC0: case MXC_ISI_SD_PAD_SINK_DC1: case MXC_ISI_SD_PAD_SINK_MEM: - f = &mxc_isi->isi_cap.src_f; + f = &isi_cap->src_f; break; default: - mutex_unlock(&mxc_isi->lock); - v4l2_err(&mxc_isi->isi_cap.sd, + mutex_unlock(&isi_cap->lock); + v4l2_err(&isi_cap->sd, "%s, Pad is not support now!\n", __func__); return -1; } @@ -1338,7 +1342,7 @@ static int mxc_isi_subdev_get_fmt(struct v4l2_subdev *sd, mf->width = f->width; mf->height = f->height; - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); mf->colorspace = V4L2_COLORSPACE_JPEG; return 0; @@ -1348,14 +1352,14 @@ static int mxc_isi_subdev_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { - struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd); struct v4l2_mbus_framefmt *mf = &fmt->format; - struct mxc_isi_frame *dst_f = &mxc_isi->isi_cap.dst_f; + struct mxc_isi_frame *dst_f = &isi_cap->dst_f; struct mxc_isi_fmt *out_fmt; int i; if (fmt->pad < MXC_ISI_SD_PAD_SOURCE_MEM && - vb2_is_busy(&mxc_isi->isi_cap.vb2_q)) + vb2_is_busy(&isi_cap->vb2_q)) return -EBUSY; for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) { @@ -1364,18 +1368,18 @@ static int mxc_isi_subdev_set_fmt(struct v4l2_subdev *sd, break; } if (i >= ARRAY_SIZE(mxc_isi_out_formats)) { - v4l2_err(&mxc_isi->isi_cap.sd, + v4l2_err(&isi_cap->sd, "%s, format is not support!\n", __func__); return -EINVAL; } - mutex_lock(&mxc_isi->lock); + mutex_lock(&isi_cap->lock); /* update out put frame size and formate */ dst_f->fmt = &mxc_isi_out_formats[i]; set_frame_bounds(dst_f, mf->width, mf->height); - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); - dev_dbg(&mxc_isi->pdev->dev, "pad%d: code: 0x%x, %dx%d", + dev_dbg(&isi_cap->pdev->dev, "pad%d: code: 0x%x, %dx%d", fmt->pad, mf->code, mf->width, mf->height); return 0; @@ -1385,22 +1389,23 @@ static int mxc_isi_subdev_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_selection *sel) { - struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); - struct mxc_isi_frame *f = &mxc_isi->isi_cap.src_f; + struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd); + struct mxc_isi_frame *f = &isi_cap->src_f; struct v4l2_rect *r = &sel->r; struct v4l2_rect *try_sel; - mutex_lock(&mxc_isi->lock); + mutex_lock(&isi_cap->lock); switch (sel->target) { case V4L2_SEL_TGT_COMPOSE_BOUNDS: - f = &mxc_isi->isi_cap.dst_f; + f = &isi_cap->dst_f; + /* fall through */ case V4L2_SEL_TGT_CROP_BOUNDS: r->width = f->o_width; r->height = f->o_height; r->left = 0; r->top = 0; - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); return 0; case V4L2_SEL_TGT_CROP: @@ -1408,10 +1413,10 @@ static int mxc_isi_subdev_get_selection(struct v4l2_subdev *sd, break; case V4L2_SEL_TGT_COMPOSE: try_sel = v4l2_subdev_get_try_compose(sd, cfg, sel->pad); - f = &mxc_isi->isi_cap.dst_f; + f = &isi_cap->dst_f; break; default: - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); return -EINVAL; } @@ -1424,12 +1429,12 @@ static int mxc_isi_subdev_get_selection(struct v4l2_subdev *sd, r->height = f->height; } - dev_dbg(&mxc_isi->pdev->dev, + dev_dbg(&isi_cap->pdev->dev, "%s, target %#x: l:%d, t:%d, %dx%d, f_w: %d, f_h: %d", __func__, sel->pad, r->left, r->top, r->width, r->height, f->c_width, f->c_height); - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); return 0; } @@ -1437,13 +1442,13 @@ static int mxc_isi_subdev_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_selection *sel) { - struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); - struct mxc_isi_frame *f = &mxc_isi->isi_cap.src_f; + struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd); + struct mxc_isi_frame *f = &isi_cap->src_f; struct v4l2_rect *r = &sel->r; struct v4l2_rect *try_sel; unsigned long flags; - mutex_lock(&mxc_isi->lock); + mutex_lock(&isi_cap->lock); switch (sel->target) { case V4L2_SEL_TGT_CROP: @@ -1451,25 +1456,25 @@ static int mxc_isi_subdev_set_selection(struct v4l2_subdev *sd, break; case V4L2_SEL_TGT_COMPOSE: try_sel = v4l2_subdev_get_try_compose(sd, cfg, sel->pad); - f = &mxc_isi->isi_cap.dst_f; + f = &isi_cap->dst_f; break; default: - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); return -EINVAL; } if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { *try_sel = sel->r; } else { - spin_lock_irqsave(&mxc_isi->slock, flags); + spin_lock_irqsave(&isi_cap->slock, flags); set_frame_crop(f, r->left, r->top, r->width, r->height); - spin_unlock_irqrestore(&mxc_isi->slock, flags); + spin_unlock_irqrestore(&isi_cap->slock, flags); } - dev_dbg(&mxc_isi->pdev->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__, + dev_dbg(&isi_cap->pdev->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__, sel->target, r->left, r->top, r->width, r->height); - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); return 0; } @@ -1486,17 +1491,16 @@ static struct v4l2_subdev_ops mxc_isi_subdev_ops = { .pad = &mxc_isi_subdev_pad_ops, }; -static int mxc_isi_register_cap_device(struct mxc_isi_dev *mxc_isi, +static int mxc_isi_register_cap_device(struct mxc_isi_cap_dev *isi_cap, struct v4l2_device *v4l2_dev) { - struct video_device *vdev = &mxc_isi->isi_cap.vdev; - struct vb2_queue *q = &mxc_isi->isi_cap.vb2_q; - struct mxc_isi_cap_dev *isi_cap = &mxc_isi->isi_cap; + struct video_device *vdev = &isi_cap->vdev; + struct vb2_queue *q = &isi_cap->vb2_q; int ret = -ENOMEM; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); memset(vdev, 0, sizeof(*vdev)); - snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", mxc_isi->id); + snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", isi_cap->id); vdev->fops = &mxc_isi_capture_fops; vdev->ioctl_ops = &mxc_isi_capture_ioctl_ops; @@ -1504,24 +1508,24 @@ static int mxc_isi_register_cap_device(struct mxc_isi_dev *mxc_isi, vdev->minor = -1; vdev->release = video_device_release_empty; vdev->queue = q; - vdev->lock = &mxc_isi->lock; + vdev->lock = &isi_cap->lock; vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE; - video_set_drvdata(vdev, mxc_isi); + video_set_drvdata(vdev, isi_cap); - INIT_LIST_HEAD(&mxc_isi->isi_cap.out_pending); - INIT_LIST_HEAD(&mxc_isi->isi_cap.out_active); - INIT_LIST_HEAD(&mxc_isi->isi_cap.out_discard); + INIT_LIST_HEAD(&isi_cap->out_pending); + INIT_LIST_HEAD(&isi_cap->out_active); + INIT_LIST_HEAD(&isi_cap->out_discard); memset(q, 0, sizeof(*q)); q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; - q->drv_priv = mxc_isi; + q->drv_priv = isi_cap; q->ops = &mxc_cap_vb2_qops; q->mem_ops = &vb2_dma_contig_memops; q->buf_struct_size = sizeof(struct mxc_isi_buffer); q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - q->lock = &mxc_isi->lock; + q->lock = &isi_cap->lock; ret = vb2_queue_init(q); if (ret) @@ -1539,7 +1543,7 @@ static int mxc_isi_register_cap_device(struct mxc_isi_dev *mxc_isi, if (ret) goto err_free_ctx; - ret = mxc_isi_ctrls_create(mxc_isi); + ret = mxc_isi_ctrls_create(isi_cap); if (ret) goto err_me_cleanup; @@ -1547,43 +1551,31 @@ static int mxc_isi_register_cap_device(struct mxc_isi_dev *mxc_isi, if (ret) goto err_ctrl_free; - vdev->ctrl_handler = &mxc_isi->ctrls.handler; + vdev->ctrl_handler = &isi_cap->ctrls.handler; v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n", vdev->name, video_device_node_name(vdev)); return 0; err_ctrl_free: - mxc_isi_ctrls_delete(mxc_isi); + mxc_isi_ctrls_delete(isi_cap); err_me_cleanup: media_entity_cleanup(&vdev->entity); err_free_ctx: return ret; } -static int mxc_isi_register_cap_and_m2m_device(struct mxc_isi_dev *mxc_isi, - struct v4l2_device *v4l2_dev) -{ - int ret; - - ret = mxc_isi_register_cap_device(mxc_isi, v4l2_dev); - if (ret) - return ret; - - return 0; -} - static int mxc_isi_subdev_registered(struct v4l2_subdev *sd) { - struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct mxc_isi_cap_dev *isi_cap = sd_to_cap_dev(sd); int ret; - if (!mxc_isi) + if (!isi_cap) return -ENXIO; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); - ret = mxc_isi_register_cap_and_m2m_device(mxc_isi, sd->v4l2_dev); + ret = mxc_isi_register_cap_device(isi_cap, sd->v4l2_dev); if (ret < 0) return ret; @@ -1592,22 +1584,22 @@ static int mxc_isi_subdev_registered(struct v4l2_subdev *sd) static void mxc_isi_subdev_unregistered(struct v4l2_subdev *sd) { - struct mxc_isi_dev *mxc_isi = v4l2_get_subdevdata(sd); + struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd); struct video_device *vdev; - if (!mxc_isi) + if (!isi_cap) return; - dev_dbg(&mxc_isi->pdev->dev, "%s\n", __func__); + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); - mutex_lock(&mxc_isi->lock); - vdev = &mxc_isi->isi_cap.vdev; + mutex_lock(&isi_cap->lock); + vdev = &isi_cap->vdev; if (video_is_registered(vdev)) { video_unregister_device(vdev); - mxc_isi_ctrls_delete(mxc_isi); + mxc_isi_ctrls_delete(isi_cap); media_entity_cleanup(&vdev->entity); } - mutex_unlock(&mxc_isi->lock); + mutex_unlock(&isi_cap->lock); } static const struct v4l2_subdev_internal_ops mxc_isi_capture_sd_internal_ops = { @@ -1615,54 +1607,109 @@ static const struct v4l2_subdev_internal_ops mxc_isi_capture_sd_internal_ops = { .unregistered = mxc_isi_subdev_unregistered, }; -int mxc_isi_initialize_capture_subdev(struct mxc_isi_dev *mxc_isi) +static int isi_cap_probe(struct platform_device *pdev) { - struct v4l2_subdev *sd = &mxc_isi->isi_cap.sd; + struct device *dev = &pdev->dev; + struct mxc_isi_dev *mxc_isi; + struct mxc_isi_cap_dev *isi_cap; + struct v4l2_subdev *sd; int ret; + isi_cap = devm_kzalloc(dev, sizeof(*isi_cap), GFP_KERNEL); + if (!isi_cap) + return -ENOMEM; + + dev->parent = mxc_isi_dev_get_parent(pdev); + if (!dev->parent) { + dev_info(dev, "deferring %s device registration\n", dev_name(dev)); + return -EPROBE_DEFER; + } + + mxc_isi = mxc_isi_get_hostdata(pdev); + if (!mxc_isi) { + dev_info(dev, "deferring %s device registration\n", dev_name(dev)); + return -EPROBE_DEFER; + } + + isi_cap->pdev = pdev; + isi_cap->id = mxc_isi->id; + mxc_isi->isi_cap = isi_cap; + + spin_lock_init(&isi_cap->slock); + mutex_init(&isi_cap->lock); + + sd = &isi_cap->sd; v4l2_subdev_init(sd, &mxc_isi_subdev_ops); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", mxc_isi->id); + snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", isi_cap->id); sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; /* ISI Sink pads */ - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC0].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC1].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC2].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC3].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC0].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC1].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC2].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC3].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_DC0].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_DC1].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_HDMI].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_MEM].flags = MEDIA_PAD_FL_SINK; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SINK_PARALLEL_CSI].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC0].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC1].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC2].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC3].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC0].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC1].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC2].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC3].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_DC0].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_DC1].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_HDMI].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MEM].flags = MEDIA_PAD_FL_SINK; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_PARALLEL_CSI].flags = MEDIA_PAD_FL_SINK; /* ISI source pads */ - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SOURCE_MEM].flags = MEDIA_PAD_FL_SOURCE; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SOURCE_DC0].flags = MEDIA_PAD_FL_SOURCE; - mxc_isi->isi_cap.sd_pads[MXC_ISI_SD_PAD_SOURCE_DC1].flags = MEDIA_PAD_FL_SOURCE; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_MEM].flags = MEDIA_PAD_FL_SOURCE; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_DC0].flags = MEDIA_PAD_FL_SOURCE; + isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_DC1].flags = MEDIA_PAD_FL_SOURCE; - ret = media_entity_pads_init(&sd->entity, MXC_ISI_SD_PADS_NUM, - mxc_isi->isi_cap.sd_pads); + ret = media_entity_pads_init(&sd->entity, MXC_ISI_SD_PADS_NUM, isi_cap->sd_pads); if (ret) return ret; sd->entity.ops = &mxc_isi_sd_media_ops; sd->internal_ops = &mxc_isi_capture_sd_internal_ops; - v4l2_set_subdevdata(sd, mxc_isi); + v4l2_set_subdevdata(sd, isi_cap); + platform_set_drvdata(pdev, isi_cap); + + pm_runtime_enable(dev); return 0; } -void mxc_isi_unregister_capture_subdev(struct mxc_isi_dev *mxc_isi) +static int isi_cap_remove(struct platform_device *pdev) { - struct v4l2_subdev *sd = &mxc_isi->isi_cap.sd; + struct mxc_isi_cap_dev *isi_cap = platform_get_drvdata(pdev); + struct v4l2_subdev *sd = &isi_cap->sd; v4l2_device_unregister_subdev(sd); media_entity_cleanup(&sd->entity); v4l2_set_subdevdata(sd, NULL); + pm_runtime_disable(&pdev->dev); + + return 0; } + +static const struct of_device_id isi_cap_of_match[] = { + {.compatible = "imx-isi-capture",}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, isi_cap_of_match); + +static struct platform_driver isi_cap_driver = { + .probe = isi_cap_probe, + .remove = isi_cap_remove, + .driver = { + .of_match_table = isi_cap_of_match, + .name = "isi-capture", + }, +}; +module_platform_driver(isi_cap_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX8 Image Sensor Interface Capture driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ISI Capture"); +MODULE_VERSION("1.0"); From d8977ba09f7e4827cc4d68bc670b72da3fa3d06b Mon Sep 17 00:00:00 2001 From: "Guoniu.zhou" Date: Thu, 17 Oct 2019 15:21:29 +0800 Subject: [PATCH 7/9] media: staging: imx: add V4L2 memory to memory driver for ISI of imx8qxp/qm ISI is an image sensor interface which used to process image data from camera sensor or memory. Add ISI V4L2 memory to memory function driver for it. Signed-off-by: Guoniu.zhou --- drivers/staging/media/imx/Kconfig | 6 + drivers/staging/media/imx/Makefile | 1 + drivers/staging/media/imx/imx8-isi-m2m.c | 1202 ++++++++++++++++++++++ 3 files changed, 1209 insertions(+) create mode 100644 drivers/staging/media/imx/imx8-isi-m2m.c diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig index 7ea840bcd711..7ba0c18e79a7 100644 --- a/drivers/staging/media/imx/Kconfig +++ b/drivers/staging/media/imx/Kconfig @@ -58,5 +58,11 @@ config IMX8_ISI_CAPTURE select VIDEOBUF2_DMA_CONTIG default y +config IMX8_ISI_M2M + bool "IMX8 Image Sensor Interface Memory to Memory Device Driver" + select V4L2_MEM2MEM_DEV + depends on IMX8_ISI_CORE + default y + endmenu endif #VIDEO_IMX_CAPTURE diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile index 31f981f54416..30929a4193a0 100644 --- a/drivers/staging/media/imx/Makefile +++ b/drivers/staging/media/imx/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o obj-$(CONFIG_IMX8_ISI_CORE) += imx8-capture.o obj-$(CONFIG_IMX8_ISI_CAPTURE) += imx8-isi-cap.o +obj-$(CONFIG_IMX8_ISI_M2M) += imx8-isi-m2m.o diff --git a/drivers/staging/media/imx/imx8-isi-m2m.c b/drivers/staging/media/imx/imx8-isi-m2m.c new file mode 100644 index 000000000000..2d116df4b83e --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-m2m.c @@ -0,0 +1,1202 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ISI V4L2 memory to memory driver for i.MX8QXP/QM platform + * + * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which + * used to process image from camera sensor or memory to memory or DC + * + * Copyright (c) 2019 NXP Semiconductor + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx8-isi-hw.h" +#include "imx8-common.h" + +#define to_isi_buffer(x) \ + container_of((x), struct mxc_isi_buffer, v4l2_buf) + +#define file_to_ctx(file) \ + container_of(file->private_data, struct mxc_isi_ctx, fh); + +#if defined(CONFIG_IMX8_ISI_CAPTURE) +extern struct mxc_isi_fmt mxc_isi_out_formats[9]; +#else +static struct mxc_isi_fmt mxc_isi_out_formats[9] = {}; +#endif + +struct mxc_isi_fmt mxc_isi_input_formats[] = { + /* Pixel link input format */ + { + .name = "XBGR32", + .fourcc = V4L2_PIX_FMT_XBGR32, + .depth = { 32 }, + .color = MXC_ISI_M2M_IN_FMT_XRGB8, + .memplanes = 1, + .colplanes = 1, + }, { + .name = "RGB565", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = { 16 }, + .color = MXC_ISI_M2M_IN_FMT_RGB565, + .memplanes = 1, + .colplanes = 1, + }, { + .name = "YUV24 (X-Y-U-V)", + .fourcc = V4L2_PIX_FMT_YUV24, + .depth = { 24 }, + .color = MXC_ISI_M2M_IN_FMT_YUV444_1P8P, + .memplanes = 1, + .colplanes = 1, + }, { + .name = "YUV16 (X-Y-U-V)", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = { 16 }, + .color = MXC_ISI_M2M_IN_FMT_YUV422_1P8P, + .memplanes = 1, + .colplanes = 1, + }, { + .name = "RGBA (R-G-B-A)", + .fourcc = V4L2_PIX_FMT_RGBA, + .depth = { 32 }, + .color = MXC_ISI_M2M_IN_FMT_XBGR8, + .memplanes = 1, + .colplanes = 1, + } +}; + +static struct v4l2_m2m_buffer *to_v4l2_m2m_buffer(struct vb2_v4l2_buffer *vbuf) +{ + struct v4l2_m2m_buffer *b; + + b = container_of(vbuf, struct v4l2_m2m_buffer, vb); + return b; +} + +void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi) +{ + struct mxc_isi_m2m_dev *isi_m2m = mxc_isi->isi_m2m; + struct v4l2_fh *fh; + struct mxc_isi_ctx *curr_mxc_ctx; + struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf; + struct mxc_isi_buffer *src_buf, *dst_buf; + struct v4l2_m2m_buffer *b; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + + curr_mxc_ctx = v4l2_m2m_get_curr_priv(isi_m2m->m2m_dev); + if (!curr_mxc_ctx) { + dev_err(&isi_m2m->pdev->dev, + "Instance released before the end of transaction\n"); + return; + } + fh = &curr_mxc_ctx->fh; + + if (isi_m2m->aborting) { + mxc_isi_channel_disable(mxc_isi); + dev_warn(&isi_m2m->pdev->dev, "Aborting current job\n"); + goto job_finish; + } + + src_vbuf = v4l2_m2m_next_src_buf(fh->m2m_ctx); + if (!src_vbuf) { + dev_err(&isi_m2m->pdev->dev, "No enought source buffers\n"); + goto job_finish; + } + src_buf = to_isi_buffer(src_vbuf); + v4l2_m2m_src_buf_remove(fh->m2m_ctx); + v4l2_m2m_buf_done(src_vbuf, VB2_BUF_STATE_DONE); + + if (!list_empty(&isi_m2m->out_active)) { + dst_buf = list_first_entry(&isi_m2m->out_active, + struct mxc_isi_buffer, list); + dst_vbuf = &dst_buf->v4l2_buf; + list_del_init(&dst_buf->list); + dst_buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns(); + v4l2_m2m_buf_done(dst_vbuf, VB2_BUF_STATE_DONE); + + } + isi_m2m->frame_count++; + + dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx); + if (dst_vbuf) { + dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE; + dst_buf = to_isi_buffer(dst_vbuf); + dst_buf->v4l2_buf.sequence = isi_m2m->frame_count; + mxc_isi_channel_set_outbuf(mxc_isi, dst_buf); + v4l2_m2m_dst_buf_remove(fh->m2m_ctx); + b = to_v4l2_m2m_buffer(dst_vbuf); + list_add_tail(&b->list, &isi_m2m->out_active); + } + +job_finish: + v4l2_m2m_job_finish(isi_m2m->m2m_dev, fh->m2m_ctx); +} + +static void mxc_isi_m2m_device_run(void *priv) +{ + struct mxc_isi_ctx *mxc_ctx = priv; + struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m; + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + struct v4l2_fh *fh = &mxc_ctx->fh; + struct vb2_v4l2_buffer *vbuf; + struct mxc_isi_buffer *src_buf; + unsigned long flags; + + dev_dbg(&isi_m2m->pdev->dev, "%s enter\n", __func__); + + spin_lock_irqsave(&isi_m2m->slock, flags); + + /* SRC */ + vbuf = v4l2_m2m_next_src_buf(fh->m2m_ctx); + if (!vbuf) { + dev_err(&isi_m2m->pdev->dev, "Null src buf\n"); + goto unlock; + } + + src_buf = to_isi_buffer(vbuf); + mxc_isi_channel_set_m2m_src_addr(mxc_isi, src_buf); + mxc_isi_channel_enable(mxc_isi, mxc_isi->m2m_enabled); + +unlock: + spin_unlock_irqrestore(&isi_m2m->slock, flags); +} + +static int mxc_isi_m2m_job_ready(void *priv) +{ + struct mxc_isi_ctx *mxc_ctx = priv; + struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m; + struct v4l2_fh *fh = &mxc_ctx->fh; + unsigned int num_src_bufs_ready; + unsigned int num_dst_bufs_ready; + unsigned long flags; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + + spin_lock_irqsave(&isi_m2m->slock, flags); + num_src_bufs_ready = v4l2_m2m_num_src_bufs_ready(fh->m2m_ctx); + num_dst_bufs_ready = v4l2_m2m_num_dst_bufs_ready(fh->m2m_ctx); + spin_unlock_irqrestore(&isi_m2m->slock, flags); + + if (num_src_bufs_ready >= 1 && num_dst_bufs_ready >= 1) + return 1; + return 0; +} + +static void mxc_isi_m2m_job_abort(void *priv) +{ + struct mxc_isi_ctx *mxc_ctx = priv; + struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m; + + isi_m2m->aborting = 1; + dev_dbg(&isi_m2m->pdev->dev, "Abort requested\n"); +} + +static struct v4l2_m2m_ops mxc_isi_m2m_ops = { + .device_run = mxc_isi_m2m_device_run, + .job_ready = mxc_isi_m2m_job_ready, + .job_abort = mxc_isi_m2m_job_abort, +}; + +static int m2m_vb2_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q); + struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m; + struct device *dev = &isi_m2m->pdev->dev; + struct mxc_isi_frame *frame; + struct mxc_isi_fmt *fmt; + unsigned long wh; + int i; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (*num_buffers < 3) { + dev_err(dev, "%s at least need 3 buffer\n", __func__); + return -EINVAL; + } + frame = &isi_m2m->dst_f; + isi_m2m->req_cap_buf_num = *num_buffers; + } else { + if (*num_buffers < 1) { + dev_err(dev, "%s at least need one buffer\n", __func__); + return -EINVAL; + } + frame = &isi_m2m->src_f; + isi_m2m->req_out_buf_num = *num_buffers; + } + + fmt = frame->fmt; + if (fmt == NULL) + return -EINVAL; + + for (i = 0; i < fmt->memplanes; i++) + alloc_devs[i] = &isi_m2m->pdev->dev; + + *num_planes = fmt->memplanes; + wh = frame->width * frame->height; + + for (i = 0; i < fmt->memplanes; i++) { + unsigned int size = (wh * fmt->depth[i]) >> 3; + + if (i == 1 && fmt->fourcc == V4L2_PIX_FMT_NV12) + size >>= 1; + sizes[i] = max_t(u32, size, frame->sizeimage[i]); + + dev_dbg(&isi_m2m->pdev->dev, "%s, buf_n=%d, planes[%d]->size=%d\n", + __func__, *num_buffers, i, sizes[i]); + } + + return 0; +} + +static int m2m_vb2_buffer_prepare(struct vb2_buffer *vb2) +{ + struct vb2_queue *vq = vb2->vb2_queue; + struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(vq); + struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m; + struct mxc_isi_frame *frame; + int i; + + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + frame = &isi_m2m->dst_f; + else + frame = &isi_m2m->src_f; + + if (frame == NULL) + return -EINVAL; + + for (i = 0; i < frame->fmt->memplanes; i++) { + unsigned long size = frame->sizeimage[i]; + + if (vb2_plane_size(vb2, i) < size) { + dev_err(&isi_m2m->pdev->dev, + "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb2, i), size); + return -EINVAL; + } + vb2_set_plane_payload(vb2, i, size); + } + + return 0; +} + +static void m2m_vb2_buffer_queue(struct vb2_buffer *vb2) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); + struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(vb2->vb2_queue); + struct v4l2_fh *fh = &mxc_ctx->fh; + + v4l2_m2m_buf_queue(fh->m2m_ctx, vbuf); +} + +static int m2m_vb2_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q); + struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m; + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + struct v4l2_fh *fh = &mxc_ctx->fh; + struct vb2_v4l2_buffer *dst_vbuf; + struct v4l2_m2m_buffer *b; + struct mxc_isi_buffer *dst_buf; + unsigned long flags; + + if (V4L2_TYPE_IS_OUTPUT(q->type)) + return 0; + + if (count < 2) { + dev_err(&isi_m2m->pdev->dev, "Need to at leas 2 buffers\n"); + return -EINVAL; + } + + spin_lock_irqsave(&isi_m2m->slock, flags); + + /* BUF1 */ + dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx); + if (!dst_vbuf) { + dev_err(&isi_m2m->pdev->dev, "%d: Null dst buf\n", __LINE__); + goto unlock; + } + dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE; + dst_buf = to_isi_buffer(dst_vbuf); + dst_buf->v4l2_buf.sequence = 0; + mxc_isi_channel_set_outbuf(mxc_isi, dst_buf); + v4l2_m2m_dst_buf_remove(fh->m2m_ctx); + b = to_v4l2_m2m_buffer(dst_vbuf); + list_add_tail(&b->list, &isi_m2m->out_active); + + /* BUF2 */ + dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx); + if (!dst_vbuf) { + dev_err(&isi_m2m->pdev->dev, "%d: Null dst buf\n", __LINE__); + goto unlock; + } + dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE; + dst_buf = to_isi_buffer(dst_vbuf); + dst_buf->v4l2_buf.sequence = 1; + mxc_isi_channel_set_outbuf(mxc_isi, dst_buf); + v4l2_m2m_dst_buf_remove(fh->m2m_ctx); + b = to_v4l2_m2m_buffer(dst_vbuf); + list_add_tail(&b->list, &isi_m2m->out_active); + + isi_m2m->frame_count = 1; + isi_m2m->aborting = 0; +unlock: + spin_unlock_irqrestore(&isi_m2m->slock, flags); + + return 0; +} + +static void m2m_vb2_stop_streaming(struct vb2_queue *q) +{ + struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q); + struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m; + struct vb2_v4l2_buffer *vb2; + struct mxc_isi_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&isi_m2m->slock, flags); + + while ((vb2 = v4l2_m2m_src_buf_remove(mxc_ctx->fh.m2m_ctx)) != NULL) + v4l2_m2m_buf_done(vb2, VB2_BUF_STATE_ERROR); + + while ((vb2 = v4l2_m2m_dst_buf_remove(mxc_ctx->fh.m2m_ctx)) != NULL) + v4l2_m2m_buf_done(vb2, VB2_BUF_STATE_ERROR); + + while (!list_empty(&isi_m2m->out_active)) { + buf = list_entry(isi_m2m->out_active.next, struct mxc_isi_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); + } + + INIT_LIST_HEAD(&isi_m2m->out_active); + + spin_unlock_irqrestore(&isi_m2m->slock, flags); +} + +static struct vb2_ops mxc_m2m_vb2_qops = { + .queue_setup = m2m_vb2_queue_setup, + .buf_prepare = m2m_vb2_buffer_prepare, + .buf_queue = m2m_vb2_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = m2m_vb2_start_streaming, + .stop_streaming = m2m_vb2_stop_streaming, +}; + +static int mxc_m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mxc_isi_ctx *mxc_ctx = priv; + struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = mxc_ctx; + src_vq->buf_struct_size = sizeof(struct mxc_isi_buffer); + src_vq->ops = &mxc_m2m_vb2_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &isi_m2m->lock; + src_vq->dev = &isi_m2m->pdev->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = mxc_ctx; + dst_vq->buf_struct_size = sizeof(struct mxc_isi_buffer); + dst_vq->ops = &mxc_m2m_vb2_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &isi_m2m->lock; + dst_vq->dev = &isi_m2m->pdev->dev; + + ret = vb2_queue_init(dst_vq); + return ret; +} + +static int mxc_isi_m2m_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + struct device *dev = &isi_m2m->pdev->dev; + struct mxc_isi_ctx *mxc_ctx = NULL; + int ret = 0; + + if (atomic_read(&mxc_isi->usage_count) > 0) { + dev_err(dev, "ISI channel[%d] is busy\n", isi_m2m->id); + return -EBUSY; + } + + if (mutex_lock_interruptible(&isi_m2m->lock)) + return -ERESTARTSYS; + + mxc_ctx = kzalloc(sizeof(*mxc_ctx), GFP_KERNEL); + if (!mxc_ctx) { + ret = -ENOMEM; + goto unlock; + } + + mxc_ctx->isi_m2m = isi_m2m; + + v4l2_fh_init(&mxc_ctx->fh, vdev); + file->private_data = &mxc_ctx->fh; + + mxc_ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(isi_m2m->m2m_dev, + mxc_ctx, + mxc_m2m_queue_init); + if (IS_ERR(mxc_ctx->fh.m2m_ctx)) { + dev_err(dev, "v4l2_m2m_ctx_init fail\n"); + ret = PTR_ERR(mxc_ctx->fh.m2m_ctx); + v4l2_fh_exit(&mxc_ctx->fh); + kfree(mxc_ctx); + goto unlock; + } + v4l2_fh_add(&mxc_ctx->fh); + + pm_runtime_get_sync(dev); + if (atomic_inc_return(&mxc_isi->usage_count) == 1) + mxc_isi_channel_init(mxc_isi); + + /* lock host data */ + mutex_lock(&mxc_isi->lock); + mxc_isi->m2m_enabled = true; + mutex_unlock(&mxc_isi->lock); +unlock: + mutex_unlock(&isi_m2m->lock); + return ret; +} + +static int mxc_isi_m2m_release(struct file *file) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + struct device *dev = &isi_m2m->pdev->dev; + struct mxc_isi_ctx *mxc_ctx = file_to_ctx(file); + + v4l2_fh_del(&mxc_ctx->fh); + v4l2_fh_exit(&mxc_ctx->fh); + + mutex_lock(&isi_m2m->lock); + v4l2_m2m_ctx_release(mxc_ctx->fh.m2m_ctx); + mutex_unlock(&isi_m2m->lock); + + kfree(mxc_ctx); + if (atomic_dec_and_test(&mxc_isi->usage_count)) + mxc_isi_channel_deinit(mxc_isi); + + mutex_lock(&mxc_isi->lock); + mxc_isi->m2m_enabled = false; + mutex_unlock(&mxc_isi->lock); + + pm_runtime_put(dev); + return 0; +} + +static const struct v4l2_file_operations mxc_isi_m2m_fops = { + .owner = THIS_MODULE, + .open = mxc_isi_m2m_open, + .release = mxc_isi_m2m_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int mxc_isi_m2m_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + + strlcpy(cap->driver, MXC_ISI_M2M, sizeof(cap->driver)); + strlcpy(cap->card, MXC_ISI_M2M, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d", + dev_name(&isi_m2m->pdev->dev), isi_m2m->id); + cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE | + V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int mxc_isi_m2m_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct mxc_isi_fmt *fmt; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + if (f->index >= (int)ARRAY_SIZE(mxc_isi_input_formats)) + return -EINVAL; + + fmt = &mxc_isi_input_formats[f->index]; + strncpy(f->description, fmt->name, sizeof(f->description) - 1); + + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int mxc_isi_m2m_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct mxc_isi_fmt *fmt; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + if (f->index >= (int)ARRAY_SIZE(mxc_isi_out_formats)) + return -EINVAL; + + fmt = &mxc_isi_out_formats[f->index]; + strncpy(f->description, fmt->name, sizeof(f->description) - 1); + + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int mxc_isi_m2m_try_fmt_vid_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct device *dev = &isi_m2m->pdev->dev; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_fmt *fmt = NULL; + int i; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_input_formats); i++) { + fmt = &mxc_isi_input_formats[i]; + if (fmt->fourcc == pix->pixelformat) + break; + } + + if (i >= ARRAY_SIZE(mxc_isi_input_formats)) { + dev_err(dev, "%s, format is not support!\n", __func__); + return -EINVAL; + } + + if (pix->width <= 0 || pix->height <= 0) { + dev_err(dev, "%s, width %d, height %d is not valid\n" + , __func__, pix->width, pix->height); + return -EINVAL; + } + + return 0; +} + +static int mxc_isi_m2m_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct device *dev = &isi_m2m->pdev->dev; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_fmt *fmt = NULL; + int i; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) { + fmt = &mxc_isi_out_formats[i]; + if (fmt->fourcc == pix->pixelformat) + break; + } + + if (i >= ARRAY_SIZE(mxc_isi_out_formats)) { + dev_err(dev, "%s, format is not support!\n", __func__); + return -EINVAL; + } + + if (pix->width <= 0 || pix->height <= 0) { + dev_err(dev, "%s, width %d, height %d is not valid\n" + , __func__, pix->width, pix->height); + return -EINVAL; + } + + return 0; +} + +static int mxc_isi_m2m_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + struct v4l2_fh *fh = file->private_data; + struct mxc_isi_frame *frame = &isi_m2m->src_f; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_fmt *fmt; + struct vb2_queue *vq; + int bpl, i; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + vq = v4l2_m2m_get_vq(fh->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + if (vb2_is_busy(vq)) { + dev_err(&isi_m2m->pdev->dev, "queue busy\n"); + return -EBUSY; + } + + for (i = 0; i < ARRAY_SIZE(mxc_isi_input_formats); i++) { + fmt = &mxc_isi_input_formats[i]; + if (pix && fmt->fourcc == pix->pixelformat) + break; + } + + if (i >= ARRAY_SIZE(mxc_isi_input_formats)) { + dev_dbg(&isi_m2m->pdev->dev, "%s, format is not support!\n", __func__); + return -EINVAL; + } + + /* update out put frame size and formate */ + if (pix->height <= 0 || pix->width <= 0) + return -EINVAL; + + frame->fmt = fmt; + frame->height = pix->height; + frame->width = pix->width; + + pix->num_planes = fmt->memplanes; + for (i = 0; i < pix->num_planes; i++) { + bpl = pix->plane_fmt[i].bytesperline; + + if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width) + pix->plane_fmt[i].bytesperline = + (pix->width * fmt->depth[i]) >> 3; + + if (pix->plane_fmt[i].sizeimage == 0) + pix->plane_fmt[i].sizeimage = (pix->width * pix->height * + fmt->depth[i] >> 3); + } + + frame->bytesperline[0] = frame->width * frame->fmt->depth[0] / 8; + frame->sizeimage[0] = frame->height * frame->bytesperline[0]; + + set_frame_bounds(frame, pix->width, pix->height); + mxc_isi_m2m_config_src(mxc_isi, frame); + + return 0; +} + +static int mxc_isi_m2m_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + struct v4l2_fh *fh = file->private_data; + struct mxc_isi_frame *frame = &isi_m2m->dst_f; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_fmt *fmt; + struct vb2_queue *vq; + int bpl, i; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + vq = v4l2_m2m_get_vq(fh->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + if (vb2_is_busy(vq)) { + dev_err(&isi_m2m->pdev->dev, "queue busy\n"); + return -EBUSY; + } + + for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) { + fmt = &mxc_isi_out_formats[i]; + if (pix && fmt->fourcc == pix->pixelformat) + break; + } + + if (i >= ARRAY_SIZE(mxc_isi_out_formats)) { + dev_err(&isi_m2m->pdev->dev, "%s, format is not support!\n", __func__); + return -EINVAL; + } + + /* update out put frame size and formate */ + if (pix->height <= 0 || pix->width <= 0) { + dev_err(&isi_m2m->pdev->dev, + "Invalid width or height(w=%d, h=%d)\n", + pix->width, pix->height); + return -EINVAL; + } + + if ((pix->pixelformat == V4L2_PIX_FMT_NV12) && ((pix->width / 4) % 2)) { + dev_err(&isi_m2m->pdev->dev, + "Invalid width or height(w=%d, h=%d) for NV12\n", + pix->width, pix->height); + return -EINVAL; + } else if ((pix->pixelformat != V4L2_PIX_FMT_XBGR32) && (pix->width % 2)) { + dev_err(&isi_m2m->pdev->dev, + "Invalid width or height(w=%d, h=%d) for %.4s\n", + pix->width, pix->height, (char *)&pix->pixelformat); + return -EINVAL; + } + + frame->fmt = fmt; + frame->height = pix->height; + frame->width = pix->width; + + pix->num_planes = fmt->memplanes; + for (i = 0; i < pix->num_planes; i++) { + bpl = pix->plane_fmt[i].bytesperline; + + if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width) + pix->plane_fmt[i].bytesperline = + (pix->width * fmt->depth[i]) >> 3; + + if (pix->plane_fmt[i].sizeimage == 0) { + + if ((i == 1) && (pix->pixelformat == V4L2_PIX_FMT_NV12)) + pix->plane_fmt[i].sizeimage = + (pix->width * (pix->height >> 1) * fmt->depth[i] >> 3); + else + pix->plane_fmt[i].sizeimage = (pix->width * pix->height * + fmt->depth[i] >> 3); + } + } + + if (pix->num_planes > 1) { + for (i = 0; i < pix->num_planes; i++) { + frame->bytesperline[i] = pix->plane_fmt[i].bytesperline; + frame->sizeimage[i] = pix->plane_fmt[i].sizeimage; + } + } else { + frame->bytesperline[0] = frame->width * frame->fmt->depth[0] / 8; + frame->sizeimage[0] = frame->height * frame->bytesperline[0]; + } + + /*memcpy(&isi_m2m->pix, pix, sizeof(*pix));*/ + memcpy(&isi_m2m->pix, pix, sizeof(*pix)); + + set_frame_bounds(frame, pix->width, pix->height); + mxc_isi_m2m_config_dst(mxc_isi, frame); + + return 0; +} + +static int mxc_isi_m2m_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_frame *frame = &isi_m2m->dst_f; + int i; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + pix->width = frame->o_width; + pix->height = frame->o_height; + pix->field = V4L2_FIELD_NONE; + pix->pixelformat = frame->fmt->fourcc; + pix->colorspace = V4L2_COLORSPACE_JPEG; + pix->num_planes = frame->fmt->memplanes; + + for (i = 0; i < pix->num_planes; ++i) { + pix->plane_fmt[i].bytesperline = frame->bytesperline[i]; + pix->plane_fmt[i].sizeimage = frame->sizeimage[i]; + } + + return 0; +} + +static int mxc_isi_m2m_g_fmt_vid_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_frame *frame = &isi_m2m->src_f; + int i; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + pix->width = frame->o_width; + pix->height = frame->o_height; + pix->field = V4L2_FIELD_NONE; + pix->pixelformat = frame->fmt->fourcc; + pix->colorspace = V4L2_COLORSPACE_JPEG; + pix->num_planes = frame->fmt->memplanes; + + for (i = 0; i < pix->num_planes; ++i) { + pix->plane_fmt[i].bytesperline = frame->bytesperline[i]; + pix->plane_fmt[i].sizeimage = frame->sizeimage[i]; + } + + return 0; +} + +static int mxc_isi_m2m_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + struct mxc_isi_frame *src_f, *dst_f; + int ret; + + src_f = &isi_m2m->src_f; + dst_f = &isi_m2m->dst_f; + + if ((dst_f->width > src_f->width) || + (dst_f->height > src_f->height)) { + dev_err(&isi_m2m->pdev->dev, "%s Not support upscale\n", __func__); + return -EINVAL; + } + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + isi_m2m->frame_count = 0; + mxc_isi_channel_config(mxc_isi, src_f, dst_f); + } + + ret = v4l2_m2m_ioctl_streamon(file, priv, type); + + return ret; +} + +static int mxc_isi_m2m_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + int ret; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + mxc_isi_channel_disable(mxc_isi); + + ret = v4l2_m2m_ioctl_streamoff(file, priv, type); + + return ret; +} + +static const struct v4l2_ioctl_ops mxc_isi_m2m_ioctl_ops = { + .vidioc_querycap = mxc_isi_m2m_querycap, + + .vidioc_enum_fmt_vid_cap = mxc_isi_m2m_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = mxc_isi_m2m_enum_fmt_vid_out, + + .vidioc_try_fmt_vid_cap_mplane = mxc_isi_m2m_try_fmt_vid_cap, + .vidioc_try_fmt_vid_out_mplane = mxc_isi_m2m_try_fmt_vid_out, + + .vidioc_s_fmt_vid_cap_mplane = mxc_isi_m2m_s_fmt_vid_cap, + .vidioc_s_fmt_vid_out_mplane = mxc_isi_m2m_s_fmt_vid_out, + + .vidioc_g_fmt_vid_cap_mplane = mxc_isi_m2m_g_fmt_vid_cap, + .vidioc_g_fmt_vid_out_mplane = mxc_isi_m2m_g_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + + .vidioc_streamon = mxc_isi_m2m_streamon, + .vidioc_streamoff = mxc_isi_m2m_streamoff, +}; + +/* + * V4L2 controls handling + */ +#define ctrl_to_mxc_isi_m2m(__ctrl) \ + container_of((__ctrl)->handler, struct mxc_isi_m2m_dev, ctrls.handler) + +static int mxc_isi_m2m_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mxc_isi_m2m_dev *isi_m2m = ctrl_to_mxc_isi_m2m(ctrl); + struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev); + unsigned long flags; + int ret = 0; + + dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__); + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + spin_lock_irqsave(&mxc_isi->slock, flags); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctrl->val < 0) { + ret = -EINVAL; + goto unlock; + } + mxc_isi->hflip = (ctrl->val > 0) ? 1 : 0; + break; + + case V4L2_CID_VFLIP: + if (ctrl->val < 0) { + ret = -EINVAL; + goto unlock; + } + mxc_isi->vflip = (ctrl->val > 0) ? 1 : 0; + break; + + case V4L2_CID_ALPHA_COMPONENT: + if (ctrl->val < 0 || ctrl->val > 255) { + ret = -EINVAL; + goto unlock; + } + mxc_isi->alpha = ctrl->val; + mxc_isi->alphaen = 1; + break; + + default: + dev_err(&isi_m2m->pdev->dev, "%s: Not support %d CID\n", __func__, ctrl->id); + ret = -EINVAL; + } + +unlock: + spin_unlock_irqrestore(&mxc_isi->slock, flags); + return ret; +} + +static int mxc_isi_m2m_g_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mxc_isi_m2m_dev *isi_m2m = ctrl_to_mxc_isi_m2m(ctrl); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&isi_m2m->slock, flags); + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + ctrl->val = isi_m2m->req_cap_buf_num; + break; + case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT: + ctrl->val = isi_m2m->req_out_buf_num; + break; + default: + dev_err(&isi_m2m->pdev->dev, "%s: Not support %d CID\n", + __func__, ctrl->id); + ret = -EINVAL; + } + + spin_unlock_irqrestore(&isi_m2m->slock, flags); + return ret; + +} + +static const struct v4l2_ctrl_ops mxc_isi_m2m_ctrl_ops = { + .s_ctrl = mxc_isi_m2m_s_ctrl, + .g_volatile_ctrl = mxc_isi_m2m_g_ctrl, +}; + +static int mxc_isi_m2m_ctrls_create(struct mxc_isi_m2m_dev *isi_m2m) +{ + struct mxc_isi_ctrls *ctrls = &isi_m2m->ctrls; + struct v4l2_ctrl_handler *handler = &ctrls->handler; + + if (isi_m2m->ctrls.ready) + return 0; + + v4l2_ctrl_handler_init(handler, 4); + + ctrls->hflip = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + ctrls->alpha = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, 0, 0xff, 1, 0); + ctrls->num_cap_buf = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 3, 16, 1, 3); + ctrls->num_out_buf = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, 1, 16, 1, 1); + + if (!handler->error) + ctrls->ready = true; + + return handler->error; + +} + +void mxc_isi_m2m_ctrls_delete(struct mxc_isi_m2m_dev *isi_m2m) +{ + struct mxc_isi_ctrls *ctrls = &isi_m2m->ctrls; + + if (ctrls->ready) { + v4l2_ctrl_handler_free(&ctrls->handler); + ctrls->ready = false; + ctrls->alpha = NULL; + } +} + +static int isi_m2m_probe(struct platform_device *pdev) +{ + struct mxc_isi_dev *mxc_isi; + struct mxc_isi_m2m_dev *isi_m2m; + struct v4l2_device *v4l2_dev; + struct video_device *vdev; + int ret = -ENOMEM; + + isi_m2m = devm_kzalloc(&pdev->dev, sizeof(*isi_m2m), GFP_KERNEL); + if (!isi_m2m) + return -ENOMEM; + isi_m2m->pdev = pdev; + + pdev->dev.parent = mxc_isi_dev_get_parent(pdev); + if (!pdev->dev.parent) { + dev_info(&pdev->dev, "deferring %s device registration\n", + dev_name(&pdev->dev)); + return -EPROBE_DEFER; + } + + mxc_isi = mxc_isi_get_hostdata(pdev); + if (!mxc_isi) { + dev_info(&pdev->dev, "deferring %s device registration\n", + dev_name(&pdev->dev)); + return -EPROBE_DEFER; + } + mxc_isi->isi_m2m = isi_m2m; + isi_m2m->id = mxc_isi->id; + + spin_lock_init(&isi_m2m->slock); + mutex_init(&isi_m2m->lock); + + /* m2m */ + isi_m2m->m2m_dev = v4l2_m2m_init(&mxc_isi_m2m_ops); + if (IS_ERR(isi_m2m->m2m_dev)) { + dev_err(&pdev->dev, "%s fail to get m2m device\n", __func__); + return PTR_ERR(isi_m2m->m2m_dev); + } + + /* V4L2 device */ + v4l2_dev = &isi_m2m->v4l2_dev; + strlcpy(v4l2_dev->name, "mx8-isi-m2m", sizeof(v4l2_dev->name)); + + ret = v4l2_device_register(&pdev->dev, v4l2_dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register v4l2_device\n"); + return -EINVAL; + } + + INIT_LIST_HEAD(&isi_m2m->out_active); + + /* Video device */ + vdev = &isi_m2m->vdev; + memset(vdev, 0, sizeof(*vdev)); + snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.m2m", isi_m2m->id); + + vdev->fops = &mxc_isi_m2m_fops; + vdev->ioctl_ops = &mxc_isi_m2m_ioctl_ops; + vdev->v4l2_dev = v4l2_dev; + vdev->minor = -1; + vdev->release = video_device_release_empty; + vdev->vfl_dir = VFL_DIR_M2M; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE | + V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE; + + ret = mxc_isi_m2m_ctrls_create(isi_m2m); + if (ret) + goto free_m2m; + + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + dev_err(&pdev->dev, "%s fail to register video device\n", __func__); + goto ctrl_free; + } + + vdev->ctrl_handler = &isi_m2m->ctrls.handler; + video_set_drvdata(vdev, isi_m2m); + platform_set_drvdata(pdev, isi_m2m); + pm_runtime_enable(&pdev->dev); + + dev_info(&pdev->dev, "Register m2m success for ISI.%d\n", isi_m2m->id); + + return 0; + +ctrl_free: + mxc_isi_m2m_ctrls_delete(isi_m2m); +free_m2m: + v4l2_m2m_release(isi_m2m->m2m_dev); + return ret; + +} + +static int isi_m2m_remove(struct platform_device *pdev) +{ + struct mxc_isi_m2m_dev *isi_m2m = platform_get_drvdata(pdev); + struct video_device *vdev = &isi_m2m->vdev; + + if (video_is_registered(vdev)) { + video_unregister_device(vdev); + mxc_isi_m2m_ctrls_delete(isi_m2m); + media_entity_cleanup(&vdev->entity); + } + v4l2_m2m_release(isi_m2m->m2m_dev); + v4l2_device_unregister(&isi_m2m->v4l2_dev); + pm_runtime_disable(&isi_m2m->pdev->dev); + + return 0; +} + +static const struct of_device_id isi_m2m_of_match[] = { + {.compatible = "imx-isi-m2m",}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, isi_m2m_of_match); + +static struct platform_driver isi_m2m_driver = { + .probe = isi_m2m_probe, + .remove = isi_m2m_remove, + .driver = { + .of_match_table = isi_m2m_of_match, + .name = "isi-m2m", + }, +}; + +static int __init mxc_isi_m2m_init(void) +{ + return platform_driver_register(&isi_m2m_driver); +} +late_initcall(mxc_isi_m2m_init); + +static void __exit mxc_isi_m2m_exit(void) +{ + platform_driver_unregister(&isi_m2m_driver); +} +module_exit(mxc_isi_m2m_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX8 Image Sensor Interface memory to memory driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ISI M2M"); +MODULE_VERSION("1.0"); From f093fe32626a9a4486d0849c85723c8bd2db8cd8 Mon Sep 17 00:00:00 2001 From: "Guoniu.zhou" Date: Fri, 25 Oct 2019 10:00:38 +0800 Subject: [PATCH 8/9] staging: media: imx: enable ISI for imx8mn platform ISI is image sensor interface of imx8 family. It's reused in imx8mn platform. But they use different clock tree, so driver need to select the related clock operation for different platform. In order to solve the problem, driver define mxc_isi_dev_ops which used to control clock operation. Dispmix subsystem of imx8mn is consist of ISI, CSI, DSI and LCDIF modules. It use GPR to manage the reset and clock signal for all modules. We add a reset driver for dispmix reset function, so add related consumer of reset in ISI core driver. Signed-off-by: Guoniu.zhou --- drivers/staging/media/imx/imx8-isi-cap.c | 21 +- drivers/staging/media/imx/imx8-isi-core.c | 287 ++++++++++++++++++++-- drivers/staging/media/imx/imx8-isi-core.h | 26 ++ drivers/staging/media/imx/imx8-isi-hw.c | 21 +- drivers/staging/media/imx/imx8-isi-hw.h | 7 + 5 files changed, 336 insertions(+), 26 deletions(-) diff --git a/drivers/staging/media/imx/imx8-isi-cap.c b/drivers/staging/media/imx/imx8-isi-cap.c index cbf82bae0eaf..662780bfbb76 100644 --- a/drivers/staging/media/imx/imx8-isi-cap.c +++ b/drivers/staging/media/imx/imx8-isi-cap.c @@ -166,7 +166,8 @@ struct mxc_isi_fmt *mxc_isi_get_src_fmt(struct v4l2_subdev_format *sd_fmt) if (sd_fmt->format.code == MEDIA_BUS_FMT_YUYV8_1X16 || sd_fmt->format.code == MEDIA_BUS_FMT_YVYU8_2X8 || sd_fmt->format.code == MEDIA_BUS_FMT_AYUV8_1X32 || - sd_fmt->format.code == MEDIA_BUS_FMT_UYVY8_2X8) + sd_fmt->format.code == MEDIA_BUS_FMT_UYVY8_2X8 || + sd_fmt->format.code == MEDIA_BUS_FMT_YUYV8_2X8) index = 1; else index = 0; @@ -1134,6 +1135,7 @@ static int mxc_isi_cap_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize) { struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct device_node *parent; struct v4l2_subdev *sd; struct mxc_isi_fmt *fmt; struct v4l2_subdev_frame_size_enum fse = { @@ -1157,6 +1159,11 @@ static int mxc_isi_cap_enum_framesizes(struct file *file, void *priv, if (ret) return ret; + parent = of_get_parent(isi_cap->pdev->dev.of_node); + if ((of_device_is_compatible(parent, "fsl,imx8mn-isi")) && + (fse.max_width > ISI_2K || fse.min_width > ISI_2K)) + return -EINVAL; + if (fse.min_width == fse.max_width && fse.min_height == fse.max_height) { fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; @@ -1180,6 +1187,7 @@ static int mxc_isi_cap_enum_frameintervals(struct file *file, void *fh, struct v4l2_frmivalenum *interval) { struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct device_node *parent; struct v4l2_subdev *sd; struct mxc_isi_fmt *fmt; struct v4l2_subdev_frame_interval_enum fie = { @@ -1203,6 +1211,11 @@ static int mxc_isi_cap_enum_frameintervals(struct file *file, void *fh, if (ret) return ret; + parent = of_get_parent(isi_cap->pdev->dev.of_node); + if (of_device_is_compatible(parent, "fsl,imx8mn-isi") && + fie.width > ISI_2K) + return -EINVAL; + interval->type = V4L2_FRMIVAL_TYPE_DISCRETE; interval->discrete = fie.interval; @@ -1353,6 +1366,7 @@ static int mxc_isi_subdev_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_format *fmt) { struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd); + struct device_node *parent; struct v4l2_mbus_framefmt *mf = &fmt->format; struct mxc_isi_frame *dst_f = &isi_cap->dst_f; struct mxc_isi_fmt *out_fmt; @@ -1373,6 +1387,11 @@ static int mxc_isi_subdev_set_fmt(struct v4l2_subdev *sd, return -EINVAL; } + parent = of_get_parent(isi_cap->pdev->dev.of_node); + if (of_device_is_compatible(parent, "fsl,imx8mn-isi") && + mf->width > ISI_2K) + return -EINVAL; + mutex_lock(&isi_cap->lock); /* update out put frame size and formate */ dst_f->fmt = &mxc_isi_out_formats[i]; diff --git a/drivers/staging/media/imx/imx8-isi-core.c b/drivers/staging/media/imx/imx8-isi-core.c index 28ce6155ad9a..8127722c5796 100644 --- a/drivers/staging/media/imx/imx8-isi-core.c +++ b/drivers/staging/media/imx/imx8-isi-core.c @@ -6,6 +6,8 @@ #include "imx8-isi-hw.h" +static const struct of_device_id mxc_isi_of_match[]; + struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev) { struct mxc_isi_dev *mxc_isi; @@ -55,6 +57,7 @@ static irqreturn_t mxc_isi_irq_handler(int irq, void *priv) spin_lock(&mxc_isi->slock); status = mxc_isi_get_irq_status(mxc_isi); + mxc_isi->status = status; mxc_isi_clean_irq_status(mxc_isi, status); if (status & CHNL_STS_FRM_STRD_MASK) { @@ -88,6 +91,146 @@ static irqreturn_t mxc_isi_irq_handler(int irq, void *priv) return IRQ_HANDLED; } +static int disp_mix_sft_rstn(struct reset_control *reset, bool enable) +{ + int ret; + + if (!reset) + return 0; + + ret = enable ? reset_control_assert(reset) : + reset_control_deassert(reset); + return ret; +} + +static int disp_mix_clks_enable(struct reset_control *reset, bool enable) +{ + int ret; + + if (!reset) + return 0; + + ret = enable ? reset_control_assert(reset) : + reset_control_deassert(reset); + return ret; +} + +static int mxc_imx8_clk_get(struct mxc_isi_dev *mxc_isi) +{ + struct device *dev = &mxc_isi->pdev->dev; + + mxc_isi->clk = devm_clk_get(dev, NULL); + + if (IS_ERR(mxc_isi->clk)) { + dev_err(dev, "failed to get isi clk\n"); + return PTR_ERR(mxc_isi->clk); + } + + return 0; +} + +static int mxc_imx8_clk_enable(struct mxc_isi_dev *mxc_isi) +{ + struct device *dev = &mxc_isi->pdev->dev; + int ret; + + ret = clk_prepare_enable(mxc_isi->clk); + if (ret < 0) { + dev_err(dev, "%s, enable clk error\n", __func__); + return ret; + } + + return 0; +} + +static void mxc_imx8_clk_disable(struct mxc_isi_dev *mxc_isi) +{ + clk_disable_unprepare(mxc_isi->clk); +} + +static struct mxc_isi_dev_ops mxc_imx8_data = { + .clk_get = mxc_imx8_clk_get, + .clk_enable = mxc_imx8_clk_enable, + .clk_disable = mxc_imx8_clk_disable, +}; + +static int mxc_imx8mn_clk_get(struct mxc_isi_dev *mxc_isi) +{ + struct device *dev = &mxc_isi->pdev->dev; + + mxc_isi->clk_disp_axi = devm_clk_get(dev, "disp_axi"); + if (IS_ERR(mxc_isi->clk_disp_axi)) { + dev_err(dev, "failed to get disp_axi clk\n"); + return PTR_ERR(mxc_isi->clk_disp_axi); + } + + mxc_isi->clk_disp_apb = devm_clk_get(dev, "disp_apb"); + if (IS_ERR(mxc_isi->clk_disp_apb)) { + dev_err(dev, "failed to get disp_apb clk\n"); + return PTR_ERR(mxc_isi->clk_disp_apb); + } + + mxc_isi->clk_root_disp_axi = devm_clk_get(dev, "disp_axi_root"); + if (IS_ERR(mxc_isi->clk_root_disp_axi)) { + dev_err(dev, "failed to get disp axi root clk\n"); + return PTR_ERR(mxc_isi->clk_root_disp_axi); + } + + mxc_isi->clk_root_disp_apb = devm_clk_get(dev, "disp_apb_root"); + if (IS_ERR(mxc_isi->clk_root_disp_apb)) { + dev_err(dev, "failed to get disp apb root clk\n"); + return PTR_ERR(mxc_isi->clk_root_disp_apb); + } + + return 0; +} + +static int mxc_imx8mn_clk_enable(struct mxc_isi_dev *mxc_isi) +{ + struct device *dev = &mxc_isi->pdev->dev; + int ret; + + ret = clk_prepare_enable(mxc_isi->clk_disp_axi); + if (ret < 0) { + dev_err(dev, "prepare and enable axi clk error\n"); + return ret; + } + + ret = clk_prepare_enable(mxc_isi->clk_disp_apb); + if (ret < 0) { + dev_err(dev, "prepare and enable abp clk error\n"); + return ret; + } + + ret = clk_prepare_enable(mxc_isi->clk_root_disp_axi); + if (ret < 0) { + dev_err(dev, "prepare and enable axi root clk error\n"); + return ret; + } + + ret = clk_prepare_enable(mxc_isi->clk_root_disp_apb); + if (ret < 0) { + dev_err(dev, "prepare and enable apb root clk error\n"); + return ret; + } + + return 0; +} + +static void mxc_imx8mn_clk_disable(struct mxc_isi_dev *mxc_isi) +{ + clk_disable_unprepare(mxc_isi->clk_root_disp_axi); + clk_disable_unprepare(mxc_isi->clk_root_disp_apb); + clk_disable_unprepare(mxc_isi->clk_disp_axi); + clk_disable_unprepare(mxc_isi->clk_disp_apb); +} + +static struct mxc_isi_dev_ops mxc_imx8mn_data = { + .clk_get = mxc_imx8mn_clk_get, + .clk_enable = mxc_imx8mn_clk_enable, + .clk_disable = mxc_imx8mn_clk_disable, +}; + static int mxc_isi_parse_dt(struct mxc_isi_dev *mxc_isi) { struct device *dev = &mxc_isi->pdev->dev; @@ -109,25 +252,114 @@ static int mxc_isi_parse_dt(struct mxc_isi_dev *mxc_isi) return 0; } +static int mxc_isi_clk_get(struct mxc_isi_dev *mxc_isi) +{ + const struct mxc_isi_dev_ops *ops = mxc_isi->ops; + + if (!ops && !ops->clk_get) + return -EINVAL; + + return ops->clk_get(mxc_isi); +} + +static int mxc_isi_clk_enable(struct mxc_isi_dev *mxc_isi) +{ + const struct mxc_isi_dev_ops *ops = mxc_isi->ops; + + if (!ops && !ops->clk_enable) + return -EINVAL; + + return ops->clk_enable(mxc_isi); +} + +static void mxc_isi_clk_disable(struct mxc_isi_dev *mxc_isi) +{ + const struct mxc_isi_dev_ops *ops = mxc_isi->ops; + + if (!ops && !ops->clk_disable) + return; + + ops->clk_disable(mxc_isi); +} + +static int mxc_isi_of_parse_resets(struct mxc_isi_dev *mxc_isi) +{ + int ret; + struct device *dev = &mxc_isi->pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *parent, *child; + struct of_phandle_args args; + struct reset_control *rstc; + const char *compat; + uint32_t len, rstc_num = 0; + + ret = of_parse_phandle_with_args(np, "resets", "#reset-cells", + 0, &args); + if (ret) + return ret; + + parent = args.np; + for_each_child_of_node(parent, child) { + compat = of_get_property(child, "compatible", NULL); + if (!compat) + continue; + + rstc = of_reset_control_array_get(child, false, false, true); + if (IS_ERR(rstc)) + continue; + + len = strlen(compat); + if (!of_compat_cmp("isi,soft-resetn", compat, len)) { + mxc_isi->soft_resetn = rstc; + rstc_num++; + } else if (!of_compat_cmp("isi,clk-enable", compat, len)) { + mxc_isi->clk_enable = rstc; + rstc_num++; + } else { + dev_warn(dev, "invalid isi reset node: %s\n", compat); + } + } + + if (!rstc_num) { + dev_err(dev, "no invalid reset control exists\n"); + return -EINVAL; + } + + of_node_put(parent); + return 0; +} + static int mxc_isi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mxc_isi_dev *mxc_isi; struct resource *res; + const struct of_device_id *of_id; int ret = 0; + mxc_isi = devm_kzalloc(dev, sizeof(*mxc_isi), GFP_KERNEL); if (!mxc_isi) return -ENOMEM; mxc_isi->pdev = pdev; + of_id = of_match_node(mxc_isi_of_match, dev->of_node); + if (!of_id) + return -EINVAL; + + mxc_isi->ops = of_id->data; + if (!mxc_isi->ops) { + dev_err(dev, "Can't get platform device data\n"); + return -EINVAL; + } ret = mxc_isi_parse_dt(mxc_isi); if (ret < 0) return ret; if (mxc_isi->id >= MXC_ISI_MAX_DEVS || mxc_isi->id < 0) { - dev_err(dev, "Invalid driver data or device id (%d)\n", mxc_isi->id); + dev_err(dev, "Invalid driver data or device id (%d)\n", + mxc_isi->id); return -EINVAL; } @@ -135,16 +367,18 @@ static int mxc_isi_probe(struct platform_device *pdev) mutex_init(&mxc_isi->lock); atomic_set(&mxc_isi->usage_count, 0); - mxc_isi->clk = devm_clk_get(dev, NULL); - if (IS_ERR(mxc_isi->clk)) { - dev_err(dev, "failed to get isi clk\n"); - return PTR_ERR(mxc_isi->clk); + if (of_device_is_compatible(dev->of_node, "fsl,imx8mn-isi")) { + ret = mxc_isi_of_parse_resets(mxc_isi); + if (ret) { + dev_warn(dev, "Can not parse reset control\n"); + return ret; + } } - ret = clk_prepare_enable(mxc_isi->clk); + ret = mxc_isi_clk_get(mxc_isi); if (ret < 0) { - dev_err(dev, "Prepare and enable isi clk error (%d)\n", ret); - return -EINVAL; + dev_err(dev, "ISI_%d get clocks fail\n", mxc_isi->id); + return ret; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -154,18 +388,26 @@ static int mxc_isi_probe(struct platform_device *pdev) return PTR_ERR(mxc_isi->regs); } + ret = mxc_isi_clk_enable(mxc_isi); + if (ret < 0) { + dev_err(dev, "ISI_%d enable clocks fail\n", mxc_isi->id); + return ret; + } + disp_mix_sft_rstn(mxc_isi->soft_resetn, false); + disp_mix_clks_enable(mxc_isi->clk_enable, true); + mxc_isi_clean_registers(mxc_isi); res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(dev, "Failed to get IRQ resource\n"); - return -ENXIO; + goto err; } ret = devm_request_irq(dev, res->start, mxc_isi_irq_handler, 0, dev_name(dev), mxc_isi); if (ret < 0) { dev_err(dev, "failed to install irq (%d)\n", ret); - return -EINVAL; + goto err; } mxc_isi_channel_set_chain_buf(mxc_isi); @@ -174,13 +416,19 @@ static int mxc_isi_probe(struct platform_device *pdev) if (ret < 0) dev_warn(dev, "Populate child platform device fail\n"); - clk_disable_unprepare(mxc_isi->clk); + mxc_isi_clk_disable(mxc_isi); platform_set_drvdata(pdev, mxc_isi); pm_runtime_enable(dev); dev_info(dev, "mxc_isi.%d registered successfully\n", mxc_isi->id); return 0; + +err: + disp_mix_clks_enable(mxc_isi->clk_enable, false); + disp_mix_sft_rstn(mxc_isi->soft_resetn, true); + mxc_isi_clk_disable(mxc_isi); + return -ENXIO; } static int mxc_isi_remove(struct platform_device *pdev) @@ -213,7 +461,9 @@ static int mxc_isi_runtime_suspend(struct device *dev) { struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev); - clk_disable_unprepare(mxc_isi->clk); + disp_mix_clks_enable(mxc_isi->clk_enable, false); + mxc_isi_clk_disable(mxc_isi); + return 0; } @@ -222,11 +472,15 @@ static int mxc_isi_runtime_resume(struct device *dev) struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev); int ret; - ret = clk_prepare_enable(mxc_isi->clk); - if (ret) + ret = mxc_isi_clk_enable(mxc_isi); + if (ret) { dev_err(dev, "%s clk enable fail\n", __func__); + return ret; + } + disp_mix_sft_rstn(mxc_isi->soft_resetn, false); + disp_mix_clks_enable(mxc_isi->clk_enable, true); - return (ret) ? ret : 0; + return 0; } static const struct dev_pm_ops mxc_isi_pm_ops = { @@ -235,7 +489,8 @@ static const struct dev_pm_ops mxc_isi_pm_ops = { }; static const struct of_device_id mxc_isi_of_match[] = { - {.compatible = "fsl,imx8-isi",}, + {.compatible = "fsl,imx8-isi", .data = &mxc_imx8_data }, + {.compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, mxc_isi_of_match); diff --git a/drivers/staging/media/imx/imx8-isi-core.h b/drivers/staging/media/imx/imx8-isi-core.h index 24fbfbd9c3b9..44f2a25271ee 100644 --- a/drivers/staging/media/imx/imx8-isi-core.h +++ b/drivers/staging/media/imx/imx8-isi-core.h @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include "imx8-common.h" @@ -41,6 +43,8 @@ #define MXC_ISI_M2M "mxc-isi-m2m" #define MXC_MAX_PLANES 3 +struct mxc_isi_dev; + enum mxc_isi_out_fmt { MXC_ISI_OUT_FMT_RGBA32 = 0x0, MXC_ISI_OUT_FMT_ABGR32, @@ -188,6 +192,7 @@ struct mxc_isi_buffer { struct vb2_v4l2_buffer v4l2_buf; struct list_head list; struct frame_addr paddr; + enum mxc_isi_buf_id id; bool discard; }; @@ -223,6 +228,12 @@ struct mxc_isi_ctx { struct v4l2_fh fh; }; +struct mxc_isi_dev_ops { + int (*clk_get)(struct mxc_isi_dev *mxc_isi); + int (*clk_enable)(struct mxc_isi_dev *mxc_isi); + void (*clk_disable)(struct mxc_isi_dev *mxc_isi); +}; + struct mxc_isi_cap_dev { struct v4l2_subdev sd; struct video_device vdev; @@ -265,8 +276,21 @@ struct mxc_isi_dev { struct mxc_isi_m2m_dev *isi_m2m; struct platform_device *pdev; + + /* clk for imx8qxp/qm platform */ struct clk *clk; + /* clks for imx8mn platform */ + struct clk *clk_disp_axi; + struct clk *clk_disp_apb; + struct clk *clk_root_disp_axi; + struct clk *clk_root_disp_apb; + + const struct mxc_isi_dev_ops *ops; + + struct reset_control *soft_resetn; + struct reset_control *clk_enable; + struct mutex lock; spinlock_t slock; @@ -285,6 +309,8 @@ struct mxc_isi_dev { u32 pre_dec_x; u32 pre_dec_y; + u32 status; + u32 interface[MAX_PORTS]; int id; diff --git a/drivers/staging/media/imx/imx8-isi-hw.c b/drivers/staging/media/imx/imx8-isi-hw.c index e39cb47ed81c..6605341e0b57 100644 --- a/drivers/staging/media/imx/imx8-isi-hw.c +++ b/drivers/staging/media/imx/imx8-isi-hw.c @@ -101,12 +101,13 @@ static void printk_pixelformat(char *prefix, int val) static bool is_rgb(u32 pix_fmt) { if ((pix_fmt == V4L2_PIX_FMT_RGB565) || - (pix_fmt == V4L2_PIX_FMT_RGB24) || - (pix_fmt == V4L2_PIX_FMT_RGB32) || - (pix_fmt == V4L2_PIX_FMT_BGR32) || + (pix_fmt == V4L2_PIX_FMT_RGB24) || + (pix_fmt == V4L2_PIX_FMT_RGB32) || + (pix_fmt == V4L2_PIX_FMT_BGR32) || (pix_fmt == V4L2_PIX_FMT_XRGB32) || (pix_fmt == V4L2_PIX_FMT_XBGR32) || - (pix_fmt == V4L2_PIX_FMT_BGR24) || + (pix_fmt == V4L2_PIX_FMT_BGR24) || + (pix_fmt == V4L2_PIX_FMT_RGBA) || (pix_fmt == V4L2_PIX_FMT_ABGR32) || (pix_fmt == V4L2_PIX_FMT_ARGB32)) return true; @@ -116,9 +117,10 @@ static bool is_rgb(u32 pix_fmt) static bool is_yuv(u32 pix_fmt) { - if ((pix_fmt == V4L2_PIX_FMT_YUYV) || + if ((pix_fmt == V4L2_PIX_FMT_YUYV) || (pix_fmt == V4L2_PIX_FMT_YUV32) || (pix_fmt == V4L2_PIX_FMT_YUV444M) || + (pix_fmt == V4L2_PIX_FMT_YUV24) || (pix_fmt == V4L2_PIX_FMT_NV12)) return true; else @@ -129,7 +131,7 @@ static void chain_buf(struct mxc_isi_dev *mxc_isi, struct mxc_isi_frame *frm) { u32 val; - if (frm->o_width > 2048) { + if (frm->o_width > ISI_2K) { val = readl(mxc_isi->regs + CHNL_CTRL); val &= ~CHNL_CTRL_CHAIN_BUF_MASK; val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET); @@ -174,16 +176,18 @@ void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi, val = readl(mxc_isi->regs + CHNL_OUT_BUF_CTRL); - if (framecount % 2 == 0) { + if (framecount == 0 || ((mxc_isi->status & 0x100) && (framecount != 1))) { writel(paddr->y, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_Y); writel(paddr->cb, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_U); writel(paddr->cr, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_V); val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_MASK; - } else if (framecount % 2 == 1) { + buf->id = MXC_ISI_BUF1; + } else if (framecount == 1 || mxc_isi->status & 0x200) { writel(paddr->y, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_Y); writel(paddr->cb, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_U); writel(paddr->cr, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_V); val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_MASK; + buf->id = MXC_ISI_BUF2; } writel(val, mxc_isi->regs + CHNL_OUT_BUF_CTRL); } @@ -701,7 +705,6 @@ void mxc_isi_m2m_config_src(struct mxc_isi_dev *mxc_isi, void mxc_isi_m2m_config_dst(struct mxc_isi_dev *mxc_isi, struct mxc_isi_frame *dst_f) { - /*struct mxc_isi_frame *dst_f = &mxc_isi->isi_m2m->dst_f;*/ u32 val; /* out format */ diff --git a/drivers/staging/media/imx/imx8-isi-hw.h b/drivers/staging/media/imx/imx8-isi-hw.h index eefb18d81cea..268be48d1f7f 100644 --- a/drivers/staging/media/imx/imx8-isi-hw.h +++ b/drivers/staging/media/imx/imx8-isi-hw.h @@ -447,6 +447,13 @@ #define CHNL_SCL_IMG_CFG_WIDTH_OFFSET 0 #define CHNL_SCL_IMG_CFG_WIDTH_MASK 0x1FFF +/* Channel Flow Control Register */ +#define CHNL_FLOW_CTRL 0x9C +#define CHNL_FLOW_CTRL_FC_DENOM_MASK 0xFF +#define CHNL_FLOW_CTRL_FC_DENOM_OFFSET 0 +#define CHNL_FLOW_CTRL_FC_NUMER_MASK 0xFF0000 +#define CHNL_FLOW_CTRL_FC_NUMER_OFFSET 0 + enum isi_csi_coeff { YUV2RGB = 0, RGB2YUV, From 6e2908e6e75e9f7f3a297c70bf006694e95c4838 Mon Sep 17 00:00:00 2001 From: "Guoniu.zhou" Date: Thu, 21 Nov 2019 14:35:33 +0800 Subject: [PATCH 9/9] staging: media: imx: add g_parm/s_parm for imx8 capture device Add g_parm/s_parm for imx8 capture device Signed-off-by: Guoniu.zhou --- drivers/staging/media/imx/imx8-isi-cap.c | 42 +++++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/drivers/staging/media/imx/imx8-isi-cap.c b/drivers/staging/media/imx/imx8-isi-cap.c index 662780bfbb76..f24205cbd3d4 100644 --- a/drivers/staging/media/imx/imx8-isi-cap.c +++ b/drivers/staging/media/imx/imx8-isi-cap.c @@ -880,7 +880,7 @@ static int mxc_isi_source_fmt_init(struct mxc_isi_cap_dev *isi_cap) ret = v4l2_subdev_call(src_sd, pad, set_fmt, NULL, &src_fmt); if (ret < 0 && ret != -ENOIOCTLCMD) { v4l2_err(&isi_cap->sd, "set remote fmt fail!\n"); - return -EINVAL; + return ret; } memset(&src_fmt, 0, sizeof(src_fmt)); @@ -889,7 +889,7 @@ static int mxc_isi_source_fmt_init(struct mxc_isi_cap_dev *isi_cap) ret = v4l2_subdev_call(src_sd, pad, get_fmt, NULL, &src_fmt); if (ret < 0 && ret != -ENOIOCTLCMD) { v4l2_err(&isi_cap->sd, "get remote fmt fail!\n"); - return -EINVAL; + return ret; } /* Pixel link master will transfer format to RGB32 or YUV32 */ @@ -1003,6 +1003,33 @@ static int mxc_isi_config_parm(struct mxc_isi_cap_dev *isi_cap) return 0; } +static int mxc_isi_cap_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct v4l2_subdev *sd; + + sd = mxc_get_remote_subdev(isi_cap, __func__); + if (!sd) + return -ENODEV; + + return v4l2_g_parm_cap(video_devdata(file), sd, a); +} + +static int mxc_isi_cap_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct v4l2_subdev *sd; + + sd = mxc_get_remote_subdev(isi_cap, __func__); + if (!sd) + return -ENODEV; + + return v4l2_s_parm_cap(video_devdata(file), sd, a); +} + + static int mxc_isi_cap_streamon(struct file *file, void *priv, enum v4l2_buf_type type) { @@ -1014,15 +1041,17 @@ static int mxc_isi_cap_streamon(struct file *file, void *priv, ret = mxc_isi_config_parm(isi_cap); if (ret < 0) - return -EINVAL; + return ret; ret = vb2_ioctl_streamon(file, priv, type); mxc_isi_channel_enable(mxc_isi, mxc_isi->m2m_enabled); - mxc_isi_pipeline_enable(isi_cap, 1); + ret = mxc_isi_pipeline_enable(isi_cap, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; mxc_isi->is_streaming = 1; - return ret; + return 0; } static int mxc_isi_cap_streamoff(struct file *file, void *priv, @@ -1238,6 +1267,9 @@ static const struct v4l2_ioctl_ops mxc_isi_capture_ioctl_ops = { .vidioc_prepare_buf = vb2_ioctl_prepare_buf, .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_g_parm = mxc_isi_cap_g_parm, + .vidioc_s_parm = mxc_isi_cap_s_parm, + .vidioc_streamon = mxc_isi_cap_streamon, .vidioc_streamoff = mxc_isi_cap_streamoff,