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>; + }; 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 e56fd009d899..0bad318383d3 100644 --- a/drivers/staging/media/imx/Kconfig +++ b/drivers/staging/media/imx/Kconfig @@ -47,6 +47,23 @@ config VIDEO_IMX_CAPTURE if VIDEO_IMX_CAPTURE menu "i.MX8QXP/QM Camera ISI/MIPI Features support" +config IMX8_ISI_CORE + bool "IMX8 Image Sensor Interface Core Driver" + 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 + +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 + config IMX8_MIPI_CSI2 tristate "IMX8 MIPI CSI2 Controller" depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile index 64faccf117e4..1805059ba6ea 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-hw.o + imx-media-common-objs := imx-media-capture.o imx-media-dev-common.o \ imx-media-of.o imx-media-utils.o @@ -16,5 +18,8 @@ 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 +obj-$(CONFIG_IMX8_ISI_M2M) += imx8-isi-m2m.o obj-$(CONFIG_IMX8_MIPI_CSI2) += imx8-mipi-csi2.o obj-$(CONFIG_IMX8_MIPI_CSI2_SAM) += imx8-mipi-csi2-sam.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..f24205cbd3d4 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-cap.c @@ -0,0 +1,1766 @@ +// 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" + +#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", + .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 || + sd_fmt->format.code == MEDIA_BUS_FMT_YUYV8_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_cap_dev *isi_cap, bool enable) +{ + 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; + 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; + + paddr->cb = 0; + paddr->cr = 0; + + 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; + default: + return -EINVAL; + } + + return 0; +} + +void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi) +{ + 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(&isi_cap->out_active)) { + dev_warn(dev, "trying to access empty active list\n"); + return; + } + + buf = list_first_entry(&isi_cap->out_active, struct mxc_isi_buffer, list); + + if (buf->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); + buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE); + } + + isi_cap->frame_count++; + + 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(&isi_cap->out_discard, + struct mxc_isi_buffer, list); + buf->v4l2_buf.sequence = isi_cap->frame_count; + mxc_isi_channel_set_outbuf(mxc_isi, buf); + list_move_tail(isi_cap->out_discard.next, &isi_cap->out_active); + return; + } + + /* ISI channel output buffer */ + 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(isi_cap->out_pending.next, &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_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; + + if (!fmt) + return -EINVAL; + + for (i = 0; i < fmt->memplanes; i++) + alloc_devs[i] = &isi_cap->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(&isi_cap->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_cap_dev *isi_cap = vb2_get_drv_priv(q); + struct mxc_isi_frame *dst_f = &isi_cap->dst_f; + int i; + + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); + + 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(&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_cap_dev *isi_cap = vb2_get_drv_priv(vb2->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&isi_cap->slock, flags); + + mxc_isi_update_buf_paddr(buf, isi_cap->dst_f.fmt->mdataplanes); + list_add_tail(&buf->list, &isi_cap->out_pending); + + spin_unlock_irqrestore(&isi_cap->slock, flags); +} + +static int cap_vb2_start_streaming(struct vb2_queue *q, unsigned int count) +{ + 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(&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 < 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 (!isi_cap->discard_buffer[i]) { + for (j = 0; j < i; j++) { + 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(&isi_cap->pdev->dev, + "%s: num_plane=%d discard_size=%d discard_buffer=%p\n" + , __func__, i, + (int)isi_cap->discard_size[i], + isi_cap->discard_buffer[i]); + } + + spin_lock_irqsave(&isi_cap->slock, flags); + + /* add two list member to out_discard list head */ + isi_cap->buf_discard[0].discard = true; + list_add_tail(&isi_cap->buf_discard[0].list, &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(&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(isi_cap->out_discard.next, &isi_cap->out_active); + + /* ISI channel output buffer 2 */ + 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(isi_cap->out_pending.next, &isi_cap->out_active); + + /* Clear frame count */ + 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_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(&isi_cap->pdev->dev, "%s\n", __func__); + + mxc_isi_channel_disable(mxc_isi); + + spin_lock_irqsave(&isi_cap->slock, flags); + + 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) + continue; + + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); + } + + 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(&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, &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, &isi_cap->out_pending, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR); + } + + INIT_LIST_HEAD(&isi_cap->out_active); + INIT_LIST_HEAD(&isi_cap->out_pending); + INIT_LIST_HEAD(&isi_cap->out_discard); + + spin_unlock_irqrestore(&isi_cap->slock, flags); + + 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 = { + .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_cap_dev *ctrl_to_isi_cap(struct v4l2_ctrl *ctrl) +{ + 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_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(&isi_cap->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(&isi_cap->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_cap_dev *isi_cap) +{ + struct mxc_isi_ctrls *ctrls = &isi_cap->ctrls; + struct v4l2_ctrl_handler *handler = &ctrls->handler; + + if (isi_cap->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_cap_dev *isi_cap) +{ + struct mxc_isi_ctrls *ctrls = &isi_cap->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_cap_dev *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_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(isi_cap); + if (!source_pad) { + v4l2_err(&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(&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_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; + int ret = -EBUSY; + + if (mxc_isi->m2m_enabled) { + dev_err(dev, "ISI channel[%d] is busy\n", isi_cap->id); + return ret; + } + + sd = mxc_get_remote_subdev(isi_cap, __func__); + if (!sd) + return -ENODEV; + + mutex_lock(&isi_cap->lock); + ret = v4l2_fh_open(file); + 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) { + dev_err(dev, "Call subdev s_power fail!\n"); + pm_runtime_put(dev); + return ret; + } + + /* 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); + + return 0; +} + +static int mxc_isi_capture_release(struct file *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; + int ret = -1; + + sd = mxc_get_remote_subdev(isi_cap, __func__); + if (!sd) + goto label; + + mutex_lock(&isi_cap->lock); + ret = _vb2_fop_release(file, NULL); + if (ret) { + dev_err(dev, "%s fail\n", __func__); + mutex_unlock(&isi_cap->lock); + goto label; + } + mutex_unlock(&isi_cap->lock); + + 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) { + dev_err(dev, "%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_cap_dev *isi_cap = video_drvdata(file); + + 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(&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; + + return 0; +} + +static int mxc_isi_cap_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_isi_cap_dev *isi_cap = video_drvdata(file); + struct mxc_isi_fmt *fmt; + + dev_dbg(&isi_cap->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_cap_dev *isi_cap = video_drvdata(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_frame *dst_f = &isi_cap->dst_f; + int i; + + dev_dbg(&isi_cap->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_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(&isi_cap->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(&isi_cap->sd, "format(%.4s) is not support!\n", + (char *)&pix->pixelformat); + return -EINVAL; + } + + if (pix->width <= 0 || pix->height <= 0) { + v4l2_err(&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_cap_dev *isi_cap) +{ + 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(isi_cap); + if (!source_pad) { + v4l2_err(&isi_cap->sd, + "%s, No remote pad found!\n", __func__); + return -EINVAL; + } + + src_sd = mxc_get_remote_subdev(isi_cap, __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(&isi_cap->sd, "set remote fmt fail!\n"); + return ret; + } + + 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(&isi_cap->sd, "get remote fmt fail!\n"); + return ret; + } + + /* 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(&isi_cap->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_cap_dev *isi_cap = video_drvdata(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mxc_isi_frame *dst_f = &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(&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 */ + 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(&isi_cap->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(&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_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(isi_cap); + if (ret < 0) + return -EINVAL; + + mxc_isi_channel_init(mxc_isi); + mxc_isi_channel_config(mxc_isi, &isi_cap->src_f, &isi_cap->dst_f); + + 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) +{ + 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(&isi_cap->pdev->dev, "%s\n", __func__); + + ret = mxc_isi_config_parm(isi_cap); + if (ret < 0) + return ret; + + ret = vb2_ioctl_streamon(file, priv, type); + mxc_isi_channel_enable(mxc_isi, mxc_isi->m2m_enabled); + ret = mxc_isi_pipeline_enable(isi_cap, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + mxc_isi->is_streaming = 1; + + return 0; +} + +static int mxc_isi_cap_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + 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(&isi_cap->pdev->dev, "%s\n", __func__); + + mxc_isi_pipeline_enable(isi_cap, 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_cap_dev *isi_cap = video_drvdata(file); + struct mxc_isi_frame *f = &isi_cap->src_f; + + dev_dbg(&isi_cap->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 = &isi_cap->dst_f; + /* fall through */ + 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 = &isi_cap->dst_f; + /* fall through */ + 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_cap_dev *isi_cap = video_drvdata(file); + struct mxc_isi_frame *f; + struct v4l2_rect rect = s->r; + unsigned long flags; + + 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 = &isi_cap->dst_f; + else if (s->target == V4L2_SEL_TGT_CROP) + f = &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(&isi_cap->slock, flags); + set_frame_crop(f, s->r.left, s->r.top, s->r.width, + s->r.height); + spin_unlock_irqrestore(&isi_cap->slock, flags); + + return 0; +} + +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 = { + .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(isi_cap, __func__); + if (!sd) { + v4l2_err(&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; + + 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; + 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_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 = { + .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(isi_cap, __func__); + if (!sd) + return -EINVAL; + + ret = v4l2_subdev_call(sd, pad, enum_frame_interval, NULL, &fie); + 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; + + return 0; +} + +static const struct v4l2_ioctl_ops mxc_isi_capture_ioctl_ops = { + .vidioc_querycap = mxc_isi_cap_querycap, + + .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, + + .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_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, + + .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_cap_dev *isi_cap = v4l2_get_subdevdata(sd); + + if (WARN_ON(!isi_cap)) + 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(&isi_cap->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(&isi_cap->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_cap_dev *isi_cap = v4l2_get_subdevdata(sd); + struct mxc_isi_frame *f; + struct v4l2_mbus_framefmt *mf; + + 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 = &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 = &isi_cap->src_f; + break; + default: + mutex_unlock(&isi_cap->lock); + v4l2_err(&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(&isi_cap->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_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; + int i; + + if (fmt->pad < MXC_ISI_SD_PAD_SOURCE_MEM && + vb2_is_busy(&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(&isi_cap->sd, + "%s, format is not support!\n", __func__); + 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]; + set_frame_bounds(dst_f, mf->width, mf->height); + mutex_unlock(&isi_cap->lock); + + dev_dbg(&isi_cap->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_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(&isi_cap->lock); + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + 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(&isi_cap->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 = &isi_cap->dst_f; + break; + default: + mutex_unlock(&isi_cap->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(&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(&isi_cap->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_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(&isi_cap->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 = &isi_cap->dst_f; + break; + default: + mutex_unlock(&isi_cap->lock); + return -EINVAL; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + *try_sel = sel->r; + } else { + spin_lock_irqsave(&isi_cap->slock, flags); + set_frame_crop(f, r->left, r->top, r->width, r->height); + spin_unlock_irqrestore(&isi_cap->slock, flags); + } + + 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(&isi_cap->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_cap_dev *isi_cap, + struct v4l2_device *v4l2_dev) +{ + struct video_device *vdev = &isi_cap->vdev; + struct vb2_queue *q = &isi_cap->vb2_q; + int ret = -ENOMEM; + + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); + memset(vdev, 0, sizeof(*vdev)); + 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; + vdev->v4l2_dev = v4l2_dev; + vdev->minor = -1; + vdev->release = video_device_release_empty; + vdev->queue = q; + vdev->lock = &isi_cap->lock; + + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE; + video_set_drvdata(vdev, isi_cap); + + 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 = 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 = &isi_cap->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(isi_cap); + if (ret) + goto err_me_cleanup; + + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret) + goto err_ctrl_free; + + 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(isi_cap); +err_me_cleanup: + media_entity_cleanup(&vdev->entity); +err_free_ctx: + return ret; +} + +static int mxc_isi_subdev_registered(struct v4l2_subdev *sd) +{ + struct mxc_isi_cap_dev *isi_cap = sd_to_cap_dev(sd); + int ret; + + if (!isi_cap) + return -ENXIO; + + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); + + ret = mxc_isi_register_cap_device(isi_cap, sd->v4l2_dev); + if (ret < 0) + return ret; + + return 0; +} + +static void mxc_isi_subdev_unregistered(struct v4l2_subdev *sd) +{ + struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd); + struct video_device *vdev; + + if (!isi_cap) + return; + + dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__); + + mutex_lock(&isi_cap->lock); + vdev = &isi_cap->vdev; + if (video_is_registered(vdev)) { + video_unregister_device(vdev); + mxc_isi_ctrls_delete(isi_cap); + media_entity_cleanup(&vdev->entity); + } + mutex_unlock(&isi_cap->lock); +} + +static const struct v4l2_subdev_internal_ops mxc_isi_capture_sd_internal_ops = { + .registered = mxc_isi_subdev_registered, + .unregistered = mxc_isi_subdev_unregistered, +}; + +static int isi_cap_probe(struct platform_device *pdev) +{ + 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", isi_cap->id); + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + + /* ISI Sink pads */ + 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 */ + 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, 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, isi_cap); + platform_set_drvdata(pdev, isi_cap); + + pm_runtime_enable(dev); + return 0; +} + +static int isi_cap_remove(struct platform_device *pdev) +{ + 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"); 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..8127722c5796 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-core.c @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 NXP Semiconductor + * + */ + +#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; + + 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; + struct device *dev = &mxc_isi->pdev->dev; + u32 status; + + 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) { + 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 | + 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 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; + struct device_node *node = dev->of_node; + int ret = 0; + + mxc_isi->id = of_alias_get_id(node, "isi"); + 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_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); + return -EINVAL; + } + + spin_lock_init(&mxc_isi->slock); + mutex_init(&mxc_isi->lock); + atomic_set(&mxc_isi->usage_count, 0); + + 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 = mxc_isi_clk_get(mxc_isi); + if (ret < 0) { + dev_err(dev, "ISI_%d get clocks fail\n", mxc_isi->id); + return ret; + } + + 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); + } + + 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"); + 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); + goto err; + } + + 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"); + + 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) +{ + struct device *dev = &pdev->dev; + + 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); + + disp_mix_clks_enable(mxc_isi->clk_enable, false); + mxc_isi_clk_disable(mxc_isi); + + return 0; +} + +static int mxc_isi_runtime_resume(struct device *dev) +{ + struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev); + int 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 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", .data = &mxc_imx8_data }, + {.compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data }, + { /* 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..44f2a25271ee --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-core.h @@ -0,0 +1,364 @@ +/* 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 +#include + +#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 + +struct mxc_isi_dev; + +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, +}; + +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; + 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; + struct v4l2_ctrl *num_cap_buf; + struct v4l2_ctrl *num_out_buf; + 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; + enum mxc_isi_buf_id id; + 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_m2m_dev *isi_m2m; + 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; + struct v4l2_fh fh; + struct vb2_queue vb2_q; + struct v4l2_pix_format_mplane pix; + + struct mxc_isi_dev *mxc_isi; + struct platform_device *pdev; + struct mxc_isi_ctrls ctrls; + struct mxc_isi_buffer buf_discard[2]; + + 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 { + /* Pointer to isi capture child device driver data */ + struct mxc_isi_cap_dev *isi_cap; + + /* Pointer to isi m2m child device driver data */ + 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; + + 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 status; + + u32 interface[MAX_PORTS]; + int id; + + 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, + 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; +} + +#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 new file mode 100644 index 000000000000..6605341e0b57 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-hw.c @@ -0,0 +1,734 @@ +// 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_RGBA) || + (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_YUV24) || + (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 *frm) +{ + u32 val; + + 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); + 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; + 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) { + 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 = isi_cap->discard_buffer_dma[1]; + if (pix->num_planes == 3) { + 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); + + 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 == 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; + 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); +} + +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; + + 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_frame *src_f, + struct mxc_isi_frame *dst_f) +{ + 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); + 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); + + 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); +} + +void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi) +{ + u32 val; + + if (mxc_isi->chain_buf) { + 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 *src_f, + struct mxc_isi_frame *dst_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 *src_f, + struct mxc_isi_frame *dst_f) +{ + u32 val; + + /* images having higher than 2048 horizontal resolution */ + 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); + 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, src_f, dst_f); + + 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); + + /* line pitch */ + val = dst_f->bytesperline[0]; + writel(val, mxc_isi->regs + CHNL_OUT_BUF_PITCH); + + /* TODO */ + mxc_isi_channel_set_flip(mxc_isi); + + 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, bool m2m_enabled) +{ + u32 val; + + val = readl(mxc_isi->regs + CHNL_CTRL); + 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); + + 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) +{ + 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); +} + +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) +{ + 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 new file mode 100644 index 000000000000..268be48d1f7f --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-hw.h @@ -0,0 +1,521 @@ +/* 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 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 + +/* 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 + +/* 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, +}; + +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_enable(struct mxc_isi_dev *mxc_isi, bool m2m_enabled); +void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi); +#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_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_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); +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__ */ 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");